зеркало из https://github.com/mozilla/gecko-dev.git
Backed out 3 changesets (bug 1435360) for web-platform reftests failures on marionette/content/reftest.js CLOSED TREE
Backed out changeset c4bc47b31311 (bug 1435360) Backed out changeset eb7a4a96c333 (bug 1435360) Backed out changeset 7d6183aa40da (bug 1435360)
This commit is contained in:
Родитель
ae7ee28db1
Коммит
575a3a3976
|
@ -207,12 +207,17 @@ function verifyView(view1, view2)
|
|||
|
||||
function verifyWasmModule(module1, module2)
|
||||
{
|
||||
// We assume the given modules have no imports and export a single function
|
||||
// named 'run'.
|
||||
var instance1 = new WebAssembly.Instance(module1);
|
||||
var instance2 = new WebAssembly.Instance(module2);
|
||||
is(instance1.exports.run(), instance2.exports.run(), "same run() result");
|
||||
|
||||
let getGlobalForObject = SpecialPowers.Cu.getGlobalForObject;
|
||||
let testingFunctions = SpecialPowers.Cu.getJSTestingFunctions();
|
||||
let wasmExtractCode = SpecialPowers.unwrap(testingFunctions.wasmExtractCode);
|
||||
let exp1 = wasmExtractCode(module1, "ion");
|
||||
let exp2 = wasmExtractCode(module2, "ion");
|
||||
let code1 = exp1.code;
|
||||
let code2 = exp2.code;
|
||||
ok(code1 instanceof getGlobalForObject(code1).Uint8Array, "Instance of Uint8Array");
|
||||
ok(code2 instanceof getGlobalForObject(code1).Uint8Array, "Instance of Uint8Array");
|
||||
ok(code1.length == code2.length, "Correct length");
|
||||
verifyBuffers(code1, code2);
|
||||
continueToNextStep();
|
||||
}
|
||||
|
||||
|
|
|
@ -23,35 +23,35 @@ function* testSteps()
|
|||
return;
|
||||
}
|
||||
|
||||
getWasmBinary(`(module (func (export "run") (result i32) (i32.const 1)))`);
|
||||
getWasmBinary("(module (func (result i32) (i32.const 1)))");
|
||||
let binary = yield undefined;
|
||||
wasmData[1].value[0] = getWasmModule(binary);
|
||||
|
||||
getWasmBinary(`(module (func (export "run") (result i32) (i32.const 2)))`);
|
||||
getWasmBinary("(module (func (result i32) (i32.const 2)))");
|
||||
binary = yield undefined;
|
||||
wasmData[1].value[1] = getWasmModule(binary);
|
||||
|
||||
getWasmBinary(`(module (func (export "run") (result i32) (i32.const 3)))`);
|
||||
getWasmBinary("(module (func (result i32) (i32.const 3)))");
|
||||
binary = yield undefined;
|
||||
wasmData[1].value[2] = getWasmModule(binary);
|
||||
|
||||
getWasmBinary(`(module (func (export "run") (result i32) (i32.const 4)))`);
|
||||
getWasmBinary("(module (func (result i32) (i32.const 4)))");
|
||||
binary = yield undefined;
|
||||
wasmData[2].value[0] = getWasmModule(binary);
|
||||
|
||||
getWasmBinary(`(module (func (export "run") (result i32) (i32.const 5)))`);
|
||||
getWasmBinary("(module (func (result i32) (i32.const 5)))");
|
||||
binary = yield undefined;
|
||||
wasmData[2].value[1] = getWasmModule(binary);
|
||||
|
||||
getWasmBinary(`(module (func (export "run") (result i32) (i32.const 6)))`);
|
||||
getWasmBinary("(module (func (result i32) (i32.const 6)))");
|
||||
binary = yield undefined;
|
||||
wasmData[2].value[2] = getWasmModule(binary);
|
||||
|
||||
getWasmBinary(`(module (func (export "run") (result i32) (i32.const 7)))`);
|
||||
getWasmBinary("(module (func (result i32) (i32.const 7)))");
|
||||
binary = yield undefined;
|
||||
wasmData[2].value[3] = getWasmModule(binary);
|
||||
|
||||
getWasmBinary(`(module (func (export "run") (result i32) (i32.const 8)))`);
|
||||
getWasmBinary("(module (func (result i32) (i32.const 8)))");
|
||||
binary = yield undefined;
|
||||
wasmData[2].value[4] = getWasmModule(binary);
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ function* testSteps()
|
|||
return;
|
||||
}
|
||||
|
||||
getWasmBinary(`(module (func (export "run") (result i32) (i32.const 13)))`);
|
||||
getWasmBinary("(module (func (nop)))");
|
||||
let binary = yield undefined;
|
||||
wasmData.value = getWasmModule(binary);
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ function* testSteps()
|
|||
yield undefined;
|
||||
}
|
||||
|
||||
getWasmBinary('(module (func $f (result i32) (i32.const 42)) (func (export "run") (result i32) (call $f)))');
|
||||
getWasmBinary("(module (func (nop)))");
|
||||
let binary = yield undefined;
|
||||
|
||||
wasmData.wasm = getWasmModule(binary);
|
||||
|
@ -31,7 +31,7 @@ function* testSteps()
|
|||
clearAllDatabases(continueToNextStepSync);
|
||||
yield undefined;
|
||||
|
||||
// The profile was created with a mythical build (buildId: 20180309213541,
|
||||
// The profile was created by an older build (buildId: 20161116145318,
|
||||
// cpuId: X64=0x2). It contains one stored wasm module (file id 1 - bytecode
|
||||
// and file id 2 - compiled/machine code). The file create_db.js in the
|
||||
// package was run locally (specifically it was temporarily added to
|
||||
|
|
Двоичные данные
dom/indexedDB/test/unit/wasm_recompile_profile.zip
Двоичные данные
dom/indexedDB/test/unit/wasm_recompile_profile.zip
Двоичный файл не отображается.
|
@ -491,12 +491,14 @@ function verifyView(view1, view2)
|
|||
|
||||
function verifyWasmModule(module1, module2)
|
||||
{
|
||||
// We assume the given modules have no imports and export a single function
|
||||
// named 'run'.
|
||||
var instance1 = new WebAssembly.Instance(module1);
|
||||
var instance2 = new WebAssembly.Instance(module2);
|
||||
is(instance1.exports.run(), instance2.exports.run(), "same run() result");
|
||||
|
||||
let testingFunctions = Cu.getJSTestingFunctions();
|
||||
let exp1 = testingFunctions.wasmExtractCode(module1, "ion");
|
||||
let exp2 = testingFunctions.wasmExtractCode(module2, "ion");
|
||||
let code1 = exp1.code;
|
||||
let code2 = exp2.code;
|
||||
ok(code1 instanceof Uint8Array, "Instance of Uint8Array");
|
||||
ok(code1.length == code2.length, "Correct length");
|
||||
verifyBuffers(code1, code2);
|
||||
continueToNextStep();
|
||||
}
|
||||
|
||||
|
|
|
@ -516,8 +516,7 @@ struct RuntimeSizes
|
|||
macro(_, MallocHeap, sharedIntlData) \
|
||||
macro(_, MallocHeap, uncompressedSourceCache) \
|
||||
macro(_, MallocHeap, scriptData) \
|
||||
macro(_, MallocHeap, tracelogger) \
|
||||
macro(_, MallocHeap, wasmRuntime)
|
||||
macro(_, MallocHeap, tracelogger)
|
||||
|
||||
RuntimeSizes()
|
||||
: FOR_EACH_SIZE(ZERO_SIZE)
|
||||
|
|
|
@ -1,185 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "jit/AsyncInterrupt.h"
|
||||
|
||||
#include "jit/JitCompartment.h"
|
||||
#include "util/Windows.h"
|
||||
|
||||
#if defined(ANDROID)
|
||||
# include <sys/system_properties.h>
|
||||
#endif
|
||||
|
||||
using namespace js;
|
||||
using namespace js::jit;
|
||||
|
||||
using mozilla::PodArrayZero;
|
||||
|
||||
static void
|
||||
RedirectIonBackedgesToInterruptCheck(JSContext* cx)
|
||||
{
|
||||
// Jitcode may only be modified on the runtime's active thread.
|
||||
if (cx != cx->runtime()->activeContext())
|
||||
return;
|
||||
|
||||
// The faulting thread is suspended so we can access cx fields that can
|
||||
// normally only be accessed by the cx's active thread.
|
||||
AutoNoteSingleThreadedRegion anstr;
|
||||
|
||||
Zone* zone = cx->zoneRaw();
|
||||
if (zone && !zone->isAtomsZone()) {
|
||||
jit::JitRuntime* jitRuntime = cx->runtime()->jitRuntime();
|
||||
if (!jitRuntime)
|
||||
return;
|
||||
|
||||
// If the backedge list is being mutated, the pc must be in C++ code and
|
||||
// thus not in a JIT iloop. We assume that the interrupt flag will be
|
||||
// checked at least once before entering JIT code (if not, no big deal;
|
||||
// the browser will just request another interrupt in a second).
|
||||
if (!jitRuntime->preventBackedgePatching()) {
|
||||
jit::JitZoneGroup* jzg = zone->group()->jitZoneGroup;
|
||||
jzg->patchIonBackedges(cx, jit::JitZoneGroup::BackedgeInterruptCheck);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(XP_WIN)
|
||||
// For the interrupt signal, pick a signal number that:
|
||||
// - is not otherwise used by mozilla or standard libraries
|
||||
// - defaults to nostop and noprint on gdb/lldb so that noone is bothered
|
||||
// SIGVTALRM a relative of SIGALRM, so intended for user code, but, unlike
|
||||
// SIGALRM, not used anywhere else in Mozilla.
|
||||
static const int sJitAsyncInterruptSignal = SIGVTALRM;
|
||||
|
||||
static void
|
||||
JitAsyncInterruptHandler(int signum, siginfo_t*, void*)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(signum == sJitAsyncInterruptSignal);
|
||||
|
||||
JSContext* cx = TlsContext.get();
|
||||
if (!cx)
|
||||
return;
|
||||
|
||||
#if defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS32) || defined(JS_SIMULATOR_MIPS64)
|
||||
SimulatorProcess::ICacheCheckingDisableCount++;
|
||||
#endif
|
||||
|
||||
RedirectIonBackedgesToInterruptCheck(cx);
|
||||
|
||||
#if defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS32) || defined(JS_SIMULATOR_MIPS64)
|
||||
SimulatorProcess::cacheInvalidatedBySignalHandler_ = true;
|
||||
SimulatorProcess::ICacheCheckingDisableCount--;
|
||||
#endif
|
||||
|
||||
cx->finishHandlingJitInterrupt();
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool sTriedInstallAsyncInterrupt = false;
|
||||
static bool sHaveAsyncInterrupt = false;
|
||||
|
||||
void
|
||||
jit::EnsureAsyncInterrupt(JSContext* cx)
|
||||
{
|
||||
// We assume that there are no races creating the first JSRuntime of the process.
|
||||
if (sTriedInstallAsyncInterrupt)
|
||||
return;
|
||||
sTriedInstallAsyncInterrupt = true;
|
||||
|
||||
#if defined(ANDROID) && !defined(__aarch64__)
|
||||
// Before Android 4.4 (SDK version 19), there is a bug
|
||||
// https://android-review.googlesource.com/#/c/52333
|
||||
// in Bionic's pthread_join which causes pthread_join to return early when
|
||||
// pthread_kill is used (on any thread). Nobody expects the pthread_cond_wait
|
||||
// EINTRquisition.
|
||||
char version_string[PROP_VALUE_MAX];
|
||||
PodArrayZero(version_string);
|
||||
if (__system_property_get("ro.build.version.sdk", version_string) > 0) {
|
||||
if (atol(version_string) < 19)
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(XP_WIN)
|
||||
// Windows uses SuspendThread to stop the active thread from another thread.
|
||||
#else
|
||||
struct sigaction interruptHandler;
|
||||
interruptHandler.sa_flags = SA_SIGINFO;
|
||||
interruptHandler.sa_sigaction = &JitAsyncInterruptHandler;
|
||||
sigemptyset(&interruptHandler.sa_mask);
|
||||
struct sigaction prev;
|
||||
if (sigaction(sJitAsyncInterruptSignal, &interruptHandler, &prev))
|
||||
MOZ_CRASH("unable to install interrupt handler");
|
||||
|
||||
// There shouldn't be any other handlers installed for
|
||||
// sJitAsyncInterruptSignal. If there are, we could always forward, but we
|
||||
// need to understand what we're doing to avoid problematic interference.
|
||||
if ((prev.sa_flags & SA_SIGINFO && prev.sa_sigaction) ||
|
||||
(prev.sa_handler != SIG_DFL && prev.sa_handler != SIG_IGN))
|
||||
{
|
||||
MOZ_CRASH("contention for interrupt signal");
|
||||
}
|
||||
#endif // defined(XP_WIN)
|
||||
|
||||
sHaveAsyncInterrupt = true;
|
||||
}
|
||||
|
||||
bool
|
||||
jit::HaveAsyncInterrupt()
|
||||
{
|
||||
MOZ_ASSERT(sTriedInstallAsyncInterrupt);
|
||||
return sHaveAsyncInterrupt;
|
||||
}
|
||||
|
||||
// JSRuntime::requestInterrupt sets interrupt_ (which is checked frequently by
|
||||
// C++ code at every Baseline JIT loop backedge) and jitStackLimit_ (which is
|
||||
// checked at every Baseline and Ion JIT function prologue). The remaining
|
||||
// sources of potential iloops (Ion loop backedges) are handled by this
|
||||
// function: Ion loop backedges are patched to instead point to a stub that
|
||||
// handles the interrupt;
|
||||
void
|
||||
jit::InterruptRunningCode(JSContext* cx)
|
||||
{
|
||||
// If signal handlers weren't installed, then Ion emit normal interrupt
|
||||
// checks and don't need asynchronous interruption.
|
||||
MOZ_ASSERT(sTriedInstallAsyncInterrupt);
|
||||
if (!sHaveAsyncInterrupt)
|
||||
return;
|
||||
|
||||
// Do nothing if we're already handling an interrupt here, to avoid races
|
||||
// below and in JitRuntime::patchIonBackedges.
|
||||
if (!cx->startHandlingJitInterrupt())
|
||||
return;
|
||||
|
||||
// If we are on context's thread, then we can patch Ion backedges without
|
||||
// any special synchronization.
|
||||
if (cx == TlsContext.get()) {
|
||||
RedirectIonBackedgesToInterruptCheck(cx);
|
||||
cx->finishHandlingJitInterrupt();
|
||||
return;
|
||||
}
|
||||
|
||||
// We are not on the runtime's active thread, so we need to halt the
|
||||
// runtime's active thread first.
|
||||
#if defined(XP_WIN)
|
||||
// On Windows, we can simply suspend the active thread. SuspendThread can
|
||||
// sporadically fail if the thread is in the middle of a syscall. Rather
|
||||
// than retrying in a loop, just wait for the next request for interrupt.
|
||||
HANDLE thread = (HANDLE)cx->threadNative();
|
||||
if (SuspendThread(thread) != (DWORD)-1) {
|
||||
RedirectIonBackedgesToInterruptCheck(cx);
|
||||
ResumeThread(thread);
|
||||
}
|
||||
cx->finishHandlingJitInterrupt();
|
||||
#else
|
||||
// On Unix, we instead deliver an async signal to the active thread which
|
||||
// halts the thread and callers our JitAsyncInterruptHandler (which has
|
||||
// already been installed by EnsureSignalHandlersInstalled).
|
||||
pthread_t thread = (pthread_t)cx->threadNative();
|
||||
pthread_kill(thread, sJitAsyncInterruptSignal);
|
||||
#endif
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef jit_AsyncInterrupt_h
|
||||
#define jit_AsyncInterrupt_h
|
||||
|
||||
#include "NamespaceImports.h"
|
||||
|
||||
namespace js {
|
||||
namespace jit {
|
||||
|
||||
// Ensure the given JSRuntime is set up to use async interrupts. Failure to
|
||||
// enable signal handlers indicates some catastrophic failure and creation of
|
||||
// the runtime must fail.
|
||||
void
|
||||
EnsureAsyncInterrupt(JSContext* cx);
|
||||
|
||||
// Return whether the async interrupt can be used to interrupt Ion code.
|
||||
bool
|
||||
HaveAsyncInterrupt();
|
||||
|
||||
// Force any currently-executing JIT code to call HandleExecutionInterrupt.
|
||||
extern void
|
||||
InterruptRunningCode(JSContext* cx);
|
||||
|
||||
} // namespace jit
|
||||
} // namespace js
|
||||
|
||||
#endif // jit_AsyncInterrupt_h
|
|
@ -12717,14 +12717,6 @@ CodeGenerator::visitInterruptCheck(LInterruptCheck* lir)
|
|||
masm.bind(ool->rejoin());
|
||||
}
|
||||
|
||||
void
|
||||
CodeGenerator::visitWasmInterruptCheck(LWasmInterruptCheck* lir)
|
||||
{
|
||||
MOZ_ASSERT(gen->compilingWasm());
|
||||
|
||||
masm.wasmInterruptCheck(ToRegister(lir->tlsPtr()), lir->mir()->bytecodeOffset());
|
||||
}
|
||||
|
||||
void
|
||||
CodeGenerator::visitWasmTrap(LWasmTrap* lir)
|
||||
{
|
||||
|
|
|
@ -464,7 +464,6 @@ class CodeGenerator final : public CodeGeneratorSpecific
|
|||
|
||||
void visitInterruptCheck(LInterruptCheck* lir);
|
||||
void visitOutOfLineInterruptCheckImplicit(OutOfLineInterruptCheckImplicit* ins);
|
||||
void visitWasmInterruptCheck(LWasmInterruptCheck* lir);
|
||||
void visitWasmTrap(LWasmTrap* lir);
|
||||
void visitWasmLoadTls(LWasmLoadTls* ins);
|
||||
void visitWasmBoundsCheck(LWasmBoundsCheck* ins);
|
||||
|
|
|
@ -392,7 +392,7 @@ JitZoneGroup::patchIonBackedges(JSContext* cx, BackedgeTarget target)
|
|||
MOZ_ASSERT(cx->runtime()->jitRuntime()->preventBackedgePatching());
|
||||
MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
|
||||
} else {
|
||||
// We must be called from jit::InterruptRunningCode, or a signal handler
|
||||
// We must be called from InterruptRunningJitCode, or a signal handler
|
||||
// triggered there. rt->handlingJitInterrupt() ensures we can't reenter
|
||||
// this code.
|
||||
MOZ_ASSERT(!cx->runtime()->jitRuntime()->preventBackedgePatching());
|
||||
|
|
|
@ -187,6 +187,10 @@ DefaultJitOptions::DefaultJitOptions()
|
|||
// pc-relative jump and call instructions.
|
||||
SET_DEFAULT(jumpThreshold, UINT32_MAX);
|
||||
|
||||
// Whether the (ARM) simulators should always interrupt before executing any
|
||||
// instruction.
|
||||
SET_DEFAULT(simulatorAlwaysInterrupt, false);
|
||||
|
||||
// Branch pruning heuristic is based on a scoring system, which is look at
|
||||
// different metrics and provide a score. The score is computed as a
|
||||
// projection where each factor defines the weight of each metric. Then this
|
||||
|
|
|
@ -76,6 +76,7 @@ struct DefaultJitOptions
|
|||
bool wasmFoldOffsets;
|
||||
bool wasmDelayTier2;
|
||||
bool ionInterruptWithoutSignals;
|
||||
bool simulatorAlwaysInterrupt;
|
||||
uint32_t baselineWarmUpThreshold;
|
||||
uint32_t exceptionBailoutThreshold;
|
||||
uint32_t frequentBailoutThreshold;
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/EndianUtils.h"
|
||||
|
||||
#include "jit/AsyncInterrupt.h"
|
||||
#include "jit/JitSpewer.h"
|
||||
#include "jit/LIR.h"
|
||||
#include "jit/MIR.h"
|
||||
#include "jit/MIRGraph.h"
|
||||
#include "wasm/WasmSignalHandlers.h"
|
||||
|
||||
#include "jit/shared/Lowering-shared-inl.h"
|
||||
#include "vm/BytecodeUtil-inl.h"
|
||||
|
@ -93,8 +93,8 @@ LIRGenerator::visitIsConstructing(MIsConstructing* ins)
|
|||
static void
|
||||
TryToUseImplicitInterruptCheck(MIRGraph& graph, MBasicBlock* backedge)
|
||||
{
|
||||
// Implicit interrupt checks require JIT async interrupt support.
|
||||
if (!jit::HaveAsyncInterrupt() || JitOptions.ionInterruptWithoutSignals)
|
||||
// Implicit interrupt checks require wasm signal handlers to be installed.
|
||||
if (!wasm::HaveSignalHandlers() || JitOptions.ionInterruptWithoutSignals)
|
||||
return;
|
||||
|
||||
// To avoid triggering expensive interrupts (backedge patching) in
|
||||
|
@ -2737,13 +2737,6 @@ LIRGenerator::visitInterruptCheck(MInterruptCheck* ins)
|
|||
assignSafepoint(lir, ins);
|
||||
}
|
||||
|
||||
void
|
||||
LIRGenerator::visitWasmInterruptCheck(MWasmInterruptCheck* ins)
|
||||
{
|
||||
auto* lir = new(alloc()) LWasmInterruptCheck(useRegisterAtStart(ins->tlsPtr()));
|
||||
add(lir, ins);
|
||||
}
|
||||
|
||||
void
|
||||
LIRGenerator::visitWasmTrap(MWasmTrap* ins)
|
||||
{
|
||||
|
|
|
@ -211,7 +211,6 @@ class LIRGenerator : public LIRGeneratorSpecific
|
|||
void visitHomeObject(MHomeObject* ins) override;
|
||||
void visitHomeObjectSuperBase(MHomeObjectSuperBase* ins) override;
|
||||
void visitInterruptCheck(MInterruptCheck* ins) override;
|
||||
void visitWasmInterruptCheck(MWasmInterruptCheck* ins) override;
|
||||
void visitWasmTrap(MWasmTrap* ins) override;
|
||||
void visitWasmReinterpret(MWasmReinterpret* ins) override;
|
||||
void visitStoreSlot(MStoreSlot* ins) override;
|
||||
|
|
|
@ -8303,33 +8303,6 @@ class MInterruptCheck : public MNullaryInstruction
|
|||
}
|
||||
};
|
||||
|
||||
// Check whether we need to fire the interrupt handler (in wasm code).
|
||||
class MWasmInterruptCheck
|
||||
: public MUnaryInstruction,
|
||||
public NoTypePolicy::Data
|
||||
{
|
||||
wasm::BytecodeOffset bytecodeOffset_;
|
||||
|
||||
MWasmInterruptCheck(MDefinition* tlsPointer, wasm::BytecodeOffset bytecodeOffset)
|
||||
: MUnaryInstruction(classOpcode, tlsPointer),
|
||||
bytecodeOffset_(bytecodeOffset)
|
||||
{
|
||||
setGuard();
|
||||
}
|
||||
|
||||
public:
|
||||
INSTRUCTION_HEADER(WasmInterruptCheck)
|
||||
TRIVIAL_NEW_WRAPPERS
|
||||
NAMED_OPERANDS((0, tlsPtr))
|
||||
|
||||
AliasSet getAliasSet() const override {
|
||||
return AliasSet::None();
|
||||
}
|
||||
wasm::BytecodeOffset bytecodeOffset() const {
|
||||
return bytecodeOffset_;
|
||||
}
|
||||
};
|
||||
|
||||
// Directly jumps to the indicated trap, leaving Wasm code and reporting a
|
||||
// runtime error.
|
||||
|
||||
|
|
|
@ -276,7 +276,6 @@ namespace jit {
|
|||
_(InstanceOf) \
|
||||
_(InstanceOfCache) \
|
||||
_(InterruptCheck) \
|
||||
_(WasmInterruptCheck) \
|
||||
_(GetDOMProperty) \
|
||||
_(GetDOMMember) \
|
||||
_(SetDOMProperty) \
|
||||
|
|
|
@ -3376,19 +3376,7 @@ MacroAssembler::maybeBranchTestType(MIRType type, MDefinition* maybeDef, Registe
|
|||
void
|
||||
MacroAssembler::wasmTrap(wasm::Trap trap, wasm::BytecodeOffset bytecodeOffset)
|
||||
{
|
||||
uint32_t trapOffset = wasmTrapInstruction().offset();
|
||||
MOZ_ASSERT_IF(!oom(), currentOffset() - trapOffset == WasmTrapInstructionLength);
|
||||
|
||||
append(trap, wasm::TrapSite(trapOffset, bytecodeOffset));
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::wasmInterruptCheck(Register tls, wasm::BytecodeOffset bytecodeOffset)
|
||||
{
|
||||
Label ok;
|
||||
branch32(Assembler::Equal, Address(tls, offsetof(wasm::TlsData, interrupt)), Imm32(0), &ok);
|
||||
wasmTrap(wasm::Trap::CheckInterrupt, bytecodeOffset);
|
||||
bind(&ok);
|
||||
append(trap, wasm::TrapSite(wasmTrapInstruction().offset(), bytecodeOffset));
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -1531,7 +1531,6 @@ class MacroAssembler : public MacroAssemblerSpecific
|
|||
CodeOffset wasmTrapInstruction() PER_SHARED_ARCH;
|
||||
|
||||
void wasmTrap(wasm::Trap trap, wasm::BytecodeOffset bytecodeOffset);
|
||||
void wasmInterruptCheck(Register tls, wasm::BytecodeOffset bytecodeOffset);
|
||||
|
||||
// Emit a bounds check against the wasm heap limit, jumping to 'label' if
|
||||
// 'cond' holds. Required when WASM_HUGE_MEMORY is not defined. If
|
||||
|
|
|
@ -128,7 +128,6 @@ static constexpr FloatRegister ABINonArgDoubleReg { FloatRegisters::d8, VFPRegis
|
|||
// Note: these three registers are all guaranteed to be different
|
||||
static constexpr Register ABINonArgReturnReg0 = r4;
|
||||
static constexpr Register ABINonArgReturnReg1 = r5;
|
||||
static constexpr Register ABINonVolatileReg = r6;
|
||||
|
||||
// This register is guaranteed to be clobberable during the prologue and
|
||||
// epilogue of an ABI call which must preserve both ABI argument, return
|
||||
|
@ -250,7 +249,6 @@ static_assert(JitStackAlignment % SimdMemoryAlignment == 0,
|
|||
"spilled values. Thus it should be larger than the alignment for SIMD accesses.");
|
||||
|
||||
static const uint32_t WasmStackAlignment = SimdMemoryAlignment;
|
||||
static const uint32_t WasmTrapInstructionLength = 4;
|
||||
|
||||
// Does this architecture support SIMD conversions between Uint32x4 and Float32x4?
|
||||
static constexpr bool SupportsUint32x4FloatConversions = false;
|
||||
|
|
|
@ -1160,6 +1160,7 @@ Simulator::Simulator(JSContext* cx)
|
|||
stackLimit_ = 0;
|
||||
pc_modified_ = false;
|
||||
icount_ = 0L;
|
||||
wasm_interrupt_ = false;
|
||||
break_pc_ = nullptr;
|
||||
break_instr_ = 0;
|
||||
single_stepping_ = false;
|
||||
|
@ -1593,6 +1594,29 @@ Simulator::registerState()
|
|||
return state;
|
||||
}
|
||||
|
||||
// The signal handler only redirects the PC to the interrupt stub when the PC is
|
||||
// in function code. However, this guard is racy for the ARM simulator since the
|
||||
// signal handler samples PC in the middle of simulating an instruction and thus
|
||||
// the current PC may have advanced once since the signal handler's guard. So we
|
||||
// re-check here.
|
||||
void
|
||||
Simulator::handleWasmInterrupt()
|
||||
{
|
||||
if (!wasm::CodeExists)
|
||||
return;
|
||||
|
||||
uint8_t* pc = (uint8_t*)get_pc();
|
||||
|
||||
const wasm::ModuleSegment* ms = nullptr;
|
||||
if (!wasm::InInterruptibleCode(cx_, pc, &ms))
|
||||
return;
|
||||
|
||||
if (!cx_->activation()->asJit()->startWasmInterrupt(registerState()))
|
||||
return;
|
||||
|
||||
set_pc(int32_t(ms->interruptCode()));
|
||||
}
|
||||
|
||||
static inline JitActivation*
|
||||
GetJitActivation(JSContext* cx)
|
||||
{
|
||||
|
@ -1635,7 +1659,7 @@ Simulator::handleWasmSegFault(int32_t addr, unsigned numBytes)
|
|||
|
||||
const wasm::MemoryAccess* memoryAccess = instance->code().lookupMemoryAccess(pc);
|
||||
if (!memoryAccess) {
|
||||
act->asJit()->startWasmTrap(wasm::Trap::OutOfBounds, 0, registerState());
|
||||
MOZ_ALWAYS_TRUE(act->asJit()->startWasmInterrupt(registerState()));
|
||||
if (!instance->code().containsCodePC(pc))
|
||||
MOZ_CRASH("Cannot map PC to trap handler");
|
||||
set_pc(int32_t(moduleSegment->outOfBoundsCode()));
|
||||
|
@ -4901,6 +4925,19 @@ Simulator::disable_single_stepping()
|
|||
single_step_callback_arg_ = nullptr;
|
||||
}
|
||||
|
||||
static void
|
||||
FakeInterruptHandler()
|
||||
{
|
||||
JSContext* cx = TlsContext.get();
|
||||
uint8_t* pc = cx->simulator()->get_pc_as<uint8_t*>();
|
||||
|
||||
const wasm::ModuleSegment* ms= nullptr;
|
||||
if (!wasm::InInterruptibleCode(cx, pc, &ms))
|
||||
return;
|
||||
|
||||
cx->simulator()->trigger_wasm_interrupt();
|
||||
}
|
||||
|
||||
template<bool EnableStopSimAt>
|
||||
void
|
||||
Simulator::execute()
|
||||
|
@ -4920,9 +4957,16 @@ Simulator::execute()
|
|||
} else {
|
||||
if (single_stepping_)
|
||||
single_step_callback_(single_step_callback_arg_, this, (void*)program_counter);
|
||||
if (MOZ_UNLIKELY(JitOptions.simulatorAlwaysInterrupt))
|
||||
FakeInterruptHandler();
|
||||
SimInstruction* instr = reinterpret_cast<SimInstruction*>(program_counter);
|
||||
instructionDecode(instr);
|
||||
icount_++;
|
||||
|
||||
if (MOZ_UNLIKELY(wasm_interrupt_)) {
|
||||
handleWasmInterrupt();
|
||||
wasm_interrupt_ = false;
|
||||
}
|
||||
}
|
||||
program_counter = get_pc();
|
||||
}
|
||||
|
|
|
@ -197,6 +197,13 @@ class Simulator
|
|||
template <typename T>
|
||||
T get_pc_as() const { return reinterpret_cast<T>(get_pc()); }
|
||||
|
||||
void trigger_wasm_interrupt() {
|
||||
// This can be called several times if a single interrupt isn't caught
|
||||
// and handled by the simulator, but this is fine; once the current
|
||||
// instruction is done executing, the interrupt will be handled anyhow.
|
||||
wasm_interrupt_ = true;
|
||||
}
|
||||
|
||||
void enable_single_stepping(SingleStepCallback cb, void* arg);
|
||||
void disable_single_stepping();
|
||||
|
||||
|
@ -286,6 +293,7 @@ class Simulator
|
|||
void printStopInfo(uint32_t code);
|
||||
|
||||
// Handle a wasm interrupt triggered by an async signal handler.
|
||||
void handleWasmInterrupt();
|
||||
JS::ProfilingFrameIterator::RegisterState registerState();
|
||||
|
||||
// Handle any wasm faults, returning true if the fault was handled.
|
||||
|
@ -418,6 +426,9 @@ class Simulator
|
|||
bool pc_modified_;
|
||||
int64_t icount_;
|
||||
|
||||
// wasm async interrupt / fault support
|
||||
bool wasm_interrupt_;
|
||||
|
||||
// Debugger input.
|
||||
char* lastDebuggerInput_;
|
||||
|
||||
|
|
|
@ -172,7 +172,6 @@ static_assert(CodeAlignment % SimdMemoryAlignment == 0,
|
|||
"alignment for SIMD constants.");
|
||||
|
||||
static const uint32_t WasmStackAlignment = SimdMemoryAlignment;
|
||||
static const uint32_t WasmTrapInstructionLength = 4;
|
||||
|
||||
// Does this architecture support SIMD conversions between Uint32x4 and Float32x4?
|
||||
static constexpr bool SupportsUint32x4FloatConversions = false;
|
||||
|
@ -458,7 +457,6 @@ static constexpr FloatRegister ABINonArgDoubleReg = { FloatRegisters::s16, Float
|
|||
// Note: these three registers are all guaranteed to be different
|
||||
static constexpr Register ABINonArgReturnReg0 = r8;
|
||||
static constexpr Register ABINonArgReturnReg1 = r9;
|
||||
static constexpr Register ABINonVolatileReg { Registers::x19 };
|
||||
|
||||
// This register is guaranteed to be clobberable during the prologue and
|
||||
// epilogue of an ABI call which must preserve both ABI argument, return
|
||||
|
|
|
@ -86,6 +86,7 @@ void Simulator::ResetState() {
|
|||
// Reset registers to 0.
|
||||
pc_ = nullptr;
|
||||
pc_modified_ = false;
|
||||
wasm_interrupt_ = false;
|
||||
for (unsigned i = 0; i < kNumberOfRegisters; i++) {
|
||||
set_xreg(i, 0xbadbeef);
|
||||
}
|
||||
|
@ -194,6 +195,15 @@ void Simulator::ExecuteInstruction() {
|
|||
VIXL_ASSERT(IsWordAligned(pc_));
|
||||
decoder_->Decode(pc_);
|
||||
increment_pc();
|
||||
|
||||
if (MOZ_UNLIKELY(wasm_interrupt_)) {
|
||||
handle_wasm_interrupt();
|
||||
// Just calling set_pc turns the pc_modified_ flag on, which means it doesn't
|
||||
// auto-step after executing the next instruction. Force that to off so it
|
||||
// will auto-step after executing the first instruction of the handler.
|
||||
pc_modified_ = false;
|
||||
wasm_interrupt_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -220,6 +230,12 @@ bool Simulator::overRecursedWithExtra(uint32_t extra) const {
|
|||
}
|
||||
|
||||
|
||||
void Simulator::trigger_wasm_interrupt() {
|
||||
MOZ_ASSERT(!wasm_interrupt_);
|
||||
wasm_interrupt_ = true;
|
||||
}
|
||||
|
||||
|
||||
static inline JitActivation*
|
||||
GetJitActivation(JSContext* cx)
|
||||
{
|
||||
|
@ -241,6 +257,32 @@ Simulator::registerState()
|
|||
return state;
|
||||
}
|
||||
|
||||
// The signal handler only redirects the PC to the interrupt stub when the PC is
|
||||
// in function code. However, this guard is racy for the ARM simulator since the
|
||||
// signal handler samples PC in the middle of simulating an instruction and thus
|
||||
// the current PC may have advanced once since the signal handler's guard. So we
|
||||
// re-check here.
|
||||
void Simulator::handle_wasm_interrupt()
|
||||
{
|
||||
if (!js::wasm::CodeExists)
|
||||
return;
|
||||
|
||||
uint8_t* pc = (uint8_t*)get_pc();
|
||||
|
||||
const js::wasm::ModuleSegment* ms = nullptr;
|
||||
if (!js::wasm::InInterruptibleCode(cx_, pc, &ms))
|
||||
return;
|
||||
|
||||
JitActivation* act = GetJitActivation(cx_);
|
||||
if (!act)
|
||||
return;
|
||||
|
||||
if (!act->startWasmInterrupt(registerState()))
|
||||
return;
|
||||
|
||||
set_pc((Instruction*)ms->interruptCode());
|
||||
}
|
||||
|
||||
bool
|
||||
Simulator::handle_wasm_seg_fault(uintptr_t addr, unsigned numBytes)
|
||||
{
|
||||
|
@ -267,7 +309,8 @@ Simulator::handle_wasm_seg_fault(uintptr_t addr, unsigned numBytes)
|
|||
|
||||
const js::wasm::MemoryAccess* memoryAccess = instance->code().lookupMemoryAccess(pc);
|
||||
if (!memoryAccess) {
|
||||
act->startWasmTrap(js::wasm::Trap::OutOfBounds, 0, registerState());
|
||||
if (!act->startWasmInterrupt(registerState()))
|
||||
MOZ_CRASH("Cannot start interrupt");
|
||||
if (!instance->code().containsCodePC(pc))
|
||||
MOZ_CRASH("Cannot map PC to trap handler");
|
||||
set_pc((Instruction*)moduleSegment->outOfBoundsCode());
|
||||
|
|
|
@ -746,6 +746,8 @@ class Simulator : public DecoderVisitor {
|
|||
pc_modified_ = true;
|
||||
}
|
||||
|
||||
void trigger_wasm_interrupt();
|
||||
void handle_wasm_interrupt();
|
||||
bool handle_wasm_ill_fault();
|
||||
bool handle_wasm_seg_fault(uintptr_t addr, unsigned numBytes);
|
||||
|
||||
|
@ -2581,6 +2583,7 @@ class Simulator : public DecoderVisitor {
|
|||
// automatically incremented.
|
||||
bool pc_modified_;
|
||||
const Instruction* pc_;
|
||||
bool wasm_interrupt_;
|
||||
|
||||
static const char* xreg_names[];
|
||||
static const char* wreg_names[];
|
||||
|
|
|
@ -135,7 +135,6 @@ static_assert(JitStackAlignment % sizeof(Value) == 0 && JitStackValueAlignment >
|
|||
// TODO Copy the static_asserts from x64/x86 assembler files.
|
||||
static constexpr uint32_t SimdMemoryAlignment = 8;
|
||||
static constexpr uint32_t WasmStackAlignment = SimdMemoryAlignment;
|
||||
static const uint32_t WasmTrapInstructionLength = 4;
|
||||
|
||||
// Does this architecture support SIMD conversions between Uint32x4 and Float32x4?
|
||||
static constexpr bool SupportsUint32x4FloatConversions = false;
|
||||
|
|
|
@ -1269,6 +1269,7 @@ Simulator::Simulator()
|
|||
pc_modified_ = false;
|
||||
icount_ = 0;
|
||||
break_count_ = 0;
|
||||
wasm_interrupt_ = false;
|
||||
break_pc_ = nullptr;
|
||||
break_instr_ = 0;
|
||||
|
||||
|
@ -1641,6 +1642,32 @@ Simulator::registerState()
|
|||
return state;
|
||||
}
|
||||
|
||||
// The signal handler only redirects the PC to the interrupt stub when the PC is
|
||||
// in function code. However, this guard is racy for the simulator since the
|
||||
// signal handler samples PC in the middle of simulating an instruction and thus
|
||||
// the current PC may have advanced once since the signal handler's guard. So we
|
||||
// re-check here.
|
||||
void
|
||||
Simulator::handleWasmInterrupt()
|
||||
{
|
||||
if (!wasm::CodeExists)
|
||||
return;
|
||||
|
||||
void* pc = (void*)get_pc();
|
||||
void* fp = (void*)getRegister(Register::fp);
|
||||
|
||||
JitActivation* activation = TlsContext.get()->activation()->asJit();
|
||||
const wasm::CodeSegment* segment = wasm::LookupCodeSegment(pc);
|
||||
if (!segment || !segment->isModule() || !segment->containsCodePC(pc))
|
||||
return;
|
||||
|
||||
if (!activation->startWasmInterrupt(registerState()))
|
||||
return;
|
||||
|
||||
set_pc(int32_t(segment->asModule()->interruptCode()));
|
||||
}
|
||||
|
||||
|
||||
// WebAssembly memories contain an extra region of guard pages (see
|
||||
// WasmArrayRawBuffer comment). The guard pages catch out-of-bounds accesses
|
||||
// using a signal handler that redirects PC to a stub that safely reports an
|
||||
|
@ -1679,7 +1706,7 @@ Simulator::handleWasmFault(int32_t addr, unsigned numBytes)
|
|||
|
||||
const wasm::MemoryAccess* memoryAccess = instance->code().lookupMemoryAccess(pc);
|
||||
if (!memoryAccess) {
|
||||
act->startWasmTrap(wasm::Trap::OutOfBounds, 0, registerState());
|
||||
MOZ_ALWAYS_TRUE(act->startWasmInterrupt(registerState()));
|
||||
if (!instance->code().containsCodePC(pc))
|
||||
MOZ_CRASH("Cannot map PC to trap handler");
|
||||
set_pc(int32_t(moduleSegment->outOfBoundsCode()));
|
||||
|
@ -3646,6 +3673,19 @@ Simulator::branchDelayInstructionDecode(SimInstruction* instr)
|
|||
instructionDecode(instr);
|
||||
}
|
||||
|
||||
static void
|
||||
FakeInterruptHandler()
|
||||
{
|
||||
JSContext* cx = TlsContext.get();
|
||||
uint8_t* pc = cx->simulator()->get_pc_as<uint8_t*>();
|
||||
|
||||
const wasm::ModuleSegment* ms = nullptr;
|
||||
if (!wasm::InInterruptibleCode(cx, pc, &ms))
|
||||
return;
|
||||
|
||||
cx->simulator()->trigger_wasm_interrupt();
|
||||
}
|
||||
|
||||
template<bool enableStopSimAt>
|
||||
void
|
||||
Simulator::execute()
|
||||
|
@ -3659,9 +3699,16 @@ Simulator::execute()
|
|||
MipsDebugger dbg(this);
|
||||
dbg.debug();
|
||||
} else {
|
||||
if (MOZ_UNLIKELY(JitOptions.simulatorAlwaysInterrupt))
|
||||
FakeInterruptHandler();
|
||||
SimInstruction* instr = reinterpret_cast<SimInstruction*>(program_counter);
|
||||
instructionDecode(instr);
|
||||
icount_++;
|
||||
|
||||
if (MOZ_UNLIKELY(wasm_interrupt_)) {
|
||||
handleWasmInterrupt();
|
||||
wasm_interrupt_ = false;
|
||||
}
|
||||
}
|
||||
program_counter = get_pc();
|
||||
}
|
||||
|
|
|
@ -202,6 +202,13 @@ class Simulator {
|
|||
template <typename T>
|
||||
T get_pc_as() const { return reinterpret_cast<T>(get_pc()); }
|
||||
|
||||
void trigger_wasm_interrupt() {
|
||||
// This can be called several times if a single interrupt isn't caught
|
||||
// and handled by the simulator, but this is fine; once the current
|
||||
// instruction is done executing, the interrupt will be handled anyhow.
|
||||
wasm_interrupt_ = true;
|
||||
}
|
||||
|
||||
// Accessor to the internal simulator stack area.
|
||||
uintptr_t stackLimit() const;
|
||||
bool overRecursed(uintptr_t newsp = 0) const;
|
||||
|
@ -297,6 +304,8 @@ class Simulator {
|
|||
void increaseStopCounter(uint32_t code);
|
||||
void printStopInfo(uint32_t code);
|
||||
|
||||
// Handle a wasm interrupt triggered by an async signal handler.
|
||||
void handleWasmInterrupt();
|
||||
JS::ProfilingFrameIterator::RegisterState registerState();
|
||||
|
||||
// Handle any wasm faults, returning true if the fault was handled.
|
||||
|
@ -356,6 +365,9 @@ class Simulator {
|
|||
int icount_;
|
||||
int break_count_;
|
||||
|
||||
// wasm async interrupt / fault support
|
||||
bool wasm_interrupt_;
|
||||
|
||||
// Debugger input.
|
||||
char* lastDebuggerInput_;
|
||||
|
||||
|
|
|
@ -146,7 +146,6 @@ static_assert(JitStackAlignment % sizeof(Value) == 0 && JitStackValueAlignment >
|
|||
static constexpr uint32_t SimdMemoryAlignment = 16;
|
||||
|
||||
static constexpr uint32_t WasmStackAlignment = SimdMemoryAlignment;
|
||||
static const uint32_t WasmTrapInstructionLength = 4;
|
||||
|
||||
// Does this architecture support SIMD conversions between Uint32x4 and Float32x4?
|
||||
static constexpr bool SupportsUint32x4FloatConversions = false;
|
||||
|
|
|
@ -1278,6 +1278,7 @@ Simulator::Simulator()
|
|||
pc_modified_ = false;
|
||||
icount_ = 0;
|
||||
break_count_ = 0;
|
||||
wasm_interrupt_ = false;
|
||||
break_pc_ = nullptr;
|
||||
break_instr_ = 0;
|
||||
single_stepping_ = false;
|
||||
|
@ -1644,6 +1645,35 @@ Simulator::registerState()
|
|||
return state;
|
||||
}
|
||||
|
||||
// The signal handler only redirects the PC to the interrupt stub when the PC is
|
||||
// in function code. However, this guard is racy for the simulator since the
|
||||
// signal handler samples PC in the middle of simulating an instruction and thus
|
||||
// the current PC may have advanced once since the signal handler's guard. So we
|
||||
// re-check here.
|
||||
void
|
||||
Simulator::handleWasmInterrupt()
|
||||
{
|
||||
if (!wasm::CodeExists)
|
||||
return;
|
||||
|
||||
void* pc = (void*)get_pc();
|
||||
void* fp = (void*)getRegister(Register::fp);
|
||||
|
||||
JitActivation* activation = TlsContext.get()->activation()->asJit();
|
||||
const wasm::CodeSegment* segment = wasm::LookupCodeSegment(pc);
|
||||
if (!segment || !segment->isModule() || !segment->containsCodePC(pc))
|
||||
return;
|
||||
|
||||
// fp can be null during the prologue/epilogue of the entry function.
|
||||
if (!fp)
|
||||
return;
|
||||
|
||||
if (!activation->startWasmInterrupt(registerState()))
|
||||
return;
|
||||
|
||||
set_pc(int64_t(segment->asModule()->interruptCode()));
|
||||
}
|
||||
|
||||
// WebAssembly memories contain an extra region of guard pages (see
|
||||
// WasmArrayRawBuffer comment). The guard pages catch out-of-bounds accesses
|
||||
// using a signal handler that redirects PC to a stub that safely reports an
|
||||
|
@ -1682,7 +1712,7 @@ Simulator::handleWasmFault(uint64_t addr, unsigned numBytes)
|
|||
|
||||
const wasm::MemoryAccess* memoryAccess = instance->code().lookupMemoryAccess(pc);
|
||||
if (!memoryAccess) {
|
||||
act->startWasmTrap(wasm::Trap::OutOfBounds, 0, registerState());
|
||||
MOZ_ALWAYS_TRUE(act->startWasmInterrupt(registerState()));
|
||||
if (!instance->code().containsCodePC(pc))
|
||||
MOZ_CRASH("Cannot map PC to trap handler");
|
||||
set_pc(int64_t(moduleSegment->outOfBoundsCode()));
|
||||
|
@ -4032,6 +4062,19 @@ Simulator::disable_single_stepping()
|
|||
single_step_callback_arg_ = nullptr;
|
||||
}
|
||||
|
||||
static void
|
||||
FakeInterruptHandler()
|
||||
{
|
||||
JSContext* cx = TlsContext.get();
|
||||
uint8_t* pc = cx->simulator()->get_pc_as<uint8_t*>();
|
||||
|
||||
const wasm::ModuleSegment* ms = nullptr;
|
||||
if (!wasm::InInterruptibleCode(cx, pc, &ms))
|
||||
return;
|
||||
|
||||
cx->simulator()->trigger_wasm_interrupt();
|
||||
}
|
||||
|
||||
template<bool enableStopSimAt>
|
||||
void
|
||||
Simulator::execute()
|
||||
|
@ -4050,9 +4093,16 @@ Simulator::execute()
|
|||
} else {
|
||||
if (single_stepping_)
|
||||
single_step_callback_(single_step_callback_arg_, this, (void*)program_counter);
|
||||
if (MOZ_UNLIKELY(JitOptions.simulatorAlwaysInterrupt))
|
||||
FakeInterruptHandler();
|
||||
SimInstruction* instr = reinterpret_cast<SimInstruction *>(program_counter);
|
||||
instructionDecode(instr);
|
||||
icount_++;
|
||||
|
||||
if (MOZ_UNLIKELY(wasm_interrupt_)) {
|
||||
handleWasmInterrupt();
|
||||
wasm_interrupt_ = false;
|
||||
}
|
||||
}
|
||||
program_counter = get_pc();
|
||||
}
|
||||
|
|
|
@ -206,6 +206,13 @@ class Simulator {
|
|||
template <typename T>
|
||||
T get_pc_as() const { return reinterpret_cast<T>(get_pc()); }
|
||||
|
||||
void trigger_wasm_interrupt() {
|
||||
// This can be called several times if a single interrupt isn't caught
|
||||
// and handled by the simulator, but this is fine; once the current
|
||||
// instruction is done executing, the interrupt will be handled anyhow.
|
||||
wasm_interrupt_ = true;
|
||||
}
|
||||
|
||||
void enable_single_stepping(SingleStepCallback cb, void* arg);
|
||||
void disable_single_stepping();
|
||||
|
||||
|
@ -312,6 +319,8 @@ class Simulator {
|
|||
void increaseStopCounter(uint32_t code);
|
||||
void printStopInfo(uint32_t code);
|
||||
|
||||
// Handle a wasm interrupt triggered by an async signal handler.
|
||||
void handleWasmInterrupt();
|
||||
JS::ProfilingFrameIterator::RegisterState registerState();
|
||||
|
||||
// Handle any wasm faults, returning true if the fault was handled.
|
||||
|
@ -369,6 +378,9 @@ class Simulator {
|
|||
int64_t icount_;
|
||||
int64_t break_count_;
|
||||
|
||||
// wasm async interrupt support
|
||||
bool wasm_interrupt_;
|
||||
|
||||
// Debugger input.
|
||||
char* lastDebuggerInput_;
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ namespace jit {
|
|||
static const bool SupportsSimd = false;
|
||||
static const uint32_t SimdMemoryAlignment = 4; // Make it 4 to avoid a bunch of div-by-zero warnings
|
||||
static const uint32_t WasmStackAlignment = 8;
|
||||
static const uint32_t WasmTrapInstructionLength = 0;
|
||||
|
||||
// Does this architecture support SIMD conversions between Uint32x4 and Float32x4?
|
||||
static constexpr bool SupportsUint32x4FloatConversions = false;
|
||||
|
|
|
@ -81,7 +81,6 @@ static constexpr Register ABINonArgReg1 { Registers::invalid_reg };
|
|||
static constexpr Register ABINonArgReg2 { Registers::invalid_reg };
|
||||
static constexpr Register ABINonArgReturnReg0 { Registers::invalid_reg };
|
||||
static constexpr Register ABINonArgReturnReg1 { Registers::invalid_reg };
|
||||
static constexpr Register ABINonVolatileReg { Registers::invalid_reg };
|
||||
static constexpr Register ABINonArgReturnVolatileReg { Registers::invalid_reg };
|
||||
|
||||
static constexpr FloatRegister ABINonArgDoubleReg = { FloatRegisters::invalid_reg };
|
||||
|
|
|
@ -1663,24 +1663,6 @@ class LInterruptCheck : public LInstructionHelper<0, 0, 1>
|
|||
}
|
||||
};
|
||||
|
||||
class LWasmInterruptCheck : public LInstructionHelper<0, 1, 0>
|
||||
{
|
||||
public:
|
||||
LIR_HEADER(WasmInterruptCheck)
|
||||
|
||||
explicit LWasmInterruptCheck(const LAllocation& tlsData)
|
||||
: LInstructionHelper(classOpcode)
|
||||
{
|
||||
setOperand(0, tlsData);
|
||||
}
|
||||
MWasmInterruptCheck* mir() const {
|
||||
return mir_->toWasmInterruptCheck();
|
||||
}
|
||||
const LAllocation* tlsPtr() {
|
||||
return getOperand(0);
|
||||
}
|
||||
};
|
||||
|
||||
class LDefVar : public LCallInstructionHelper<0, 1, 0>
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -388,7 +388,6 @@
|
|||
_(InstanceOfV) \
|
||||
_(InstanceOfCache) \
|
||||
_(InterruptCheck) \
|
||||
_(WasmInterruptCheck) \
|
||||
_(Rotate) \
|
||||
_(RotateI64) \
|
||||
_(GetDOMProperty) \
|
||||
|
|
|
@ -250,7 +250,6 @@ static_assert(JitStackAlignment % SimdMemoryAlignment == 0,
|
|||
"spilled values. Thus it should be larger than the alignment for SIMD accesses.");
|
||||
|
||||
static const uint32_t WasmStackAlignment = SimdMemoryAlignment;
|
||||
static const uint32_t WasmTrapInstructionLength = 2;
|
||||
|
||||
static const Scale ScalePointer = TimesEight;
|
||||
|
||||
|
|
|
@ -167,7 +167,6 @@ static_assert(JitStackAlignment % SimdMemoryAlignment == 0,
|
|||
"spilled values. Thus it should be larger than the alignment for SIMD accesses.");
|
||||
|
||||
static const uint32_t WasmStackAlignment = SimdMemoryAlignment;
|
||||
static const uint32_t WasmTrapInstructionLength = 2;
|
||||
|
||||
struct ImmTag : public Imm32
|
||||
{
|
||||
|
|
|
@ -7272,6 +7272,9 @@ JS_SetGlobalJitCompilerOption(JSContext* cx, JSJitCompilerOption opt, uint32_t v
|
|||
}
|
||||
jit::JitOptions.jumpThreshold = value;
|
||||
break;
|
||||
case JSJITCOMPILER_SIMULATOR_ALWAYS_INTERRUPT:
|
||||
jit::JitOptions.simulatorAlwaysInterrupt = !!value;
|
||||
break;
|
||||
case JSJITCOMPILER_SPECTRE_INDEX_MASKING:
|
||||
jit::JitOptions.spectreIndexMasking = !!value;
|
||||
break;
|
||||
|
|
|
@ -213,7 +213,6 @@ UNIFIED_SOURCES += [
|
|||
'jit/AliasAnalysis.cpp',
|
||||
'jit/AliasAnalysisShared.cpp',
|
||||
'jit/AlignmentMaskAnalysis.cpp',
|
||||
'jit/AsyncInterrupt.cpp',
|
||||
'jit/BacktrackingAllocator.cpp',
|
||||
'jit/Bailouts.cpp',
|
||||
'jit/BaselineBailouts.cpp',
|
||||
|
|
|
@ -73,7 +73,7 @@ JSCompartment::JSCompartment(Zone* zone, const JS::CompartmentOptions& options =
|
|||
objectMetadataTable(nullptr),
|
||||
innerViews(zone),
|
||||
lazyArrayBuffers(nullptr),
|
||||
wasm(zone->runtimeFromActiveCooperatingThread()),
|
||||
wasm(zone),
|
||||
nonSyntacticLexicalEnvironments_(nullptr),
|
||||
gcIncomingGrayPointers(nullptr),
|
||||
debugModeBits(0),
|
||||
|
@ -183,7 +183,7 @@ JSRuntime::createJitRuntime(JSContext* cx)
|
|||
if (!jrt)
|
||||
return nullptr;
|
||||
|
||||
// Protect jitRuntime_ from being observed (by jit::InterruptRunningCode)
|
||||
// Protect jitRuntime_ from being observed (by InterruptRunningJitCode)
|
||||
// while it is being initialized. Unfortunately, initialization depends on
|
||||
// jitRuntime_ being non-null, so we can't just wait to assign jitRuntime_.
|
||||
JitRuntime::AutoPreventBackedgePatching apbp(cx->runtime(), jrt);
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
#include "builtin/String.h"
|
||||
#include "gc/FreeOp.h"
|
||||
#include "gc/Marking.h"
|
||||
#include "jit/AsyncInterrupt.h"
|
||||
#include "jit/Ion.h"
|
||||
#include "jit/PcScriptCache.h"
|
||||
#include "js/CharacterEncoding.h"
|
||||
|
@ -53,6 +52,7 @@
|
|||
#include "vm/JSObject.h"
|
||||
#include "vm/JSScript.h"
|
||||
#include "vm/Shape.h"
|
||||
#include "wasm/WasmSignalHandlers.h"
|
||||
|
||||
#include "vm/JSObject-inl.h"
|
||||
#include "vm/JSScript-inl.h"
|
||||
|
@ -102,7 +102,7 @@ JSContext::init(ContextKind kind)
|
|||
{
|
||||
// Skip most of the initialization if this thread will not be running JS.
|
||||
if (kind == ContextKind::Cooperative) {
|
||||
// Get a platform-native handle for this thread, used by jit::InterruptRunningCode.
|
||||
// Get a platform-native handle for this thread, used by js::InterruptRunningJitCode.
|
||||
#ifdef XP_WIN
|
||||
size_t openFlags = THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME |
|
||||
THREAD_QUERY_INFORMATION;
|
||||
|
@ -123,12 +123,11 @@ JSContext::init(ContextKind kind)
|
|||
return false;
|
||||
|
||||
#ifdef JS_SIMULATOR
|
||||
simulator_ = jit::Simulator::Create(this);
|
||||
simulator_ = js::jit::Simulator::Create(this);
|
||||
if (!simulator_)
|
||||
return false;
|
||||
#endif
|
||||
|
||||
jit::EnsureAsyncInterrupt(this);
|
||||
if (!wasm::EnsureSignalHandlers(this))
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
_(FutexThread, 500) \
|
||||
_(GeckoProfilerStrings, 500) \
|
||||
_(ProtectedRegionTree, 500) \
|
||||
_(WasmSigIdSet, 500) \
|
||||
_(ShellOffThreadState, 500) \
|
||||
_(SimulatorCacheLock, 500) \
|
||||
_(Arm64SimulatorLock, 500) \
|
||||
|
@ -48,16 +49,14 @@
|
|||
_(ProcessExecutableRegion, 500) \
|
||||
_(OffThreadPromiseState, 500) \
|
||||
_(BufferStreamState, 500) \
|
||||
_(SharedArrayGrow, 500) \
|
||||
_(RuntimeScriptData, 500) \
|
||||
_(WasmSigIdSet, 500) \
|
||||
_(WasmCodeProfilingLabels, 500) \
|
||||
_(WasmModuleTieringLock, 500) \
|
||||
_(WasmCompileTaskState, 500) \
|
||||
_(WasmCodeStreamEnd, 500) \
|
||||
_(WasmTailBytesPtr, 500) \
|
||||
_(WasmStreamStatus, 500) \
|
||||
_(WasmRuntimeInstances, 500) \
|
||||
_(SharedArrayGrow, 500) \
|
||||
_(RuntimeScriptData, 500) \
|
||||
\
|
||||
_(ThreadId, 600) \
|
||||
_(WasmCodeSegmentMap, 600) \
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
#include "gc/PublicIterators.h"
|
||||
#include "jit/arm/Simulator-arm.h"
|
||||
#include "jit/arm64/vixl/Simulator-vixl.h"
|
||||
#include "jit/AsyncInterrupt.h"
|
||||
#include "jit/JitCompartment.h"
|
||||
#include "jit/mips32/Simulator-mips32.h"
|
||||
#include "jit/mips64/Simulator-mips64.h"
|
||||
|
@ -45,6 +44,7 @@
|
|||
#include "vm/JSScript.h"
|
||||
#include "vm/TraceLogging.h"
|
||||
#include "vm/TraceLoggingGraph.h"
|
||||
#include "wasm/WasmSignalHandlers.h"
|
||||
|
||||
#include "gc/GC-inl.h"
|
||||
#include "vm/JSContext-inl.h"
|
||||
|
@ -177,8 +177,7 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime)
|
|||
lastAnimationTime(0),
|
||||
performanceMonitoring_(),
|
||||
stackFormat_(parentRuntime ? js::StackFormat::Default
|
||||
: js::StackFormat::SpiderMonkey),
|
||||
wasmInstances(mutexid::WasmRuntimeInstances)
|
||||
: js::StackFormat::SpiderMonkey)
|
||||
{
|
||||
liveRuntimesCount++;
|
||||
|
||||
|
@ -194,8 +193,6 @@ JSRuntime::~JSRuntime()
|
|||
|
||||
DebugOnly<size_t> oldCount = liveRuntimesCount--;
|
||||
MOZ_ASSERT(oldCount > 0);
|
||||
|
||||
MOZ_ASSERT(wasmInstances.lock()->empty());
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -512,8 +509,6 @@ JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::Runtim
|
|||
jitRuntime_->execAlloc().addSizeOfCode(&rtSizes->code);
|
||||
jitRuntime_->backedgeExecAlloc().addSizeOfCode(&rtSizes->code);
|
||||
}
|
||||
|
||||
rtSizes->wasmRuntime += wasmInstances.lock()->sizeOfExcludingThis(mallocSizeOf);
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -603,8 +598,7 @@ JSContext::requestInterrupt(InterruptMode mode)
|
|||
if (fx.isWaiting())
|
||||
fx.wake(FutexThread::WakeForJSInterrupt);
|
||||
fx.unlock();
|
||||
jit::InterruptRunningCode(this);
|
||||
wasm::InterruptRunningCode(this);
|
||||
InterruptRunningJitCode(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -999,14 +999,18 @@ struct JSRuntime : public js::MallocProvider<JSRuntime>
|
|||
public:
|
||||
js::RuntimeCaches& caches() { return caches_.ref(); }
|
||||
|
||||
// When wasm traps, the signal handler records some data for unwinding
|
||||
// purposes. Wasm code can't trap reentrantly.
|
||||
js::ActiveThreadData<mozilla::Maybe<js::wasm::TrapData>> wasmTrapData;
|
||||
// When wasm traps or is interrupted, the signal handler records some data
|
||||
// for unwinding purposes. Wasm code can't interrupt or trap reentrantly.
|
||||
js::ActiveThreadData<
|
||||
mozilla::MaybeOneOf<js::wasm::TrapData, js::wasm::InterruptData>
|
||||
> wasmUnwindData;
|
||||
|
||||
// List of all the live wasm::Instances in the runtime. Equal to the union
|
||||
// of all instances registered in all JSCompartments. Accessed from watchdog
|
||||
// threads for purposes of wasm::InterruptRunningCode().
|
||||
js::ExclusiveData<js::wasm::InstanceVector> wasmInstances;
|
||||
js::wasm::TrapData& wasmTrapData() {
|
||||
return wasmUnwindData.ref().ref<js::wasm::TrapData>();
|
||||
}
|
||||
js::wasm::InterruptData& wasmInterruptData() {
|
||||
return wasmUnwindData.ref().ref<js::wasm::InterruptData>();
|
||||
}
|
||||
|
||||
public:
|
||||
#if defined(NIGHTLY_BUILD)
|
||||
|
|
|
@ -1574,7 +1574,7 @@ jit::JitActivation::~JitActivation()
|
|||
// JitActivations.
|
||||
MOZ_ASSERT(!bailoutData_);
|
||||
|
||||
// Traps get handled immediately.
|
||||
MOZ_ASSERT(!isWasmInterrupted());
|
||||
MOZ_ASSERT(!isWasmTrapping());
|
||||
|
||||
clearRematerializedFrames();
|
||||
|
@ -1742,6 +1742,86 @@ jit::JitActivation::traceIonRecovery(JSTracer* trc)
|
|||
it->trace(trc);
|
||||
}
|
||||
|
||||
bool
|
||||
jit::JitActivation::startWasmInterrupt(const JS::ProfilingFrameIterator::RegisterState& state)
|
||||
{
|
||||
// fp may be null when first entering wasm code from an interpreter entry
|
||||
// stub.
|
||||
if (!state.fp)
|
||||
return false;
|
||||
|
||||
MOZ_ASSERT(state.pc);
|
||||
|
||||
// Execution can only be interrupted in function code. Afterwards, control
|
||||
// flow does not reenter function code and thus there can be no
|
||||
// interrupt-during-interrupt.
|
||||
|
||||
bool unwound;
|
||||
wasm::UnwindState unwindState;
|
||||
MOZ_ALWAYS_TRUE(wasm::StartUnwinding(state, &unwindState, &unwound));
|
||||
|
||||
void* pc = unwindState.pc;
|
||||
|
||||
if (unwound) {
|
||||
// In the prologue/epilogue, FP might have been fixed up to the
|
||||
// caller's FP, and the caller could be the jit entry. Ignore this
|
||||
// interrupt, in this case, because FP points to a jit frame and not a
|
||||
// wasm one.
|
||||
if (!wasm::LookupCode(pc)->lookupFuncRange(pc))
|
||||
return false;
|
||||
}
|
||||
|
||||
cx_->runtime()->wasmUnwindData.ref().construct<wasm::InterruptData>(pc, state.pc);
|
||||
setWasmExitFP(unwindState.fp);
|
||||
|
||||
MOZ_ASSERT(compartment() == unwindState.fp->tls->instance->compartment());
|
||||
MOZ_ASSERT(isWasmInterrupted());
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
jit::JitActivation::finishWasmInterrupt()
|
||||
{
|
||||
MOZ_ASSERT(isWasmInterrupted());
|
||||
|
||||
cx_->runtime()->wasmUnwindData.ref().destroy();
|
||||
packedExitFP_ = nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
jit::JitActivation::isWasmInterrupted() const
|
||||
{
|
||||
JSRuntime* rt = cx_->runtime();
|
||||
if (!rt->wasmUnwindData.ref().constructed<wasm::InterruptData>())
|
||||
return false;
|
||||
|
||||
Activation* act = cx_->activation();
|
||||
while (act && !act->hasWasmExitFP())
|
||||
act = act->prev();
|
||||
|
||||
if (act != this)
|
||||
return false;
|
||||
|
||||
DebugOnly<const wasm::Frame*> fp = wasmExitFP();
|
||||
DebugOnly<void*> unwindPC = rt->wasmInterruptData().unwindPC;
|
||||
MOZ_ASSERT(fp->instance()->code().containsCodePC(unwindPC));
|
||||
return true;
|
||||
}
|
||||
|
||||
void*
|
||||
jit::JitActivation::wasmInterruptUnwindPC() const
|
||||
{
|
||||
MOZ_ASSERT(isWasmInterrupted());
|
||||
return cx_->runtime()->wasmInterruptData().unwindPC;
|
||||
}
|
||||
|
||||
void*
|
||||
jit::JitActivation::wasmInterruptResumePC() const
|
||||
{
|
||||
MOZ_ASSERT(isWasmInterrupted());
|
||||
return cx_->runtime()->wasmInterruptData().resumePC;
|
||||
}
|
||||
|
||||
void
|
||||
jit::JitActivation::startWasmTrap(wasm::Trap trap, uint32_t bytecodeOffset,
|
||||
const wasm::RegisterState& state)
|
||||
|
@ -1762,13 +1842,7 @@ jit::JitActivation::startWasmTrap(wasm::Trap trap, uint32_t bytecodeOffset,
|
|||
if (unwound)
|
||||
bytecodeOffset = code.lookupCallSite(pc)->lineOrBytecode();
|
||||
|
||||
wasm::TrapData trapData;
|
||||
trapData.resumePC = ((uint8_t*)state.pc) + jit::WasmTrapInstructionLength;
|
||||
trapData.unwoundPC = pc;
|
||||
trapData.trap = trap;
|
||||
trapData.bytecodeOffset = bytecodeOffset;
|
||||
|
||||
cx_->runtime()->wasmTrapData = Some(trapData);
|
||||
cx_->runtime()->wasmUnwindData.ref().construct<wasm::TrapData>(pc, trap, bytecodeOffset);
|
||||
setWasmExitFP(fp);
|
||||
}
|
||||
|
||||
|
@ -1777,7 +1851,7 @@ jit::JitActivation::finishWasmTrap()
|
|||
{
|
||||
MOZ_ASSERT(isWasmTrapping());
|
||||
|
||||
cx_->runtime()->wasmTrapData.ref().reset();
|
||||
cx_->runtime()->wasmUnwindData.ref().destroy();
|
||||
packedExitFP_ = nullptr;
|
||||
}
|
||||
|
||||
|
@ -1785,7 +1859,7 @@ bool
|
|||
jit::JitActivation::isWasmTrapping() const
|
||||
{
|
||||
JSRuntime* rt = cx_->runtime();
|
||||
if (!rt->wasmTrapData.ref())
|
||||
if (!rt->wasmUnwindData.ref().constructed<wasm::TrapData>())
|
||||
return false;
|
||||
|
||||
Activation* act = cx_->activation();
|
||||
|
@ -1795,29 +1869,24 @@ jit::JitActivation::isWasmTrapping() const
|
|||
if (act != this)
|
||||
return false;
|
||||
|
||||
MOZ_ASSERT(wasmExitFP()->instance()->code().containsCodePC(rt->wasmTrapData->unwoundPC));
|
||||
DebugOnly<const wasm::Frame*> fp = wasmExitFP();
|
||||
DebugOnly<void*> unwindPC = rt->wasmTrapData().pc;
|
||||
MOZ_ASSERT(fp->instance()->code().containsCodePC(unwindPC));
|
||||
return true;
|
||||
}
|
||||
|
||||
void*
|
||||
jit::JitActivation::wasmTrapResumePC() const
|
||||
jit::JitActivation::wasmTrapPC() const
|
||||
{
|
||||
MOZ_ASSERT(isWasmTrapping());
|
||||
return cx_->runtime()->wasmTrapData->resumePC;
|
||||
}
|
||||
|
||||
void*
|
||||
jit::JitActivation::wasmTrapUnwoundPC() const
|
||||
{
|
||||
MOZ_ASSERT(isWasmTrapping());
|
||||
return cx_->runtime()->wasmTrapData->unwoundPC;
|
||||
return cx_->runtime()->wasmTrapData().pc;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
jit::JitActivation::wasmTrapBytecodeOffset() const
|
||||
{
|
||||
MOZ_ASSERT(isWasmTrapping());
|
||||
return cx_->runtime()->wasmTrapData->bytecodeOffset;
|
||||
return cx_->runtime()->wasmTrapData().bytecodeOffset;
|
||||
}
|
||||
|
||||
InterpreterFrameIterator&
|
||||
|
|
|
@ -1805,11 +1805,21 @@ class JitActivation : public Activation
|
|||
return offsetof(JitActivation, encodedWasmExitReason_);
|
||||
}
|
||||
|
||||
// Interrupts are started from the interrupt signal handler (or the ARM
|
||||
// simulator) and cleared by WasmHandleExecutionInterrupt or WasmHandleThrow
|
||||
// when the interrupt is handled.
|
||||
|
||||
// Returns true iff we've entered interrupted state.
|
||||
bool startWasmInterrupt(const wasm::RegisterState& state);
|
||||
void finishWasmInterrupt();
|
||||
bool isWasmInterrupted() const;
|
||||
void* wasmInterruptUnwindPC() const;
|
||||
void* wasmInterruptResumePC() const;
|
||||
|
||||
void startWasmTrap(wasm::Trap trap, uint32_t bytecodeOffset, const wasm::RegisterState& state);
|
||||
void finishWasmTrap();
|
||||
bool isWasmTrapping() const;
|
||||
void* wasmTrapResumePC() const;
|
||||
void* wasmTrapUnwoundPC() const;
|
||||
void* wasmTrapPC() const;
|
||||
uint32_t wasmTrapBytecodeOffset() const;
|
||||
};
|
||||
|
||||
|
|
|
@ -3371,10 +3371,10 @@ class BaseCompiler final : public BaseCompilerInterface
|
|||
masm.loadConstantDouble(d, dest);
|
||||
}
|
||||
|
||||
void addInterruptCheck() {
|
||||
ScratchI32 tmp(*this);
|
||||
masm.loadWasmTlsRegFromFrame(tmp);
|
||||
masm.wasmInterruptCheck(tmp, bytecodeOffset());
|
||||
void addInterruptCheck()
|
||||
{
|
||||
// Always use signals for interrupts with Asm.JS/Wasm
|
||||
MOZ_RELEASE_ASSERT(HaveSignalHandlers());
|
||||
}
|
||||
|
||||
void jumpTable(const LabelVector& labels, Label* theTable) {
|
||||
|
@ -9491,6 +9491,8 @@ BaseCompiler::init()
|
|||
if (!fr.setupLocals(locals_, sig().args(), debugEnabled_, &localInfo_))
|
||||
return false;
|
||||
|
||||
addInterruptCheck();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -69,6 +69,28 @@ CallingActivation()
|
|||
return act->asJit();
|
||||
}
|
||||
|
||||
static void*
|
||||
WasmHandleExecutionInterrupt()
|
||||
{
|
||||
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 HandleThrow. The JitActivation must stay in the
|
||||
// interrupted state until then so that stack unwinding works in
|
||||
// HandleThrow.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// If CheckForInterrupt succeeded, then execution can proceed and the
|
||||
// interrupt is over.
|
||||
void* resumePC = activation->wasmInterruptResumePC();
|
||||
activation->finishWasmInterrupt();
|
||||
return resumePC;
|
||||
}
|
||||
|
||||
static bool
|
||||
WasmHandleDebugTrap()
|
||||
{
|
||||
|
@ -197,6 +219,7 @@ wasm::HandleThrow(JSContext* cx, WasmFrameIter& iter)
|
|||
frame->leave(cx);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!cx->activation()->asJit()->isWasmInterrupted(), "unwinding clears the interrupt");
|
||||
MOZ_ASSERT(!cx->activation()->asJit()->isWasmTrapping(), "unwinding clears the trapping state");
|
||||
|
||||
return iter.unwoundAddressOfReturnAddress();
|
||||
|
@ -211,94 +234,61 @@ WasmHandleThrow()
|
|||
return HandleThrow(cx, iter);
|
||||
}
|
||||
|
||||
// Unconditionally returns nullptr per calling convention of HandleTrap().
|
||||
static void*
|
||||
ReportError(JSContext* cx, unsigned errorNumber)
|
||||
{
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber);
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
// Has the same return-value convention as HandleTrap().
|
||||
static void*
|
||||
CheckInterrupt(JSContext* cx, JitActivation* activation)
|
||||
{
|
||||
ResetInterruptState(cx);
|
||||
|
||||
if (!CheckForInterrupt(cx))
|
||||
return nullptr;
|
||||
|
||||
void* resumePC = activation->wasmTrapResumePC();
|
||||
activation->finishWasmTrap();
|
||||
return resumePC;
|
||||
}
|
||||
|
||||
// The calling convention between this function and its caller in the stub
|
||||
// generated by GenerateTrapExit() is:
|
||||
// - return nullptr if the stub should jump to the throw stub to unwind
|
||||
// the activation;
|
||||
// - return the (non-null) resumePC that should be jumped if execution should
|
||||
// resume after the trap.
|
||||
static void*
|
||||
HandleTrap(Trap trap)
|
||||
{
|
||||
JitActivation* activation = CallingActivation();
|
||||
JSContext* cx = activation->cx();
|
||||
|
||||
switch (trap) {
|
||||
case Trap::Unreachable:
|
||||
return ReportError(cx, JSMSG_WASM_UNREACHABLE);
|
||||
case Trap::IntegerOverflow:
|
||||
return ReportError(cx, JSMSG_WASM_INTEGER_OVERFLOW);
|
||||
case Trap::InvalidConversionToInteger:
|
||||
return ReportError(cx, JSMSG_WASM_INVALID_CONVERSION);
|
||||
case Trap::IntegerDivideByZero:
|
||||
return ReportError(cx, JSMSG_WASM_INT_DIVIDE_BY_ZERO);
|
||||
case Trap::IndirectCallToNull:
|
||||
return ReportError(cx, JSMSG_WASM_IND_CALL_TO_NULL);
|
||||
case Trap::IndirectCallBadSig:
|
||||
return ReportError(cx, JSMSG_WASM_IND_CALL_BAD_SIG);
|
||||
case Trap::ImpreciseSimdConversion:
|
||||
return ReportError(cx, JSMSG_SIMD_FAILED_CONVERSION);
|
||||
case Trap::OutOfBounds:
|
||||
return ReportError(cx, JSMSG_WASM_OUT_OF_BOUNDS);
|
||||
case Trap::UnalignedAccess:
|
||||
return ReportError(cx, JSMSG_WASM_UNALIGNED_ACCESS);
|
||||
case Trap::CheckInterrupt:
|
||||
return CheckInterrupt(cx, activation);
|
||||
case Trap::StackOverflow:
|
||||
// TlsData::setInterrupt() causes a fake stack overflow. Since
|
||||
// TlsData::setInterrupt() is called racily, it's possible for a real
|
||||
// stack overflow to trap, followed by a racy call to setInterrupt().
|
||||
// Thus, we must check for a real stack overflow first before we
|
||||
// CheckInterrupt() and possibly resume execution.
|
||||
if (!CheckRecursionLimit(cx))
|
||||
return nullptr;
|
||||
if (activation->wasmExitFP()->tls->isInterrupted())
|
||||
return CheckInterrupt(cx, activation);
|
||||
return ReportError(cx, JSMSG_OVER_RECURSED);
|
||||
case Trap::ThrowReported:
|
||||
// Error was already reported under another name.
|
||||
return nullptr;
|
||||
case Trap::Limit:
|
||||
break;
|
||||
}
|
||||
|
||||
MOZ_CRASH("unexpected trap");
|
||||
}
|
||||
|
||||
static void
|
||||
WasmOldReportTrap(int32_t trapIndex)
|
||||
{
|
||||
JSContext* cx = TlsContext.get();
|
||||
|
||||
MOZ_ASSERT(trapIndex < int32_t(Trap::Limit) && trapIndex >= 0);
|
||||
DebugOnly<void*> resumePC = HandleTrap(Trap(trapIndex));
|
||||
MOZ_ASSERT(!resumePC);
|
||||
Trap trap = Trap(trapIndex);
|
||||
|
||||
unsigned errorNumber;
|
||||
switch (trap) {
|
||||
case Trap::Unreachable:
|
||||
errorNumber = JSMSG_WASM_UNREACHABLE;
|
||||
break;
|
||||
case Trap::IntegerOverflow:
|
||||
errorNumber = JSMSG_WASM_INTEGER_OVERFLOW;
|
||||
break;
|
||||
case Trap::InvalidConversionToInteger:
|
||||
errorNumber = JSMSG_WASM_INVALID_CONVERSION;
|
||||
break;
|
||||
case Trap::IntegerDivideByZero:
|
||||
errorNumber = JSMSG_WASM_INT_DIVIDE_BY_ZERO;
|
||||
break;
|
||||
case Trap::IndirectCallToNull:
|
||||
errorNumber = JSMSG_WASM_IND_CALL_TO_NULL;
|
||||
break;
|
||||
case Trap::IndirectCallBadSig:
|
||||
errorNumber = JSMSG_WASM_IND_CALL_BAD_SIG;
|
||||
break;
|
||||
case Trap::ImpreciseSimdConversion:
|
||||
errorNumber = JSMSG_SIMD_FAILED_CONVERSION;
|
||||
break;
|
||||
case Trap::OutOfBounds:
|
||||
errorNumber = JSMSG_WASM_OUT_OF_BOUNDS;
|
||||
break;
|
||||
case Trap::UnalignedAccess:
|
||||
errorNumber = JSMSG_WASM_UNALIGNED_ACCESS;
|
||||
break;
|
||||
case Trap::StackOverflow:
|
||||
errorNumber = JSMSG_OVER_RECURSED;
|
||||
break;
|
||||
case Trap::ThrowReported:
|
||||
// Error was already reported under another name.
|
||||
return;
|
||||
default:
|
||||
MOZ_CRASH("unexpected trap");
|
||||
}
|
||||
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber);
|
||||
}
|
||||
|
||||
static void*
|
||||
WasmHandleTrap()
|
||||
static void
|
||||
WasmReportTrap()
|
||||
{
|
||||
return HandleTrap(TlsContext.get()->runtime()->wasmTrapData->trap);
|
||||
Trap trap = TlsContext.get()->runtime()->wasmTrapData().trap;
|
||||
WasmOldReportTrap(int32_t(trap));
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -521,15 +511,18 @@ void*
|
|||
wasm::AddressOf(SymbolicAddress imm, ABIFunctionType* abiType)
|
||||
{
|
||||
switch (imm) {
|
||||
case SymbolicAddress::HandleExecutionInterrupt:
|
||||
*abiType = Args_General0;
|
||||
return FuncCast(WasmHandleExecutionInterrupt, *abiType);
|
||||
case SymbolicAddress::HandleDebugTrap:
|
||||
*abiType = Args_General0;
|
||||
return FuncCast(WasmHandleDebugTrap, *abiType);
|
||||
case SymbolicAddress::HandleThrow:
|
||||
*abiType = Args_General0;
|
||||
return FuncCast(WasmHandleThrow, *abiType);
|
||||
case SymbolicAddress::HandleTrap:
|
||||
case SymbolicAddress::ReportTrap:
|
||||
*abiType = Args_General0;
|
||||
return FuncCast(WasmHandleTrap, *abiType);
|
||||
return FuncCast(WasmReportTrap, *abiType);
|
||||
case SymbolicAddress::OldReportTrap:
|
||||
*abiType = Args_General1;
|
||||
return FuncCast(WasmOldReportTrap, *abiType);
|
||||
|
@ -699,9 +692,10 @@ wasm::NeedsBuiltinThunk(SymbolicAddress sym)
|
|||
// Some functions don't want to a thunk, because they already have one or
|
||||
// they don't have frame info.
|
||||
switch (sym) {
|
||||
case SymbolicAddress::HandleExecutionInterrupt: // GenerateInterruptExit
|
||||
case SymbolicAddress::HandleDebugTrap: // GenerateDebugTrapStub
|
||||
case SymbolicAddress::HandleThrow: // GenerateThrowStub
|
||||
case SymbolicAddress::HandleTrap: // GenerateTrapExit
|
||||
case SymbolicAddress::ReportTrap: // GenerateTrapExit
|
||||
case SymbolicAddress::OldReportTrap: // GenerateOldTrapExit
|
||||
case SymbolicAddress::ReportOutOfBounds: // GenerateOutOfBoundsExit
|
||||
case SymbolicAddress::ReportUnalignedAccess: // GenerateUnalignedExit
|
||||
|
@ -880,8 +874,8 @@ PopulateTypedNatives(TypedNativeToFuncPtrMap* typedNatives)
|
|||
// things:
|
||||
// - bridging the few differences between the internal wasm ABI and the external
|
||||
// native ABI (viz. float returns on x86 and soft-fp ARM)
|
||||
// - executing an exit prologue/epilogue which in turn allows any profiling
|
||||
// iterator to see the full stack up to the wasm operation that called out
|
||||
// - executing an exit prologue/epilogue which in turn allows any asynchronous
|
||||
// interrupt to see the full stack up to the wasm operation that called out
|
||||
//
|
||||
// Thunks are created for two kinds of C++ callees, enumerated above:
|
||||
// - SymbolicAddress: for statically compiled calls in the wasm module
|
||||
|
|
|
@ -335,6 +335,7 @@ ModuleSegment::initialize(Tier tier,
|
|||
const CodeRangeVector& codeRanges)
|
||||
{
|
||||
MOZ_ASSERT(bytes_ == nullptr);
|
||||
MOZ_ASSERT(linkData.interruptOffset);
|
||||
MOZ_ASSERT(linkData.outOfBoundsOffset);
|
||||
MOZ_ASSERT(linkData.unalignedAccessOffset);
|
||||
MOZ_ASSERT(linkData.trapOffset);
|
||||
|
@ -342,6 +343,7 @@ ModuleSegment::initialize(Tier tier,
|
|||
tier_ = tier;
|
||||
bytes_ = Move(codeBytes);
|
||||
length_ = codeLength;
|
||||
interruptCode_ = bytes_.get() + linkData.interruptOffset;
|
||||
outOfBoundsCode_ = bytes_.get() + linkData.outOfBoundsOffset;
|
||||
unalignedAccessCode_ = bytes_.get() + linkData.unalignedAccessOffset;
|
||||
trapCode_ = bytes_.get() + linkData.trapOffset;
|
||||
|
|
|
@ -144,8 +144,9 @@ class ModuleSegment : public CodeSegment
|
|||
{
|
||||
Tier tier_;
|
||||
|
||||
// These are pointers into code for stubs used for signal-handler
|
||||
// control-flow transfer.
|
||||
// These are pointers into code for stubs used for asynchronous
|
||||
// signal-handler control-flow transfer.
|
||||
uint8_t* interruptCode_;
|
||||
uint8_t* outOfBoundsCode_;
|
||||
uint8_t* unalignedAccessCode_;
|
||||
uint8_t* trapCode_;
|
||||
|
@ -172,6 +173,7 @@ class ModuleSegment : public CodeSegment
|
|||
ModuleSegment()
|
||||
: CodeSegment(),
|
||||
tier_(Tier(-1)),
|
||||
interruptCode_(nullptr),
|
||||
outOfBoundsCode_(nullptr),
|
||||
unalignedAccessCode_(nullptr),
|
||||
trapCode_(nullptr)
|
||||
|
@ -193,6 +195,7 @@ class ModuleSegment : public CodeSegment
|
|||
|
||||
Tier tier() const { return tier_; }
|
||||
|
||||
uint8_t* interruptCode() const { return interruptCode_; }
|
||||
uint8_t* outOfBoundsCode() const { return outOfBoundsCode_; }
|
||||
uint8_t* unalignedAccessCode() const { return unalignedAccessCode_; }
|
||||
uint8_t* trapCode() const { return trapCode_; }
|
||||
|
|
|
@ -26,8 +26,7 @@
|
|||
using namespace js;
|
||||
using namespace wasm;
|
||||
|
||||
Compartment::Compartment(JSRuntime* rt)
|
||||
: runtime_(rt)
|
||||
Compartment::Compartment(Zone* zone)
|
||||
{}
|
||||
|
||||
Compartment::~Compartment()
|
||||
|
@ -63,8 +62,6 @@ struct InstanceComparator
|
|||
bool
|
||||
Compartment::registerInstance(JSContext* cx, HandleWasmInstanceObject instanceObj)
|
||||
{
|
||||
MOZ_ASSERT(runtime_ == cx->runtime());
|
||||
|
||||
Instance& instance = instanceObj->instance();
|
||||
MOZ_ASSERT(this == &instance.compartment()->wasm);
|
||||
|
||||
|
@ -73,27 +70,15 @@ Compartment::registerInstance(JSContext* cx, HandleWasmInstanceObject instanceOb
|
|||
if (instance.debugEnabled() && instance.compartment()->debuggerObservesAllExecution())
|
||||
instance.ensureEnterFrameTrapsState(cx, true);
|
||||
|
||||
{
|
||||
if (!instances_.reserve(instances_.length() + 1))
|
||||
return false;
|
||||
size_t index;
|
||||
if (BinarySearchIf(instances_, 0, instances_.length(), InstanceComparator(instance), &index))
|
||||
MOZ_CRASH("duplicate registration");
|
||||
|
||||
auto runtimeInstances = cx->runtime()->wasmInstances.lock();
|
||||
if (!runtimeInstances->reserve(runtimeInstances->length() + 1))
|
||||
return false;
|
||||
|
||||
// To avoid implementing rollback, do not fail after mutations start.
|
||||
|
||||
InstanceComparator cmp(instance);
|
||||
size_t index;
|
||||
|
||||
MOZ_ALWAYS_FALSE(BinarySearchIf(instances_, 0, instances_.length(), cmp, &index));
|
||||
MOZ_ALWAYS_TRUE(instances_.insert(instances_.begin() + index, &instance));
|
||||
|
||||
MOZ_ALWAYS_FALSE(BinarySearchIf(runtimeInstances.get(), 0, runtimeInstances->length(), cmp, &index));
|
||||
MOZ_ALWAYS_TRUE(runtimeInstances->insert(runtimeInstances->begin() + index, &instance));
|
||||
if (!instances_.insert(instances_.begin() + index, &instance)) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Notify the debugger after wasmInstances is unlocked.
|
||||
Debugger::onNewWasmInstance(cx, instanceObj);
|
||||
return true;
|
||||
}
|
||||
|
@ -101,15 +86,10 @@ Compartment::registerInstance(JSContext* cx, HandleWasmInstanceObject instanceOb
|
|||
void
|
||||
Compartment::unregisterInstance(Instance& instance)
|
||||
{
|
||||
InstanceComparator cmp(instance);
|
||||
size_t index;
|
||||
|
||||
if (BinarySearchIf(instances_, 0, instances_.length(), cmp, &index))
|
||||
instances_.erase(instances_.begin() + index);
|
||||
|
||||
auto runtimeInstances = runtime_->wasmInstances.lock();
|
||||
if (BinarySearchIf(runtimeInstances.get(), 0, runtimeInstances->length(), cmp, &index))
|
||||
runtimeInstances->erase(runtimeInstances->begin() + index);
|
||||
if (!BinarySearchIf(instances_, 0, instances_.length(), InstanceComparator(instance), &index))
|
||||
return;
|
||||
instances_.erase(instances_.begin() + index);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -124,19 +104,3 @@ Compartment::addSizeOfExcludingThis(MallocSizeOf mallocSizeOf, size_t* compartme
|
|||
{
|
||||
*compartmentTables += instances_.sizeOfExcludingThis(mallocSizeOf);
|
||||
}
|
||||
|
||||
void
|
||||
wasm::InterruptRunningCode(JSContext* cx)
|
||||
{
|
||||
auto runtimeInstances = cx->runtime()->wasmInstances.lock();
|
||||
for (Instance* instance : runtimeInstances.get())
|
||||
instance->tlsData()->setInterrupt();
|
||||
}
|
||||
|
||||
void
|
||||
wasm::ResetInterruptState(JSContext* cx)
|
||||
{
|
||||
auto runtimeInstances = cx->runtime()->wasmInstances.lock();
|
||||
for (Instance* instance : runtimeInstances.get())
|
||||
instance->tlsData()->resetInterrupt(cx);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
namespace js {
|
||||
namespace wasm {
|
||||
|
||||
typedef Vector<Instance*, 0, SystemAllocPolicy> InstanceVector;
|
||||
|
||||
// wasm::Compartment lives in JSCompartment and contains the wasm-related
|
||||
// per-compartment state. wasm::Compartment tracks every live instance in the
|
||||
// compartment and must be notified, via registerInstance(), of any new
|
||||
|
@ -31,11 +33,10 @@ namespace wasm {
|
|||
|
||||
class Compartment
|
||||
{
|
||||
JSRuntime* runtime_;
|
||||
InstanceVector instances_;
|
||||
|
||||
public:
|
||||
explicit Compartment(JSRuntime* rt);
|
||||
explicit Compartment(Zone* zone);
|
||||
~Compartment();
|
||||
|
||||
// Before a WasmInstanceObject can be considered fully constructed and
|
||||
|
@ -63,19 +64,6 @@ class Compartment
|
|||
void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, size_t* compartmentTables);
|
||||
};
|
||||
|
||||
// Interrupt all running wasm Instances that have been registered with
|
||||
// wasm::Compartments in the given JSContext.
|
||||
|
||||
extern void
|
||||
InterruptRunningCode(JSContext* cx);
|
||||
|
||||
// After a wasm Instance sees an interrupt request and calls
|
||||
// CheckForInterrupt(), it should call RunningCodeInterrupted() to clear the
|
||||
// interrupt request for all wasm Instances to avoid spurious trapping.
|
||||
|
||||
void
|
||||
ResetInterruptState(JSContext* cx);
|
||||
|
||||
} // namespace wasm
|
||||
} // namespace js
|
||||
|
||||
|
|
|
@ -50,9 +50,9 @@ WasmFrameIter::WasmFrameIter(JitActivation* activation, wasm::Frame* fp)
|
|||
|
||||
if (activation->isWasmTrapping()) {
|
||||
code_ = &fp_->tls->instance->code();
|
||||
MOZ_ASSERT(code_ == LookupCode(activation->wasmTrapUnwoundPC()));
|
||||
MOZ_ASSERT(code_ == LookupCode(activation->wasmTrapPC()));
|
||||
|
||||
codeRange_ = code_->lookupFuncRange(activation->wasmTrapUnwoundPC());
|
||||
codeRange_ = code_->lookupFuncRange(activation->wasmTrapPC());
|
||||
MOZ_ASSERT(codeRange_);
|
||||
|
||||
lineOrBytecode_ = activation->wasmTrapBytecodeOffset();
|
||||
|
@ -61,6 +61,27 @@ WasmFrameIter::WasmFrameIter(JitActivation* activation, wasm::Frame* fp)
|
|||
return;
|
||||
}
|
||||
|
||||
// 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 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.
|
||||
|
||||
if (activation->isWasmInterrupted()) {
|
||||
code_ = &fp_->tls->instance->code();
|
||||
MOZ_ASSERT(code_ == LookupCode(activation->wasmInterruptUnwindPC()));
|
||||
|
||||
codeRange_ = code_->lookupFuncRange(activation->wasmInterruptUnwindPC());
|
||||
MOZ_ASSERT(codeRange_);
|
||||
|
||||
lineOrBytecode_ = codeRange_->funcLineOrBytecode();
|
||||
|
||||
MOZ_ASSERT(!done());
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, 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
|
||||
|
@ -90,12 +111,14 @@ WasmFrameIter::operator++()
|
|||
// 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 trapping, then exitFP is included in the callstack (otherwise,
|
||||
// it is skipped, as explained above). So to unwind the innermost frame, we
|
||||
// just clear the trapping state.
|
||||
// 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_->isWasmTrapping())
|
||||
if (activation_->isWasmInterrupted())
|
||||
activation_->finishWasmInterrupt();
|
||||
else if (activation_->isWasmTrapping())
|
||||
activation_->finishWasmTrap();
|
||||
activation_->setWasmExitFP(fp_);
|
||||
}
|
||||
|
@ -709,8 +732,10 @@ ProfilingFrameIterator::initFromExitFP(const Frame* fp)
|
|||
// This means that the innermost frame is skipped. This is fine because:
|
||||
// - for import exit calls, the innermost frame is a thunk, so the first
|
||||
// frame that shows up is the function calling the import;
|
||||
// - for Math and other builtin calls, we note the absence of an exit
|
||||
// reason and inject a fake "builtin" frame; and
|
||||
// - for Math and other builtin calls as well as interrupts, we note the
|
||||
// absence of an exit reason and inject a fake "builtin" frame; and
|
||||
// - for async interrupts, we just accept that we'll lose the innermost
|
||||
// frame.
|
||||
switch (codeRange_->kind()) {
|
||||
case CodeRange::InterpEntry:
|
||||
callerPC_ = nullptr;
|
||||
|
@ -738,6 +763,7 @@ ProfilingFrameIterator::initFromExitFP(const Frame* fp)
|
|||
case CodeRange::OutOfBoundsExit:
|
||||
case CodeRange::UnalignedExit:
|
||||
case CodeRange::Throw:
|
||||
case CodeRange::Interrupt:
|
||||
case CodeRange::FarJumpIsland:
|
||||
MOZ_CRASH("Unexpected CodeRange kind");
|
||||
}
|
||||
|
@ -935,6 +961,11 @@ js::wasm::StartUnwinding(const RegisterState& registers, UnwindState* unwindStat
|
|||
// the entire activation. To simplify testing, we simply pretend throw
|
||||
// stubs have already popped the entire stack.
|
||||
return false;
|
||||
case CodeRange::Interrupt:
|
||||
// When the PC is in the async interrupt stub, the fp may be garbage and
|
||||
// so we cannot blindly unwind it. Since the percent of time spent in
|
||||
// the interrupt stub is extremely small, just ignore the stack.
|
||||
return false;
|
||||
}
|
||||
|
||||
unwindState->code = code;
|
||||
|
@ -1060,6 +1091,7 @@ ProfilingFrameIterator::operator++()
|
|||
MOZ_CRASH("should have had null caller fp");
|
||||
case CodeRange::JitEntry:
|
||||
MOZ_CRASH("should have been guarded above");
|
||||
case CodeRange::Interrupt:
|
||||
case CodeRange::Throw:
|
||||
MOZ_CRASH("code range doesn't have frame");
|
||||
}
|
||||
|
@ -1072,9 +1104,10 @@ ThunkedNativeToDescription(SymbolicAddress func)
|
|||
{
|
||||
MOZ_ASSERT(NeedsBuiltinThunk(func));
|
||||
switch (func) {
|
||||
case SymbolicAddress::HandleExecutionInterrupt:
|
||||
case SymbolicAddress::HandleDebugTrap:
|
||||
case SymbolicAddress::HandleThrow:
|
||||
case SymbolicAddress::HandleTrap:
|
||||
case SymbolicAddress::ReportTrap:
|
||||
case SymbolicAddress::OldReportTrap:
|
||||
case SymbolicAddress::ReportOutOfBounds:
|
||||
case SymbolicAddress::ReportUnalignedAccess:
|
||||
|
@ -1227,7 +1260,8 @@ ProfilingFrameIterator::label() const
|
|||
case CodeRange::OutOfBoundsExit: return "out-of-bounds stub (in wasm)";
|
||||
case CodeRange::UnalignedExit: return "unaligned trap stub (in wasm)";
|
||||
case CodeRange::FarJumpIsland: return "interstitial (in wasm)";
|
||||
case CodeRange::Throw: MOZ_CRASH("does not have a frame");
|
||||
case CodeRange::Throw: MOZ_FALLTHROUGH;
|
||||
case CodeRange::Interrupt: MOZ_CRASH("does not have a frame");
|
||||
}
|
||||
|
||||
MOZ_CRASH("bad code range kind");
|
||||
|
|
|
@ -49,6 +49,12 @@ struct CallableOffsets;
|
|||
//
|
||||
// If you want to handle every kind of frames (including JS jit frames), use
|
||||
// JitFrameIter.
|
||||
//
|
||||
// The one exception is that this iterator may be called from the interrupt
|
||||
// callback which may be called asynchronously from asm.js code; in this case,
|
||||
// the backtrace may not be correct. That being said, we try our best printing
|
||||
// an informative message to the user and at least the name of the innermost
|
||||
// function stack frame.
|
||||
|
||||
class WasmFrameIter
|
||||
{
|
||||
|
@ -152,7 +158,7 @@ class ExitReason
|
|||
};
|
||||
|
||||
// Iterates over the frames of a single wasm JitActivation, given an
|
||||
// asynchronously-profiled thread's state.
|
||||
// asynchronously-interrupted thread's state.
|
||||
class ProfilingFrameIterator
|
||||
{
|
||||
const Code* code_;
|
||||
|
|
|
@ -550,6 +550,10 @@ ModuleGenerator::noteCodeRange(uint32_t codeRangeIndex, const CodeRange& codeRan
|
|||
MOZ_ASSERT(!linkDataTier_->unalignedAccessOffset);
|
||||
linkDataTier_->unalignedAccessOffset = codeRange.begin();
|
||||
break;
|
||||
case CodeRange::Interrupt:
|
||||
MOZ_ASSERT(!linkDataTier_->interruptOffset);
|
||||
linkDataTier_->interruptOffset = codeRange.begin();
|
||||
break;
|
||||
case CodeRange::TrapExit:
|
||||
MOZ_ASSERT(!linkDataTier_->trapOffset);
|
||||
linkDataTier_->trapOffset = codeRange.begin();
|
||||
|
|
|
@ -406,7 +406,7 @@ Instance::Instance(JSContext* cx,
|
|||
#endif
|
||||
tlsData()->instance = this;
|
||||
tlsData()->cx = cx;
|
||||
tlsData()->resetInterrupt(cx);
|
||||
tlsData()->stackLimit = cx->stackLimitForJitCode(JS::StackForUntrustedScript);
|
||||
tlsData()->jumpTable = code_->tieringJumpTable();
|
||||
|
||||
Tier callerTier = code_->bestTier();
|
||||
|
|
|
@ -251,6 +251,8 @@ class FunctionCompiler
|
|||
return false;
|
||||
}
|
||||
|
||||
addInterruptCheck();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1033,9 +1035,8 @@ class FunctionCompiler
|
|||
|
||||
void addInterruptCheck()
|
||||
{
|
||||
if (inDeadCode())
|
||||
return;
|
||||
curBlock_->add(MWasmInterruptCheck::New(alloc(), tlsPointer_, bytecodeOffset()));
|
||||
// We rely on signal handlers for interrupts on Asm.JS/Wasm
|
||||
MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers());
|
||||
}
|
||||
|
||||
MDefinition* extractSimdElement(unsigned lane, MDefinition* base, MIRType type, SimdSign sign)
|
||||
|
|
|
@ -42,6 +42,7 @@ struct CompileArgs;
|
|||
|
||||
struct LinkDataTierCacheablePod
|
||||
{
|
||||
uint32_t interruptOffset;
|
||||
uint32_t outOfBoundsOffset;
|
||||
uint32_t unalignedAccessOffset;
|
||||
uint32_t trapOffset;
|
||||
|
|
|
@ -50,14 +50,15 @@ class ProcessCodeSegmentMap
|
|||
CodeSegmentVector segments1_;
|
||||
CodeSegmentVector segments2_;
|
||||
|
||||
// Because of profiling, the thread running wasm might need to know to which
|
||||
// CodeSegment the current PC belongs, during a call to lookup(). A lookup
|
||||
// is a read-only operation, and we don't want to take a lock then
|
||||
// Because of sampling/interruptions/stack iteration in general, the
|
||||
// thread running wasm might need to know to which CodeSegment the
|
||||
// current PC belongs, during a call to lookup(). A lookup is a
|
||||
// read-only operation, and we don't want to take a lock then
|
||||
// (otherwise, we could have a deadlock situation if an async lookup
|
||||
// happened on a given thread that was holding mutatorsMutex_ while getting
|
||||
// sampled). Since the writer could be modifying the data that is getting
|
||||
// looked up, the writer functions use spin-locks to know if there are any
|
||||
// observers (i.e. calls to lookup()) of the atomic data.
|
||||
// interrupted/sampled). Since the writer could be modifying the data that
|
||||
// is getting looked up, the writer functions use spin-locks to know if
|
||||
// there are any observers (i.e. calls to lookup()) of the atomic data.
|
||||
|
||||
Atomic<size_t> observers_;
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ class CodeSegment;
|
|||
|
||||
// These methods return the wasm::CodeSegment (resp. wasm::Code) containing
|
||||
// the given pc, if any exist in the process. These methods do not take a lock,
|
||||
// and thus are safe to use in a profiling context.
|
||||
// and thus are safe to use in a profiling or async interrupt context.
|
||||
|
||||
const CodeSegment*
|
||||
LookupCodeSegment(const void* pc, const CodeRange** codeRange = nullptr);
|
||||
|
|
|
@ -31,32 +31,20 @@
|
|||
|
||||
#include "vm/ArrayBufferObject-inl.h"
|
||||
|
||||
#if defined(XP_WIN)
|
||||
# include "util/Windows.h"
|
||||
#else
|
||||
# include <signal.h>
|
||||
# include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
|
||||
# include <sys/ucontext.h> // for ucontext_t, mcontext_t
|
||||
#endif
|
||||
|
||||
#if defined(__x86_64__)
|
||||
# if defined(__DragonFly__)
|
||||
# include <machine/npx.h> // for union savefpu
|
||||
# elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
|
||||
defined(__NetBSD__) || defined(__OpenBSD__)
|
||||
# include <machine/fpu.h> // for struct savefpu/fxsave64
|
||||
# endif
|
||||
#endif
|
||||
|
||||
using namespace js;
|
||||
using namespace js::jit;
|
||||
using namespace js::wasm;
|
||||
|
||||
using JS::GenericNaN;
|
||||
using mozilla::DebugOnly;
|
||||
using mozilla::PodArrayZero;
|
||||
|
||||
#if defined(ANDROID)
|
||||
# include <sys/system_properties.h>
|
||||
# if defined(MOZ_LINKER)
|
||||
extern "C" MFBT_API bool IsSignalHandlingBroken();
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Crashing inside the signal handler can cause the handler to be recursively
|
||||
// invoked, eventually blowing the stack without actually showing a crash
|
||||
|
@ -269,20 +257,38 @@ struct AutoSignalHandler
|
|||
# define RFP_sig(p) ((p)->uc_mcontext.mc_regs[30])
|
||||
# endif
|
||||
#elif defined(XP_DARWIN)
|
||||
# define EIP_sig(p) ((p)->thread.uts.ts32.__eip)
|
||||
# define EBP_sig(p) ((p)->thread.uts.ts32.__ebp)
|
||||
# define ESP_sig(p) ((p)->thread.uts.ts32.__esp)
|
||||
# define RIP_sig(p) ((p)->thread.__rip)
|
||||
# define RBP_sig(p) ((p)->thread.__rbp)
|
||||
# define RSP_sig(p) ((p)->thread.__rsp)
|
||||
# define R11_sig(p) ((p)->thread.__r[11])
|
||||
# define R13_sig(p) ((p)->thread.__sp)
|
||||
# define R14_sig(p) ((p)->thread.__lr)
|
||||
# define R15_sig(p) ((p)->thread.__pc)
|
||||
# define EIP_sig(p) ((p)->uc_mcontext->__ss.__eip)
|
||||
# define EBP_sig(p) ((p)->uc_mcontext->__ss.__ebp)
|
||||
# define ESP_sig(p) ((p)->uc_mcontext->__ss.__esp)
|
||||
# define RIP_sig(p) ((p)->uc_mcontext->__ss.__rip)
|
||||
# define RBP_sig(p) ((p)->uc_mcontext->__ss.__rbp)
|
||||
# define RSP_sig(p) ((p)->uc_mcontext->__ss.__rsp)
|
||||
# define R14_sig(p) ((p)->uc_mcontext->__ss.__lr)
|
||||
# define R15_sig(p) ((p)->uc_mcontext->__ss.__pc)
|
||||
#else
|
||||
# error "Don't know how to read/write to the thread state via the mcontext_t."
|
||||
#endif
|
||||
|
||||
#if defined(XP_WIN)
|
||||
# include "util/Windows.h"
|
||||
#else
|
||||
# include <signal.h>
|
||||
# include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
|
||||
# include <sys/ucontext.h> // for ucontext_t, mcontext_t
|
||||
#endif
|
||||
|
||||
#if defined(__x86_64__)
|
||||
# if defined(__DragonFly__)
|
||||
# include <machine/npx.h> // for union savefpu
|
||||
# elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
|
||||
defined(__NetBSD__) || defined(__OpenBSD__)
|
||||
# include <machine/fpu.h> // for struct savefpu/fxsave64
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(ANDROID)
|
||||
// Not all versions of the Android NDK define ucontext_t or mcontext_t.
|
||||
// Detect this and provide custom but compatible definitions. Note that these
|
||||
|
@ -363,30 +369,38 @@ enum { REG_EIP = 14 };
|
|||
# endif // !defined(__BIONIC_HAVE_UCONTEXT_T)
|
||||
#endif // defined(ANDROID)
|
||||
|
||||
#if !defined(XP_WIN)
|
||||
# define CONTEXT ucontext_t
|
||||
#endif
|
||||
|
||||
// Define a context type for use in the emulator code. This is usually just
|
||||
// the same as CONTEXT, but on Mac we use a different structure since we call
|
||||
// into the emulator code from a Mach exception handler rather than a
|
||||
// sigaction-style signal handler.
|
||||
#if defined(XP_DARWIN)
|
||||
# if defined(__x86_64__)
|
||||
struct macos_x64_context {
|
||||
x86_thread_state64_t thread;
|
||||
x86_float_state64_t float_;
|
||||
};
|
||||
# define CONTEXT macos_x64_context
|
||||
# define EMULATOR_CONTEXT macos_x64_context
|
||||
# elif defined(__i386__)
|
||||
struct macos_x86_context {
|
||||
x86_thread_state_t thread;
|
||||
x86_float_state_t float_;
|
||||
};
|
||||
# define CONTEXT macos_x86_context
|
||||
# define EMULATOR_CONTEXT macos_x86_context
|
||||
# elif defined(__arm__)
|
||||
struct macos_arm_context {
|
||||
arm_thread_state_t thread;
|
||||
arm_neon_state_t float_;
|
||||
};
|
||||
# define CONTEXT macos_arm_context
|
||||
# define EMULATOR_CONTEXT macos_arm_context
|
||||
# else
|
||||
# error Unsupported architecture
|
||||
# endif
|
||||
#elif !defined(XP_WIN)
|
||||
# define CONTEXT ucontext_t
|
||||
#else
|
||||
# define EMULATOR_CONTEXT CONTEXT
|
||||
#endif
|
||||
|
||||
#if defined(_M_X64) || defined(__x86_64__)
|
||||
|
@ -414,10 +428,14 @@ struct macos_arm_context {
|
|||
# define LR_sig(p) R31_sig(p)
|
||||
#endif
|
||||
|
||||
#if defined(PC_sig) && defined(FP_sig) && defined(SP_sig)
|
||||
# define KNOWS_MACHINE_STATE
|
||||
#endif
|
||||
|
||||
static uint8_t**
|
||||
ContextToPC(CONTEXT* context)
|
||||
{
|
||||
#ifdef PC_sig
|
||||
#ifdef KNOWS_MACHINE_STATE
|
||||
return reinterpret_cast<uint8_t**>(&PC_sig(context));
|
||||
#else
|
||||
MOZ_CRASH();
|
||||
|
@ -427,49 +445,117 @@ ContextToPC(CONTEXT* context)
|
|||
static uint8_t*
|
||||
ContextToFP(CONTEXT* context)
|
||||
{
|
||||
#ifdef FP_sig
|
||||
#ifdef KNOWS_MACHINE_STATE
|
||||
return reinterpret_cast<uint8_t*>(FP_sig(context));
|
||||
#else
|
||||
MOZ_CRASH();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef KNOWS_MACHINE_STATE
|
||||
static uint8_t*
|
||||
ContextToSP(CONTEXT* context)
|
||||
{
|
||||
#ifdef SP_sig
|
||||
return reinterpret_cast<uint8_t*>(SP_sig(context));
|
||||
#else
|
||||
MOZ_CRASH();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(__arm__) || defined(__aarch64__) || defined(__mips__)
|
||||
# if defined(__arm__) || defined(__aarch64__) || defined(__mips__)
|
||||
static uint8_t*
|
||||
ContextToLR(CONTEXT* context)
|
||||
{
|
||||
# ifdef LR_sig
|
||||
return reinterpret_cast<uint8_t*>(LR_sig(context));
|
||||
}
|
||||
# endif
|
||||
#endif // KNOWS_MACHINE_STATE
|
||||
|
||||
#if defined(XP_DARWIN)
|
||||
|
||||
static uint8_t**
|
||||
ContextToPC(EMULATOR_CONTEXT* context)
|
||||
{
|
||||
# if defined(__x86_64__)
|
||||
static_assert(sizeof(context->thread.__rip) == sizeof(void*),
|
||||
"stored IP should be compile-time pointer-sized");
|
||||
return reinterpret_cast<uint8_t**>(&context->thread.__rip);
|
||||
# elif defined(__i386__)
|
||||
static_assert(sizeof(context->thread.uts.ts32.__eip) == sizeof(void*),
|
||||
"stored IP should be compile-time pointer-sized");
|
||||
return reinterpret_cast<uint8_t**>(&context->thread.uts.ts32.__eip);
|
||||
# elif defined(__arm__)
|
||||
static_assert(sizeof(context->thread.__pc) == sizeof(void*),
|
||||
"stored IP should be compile-time pointer-sized");
|
||||
return reinterpret_cast<uint8_t**>(&context->thread.__pc);
|
||||
# else
|
||||
MOZ_CRASH();
|
||||
# error Unsupported architecture
|
||||
# endif
|
||||
}
|
||||
|
||||
static uint8_t*
|
||||
ContextToFP(EMULATOR_CONTEXT* context)
|
||||
{
|
||||
# if defined(__x86_64__)
|
||||
return (uint8_t*)context->thread.__rbp;
|
||||
# elif defined(__i386__)
|
||||
return (uint8_t*)context->thread.uts.ts32.__ebp;
|
||||
# elif defined(__arm__)
|
||||
return (uint8_t*)context->thread.__r[11];
|
||||
# else
|
||||
# error Unsupported architecture
|
||||
# endif
|
||||
}
|
||||
|
||||
# if defined(__arm__) || defined(__aarch64__)
|
||||
static uint8_t*
|
||||
ContextToLR(EMULATOR_CONTEXT* context)
|
||||
{
|
||||
return (uint8_t*)context->thread.__lr;
|
||||
}
|
||||
# endif
|
||||
|
||||
static uint8_t*
|
||||
ContextToSP(EMULATOR_CONTEXT* context)
|
||||
{
|
||||
# if defined(__x86_64__)
|
||||
return (uint8_t*)context->thread.__rsp;
|
||||
# elif defined(__i386__)
|
||||
return (uint8_t*)context->thread.uts.ts32.__esp;
|
||||
# elif defined(__arm__)
|
||||
return (uint8_t*)context->thread.__sp;
|
||||
# else
|
||||
# error Unsupported architecture
|
||||
# endif
|
||||
}
|
||||
#endif
|
||||
|
||||
static JS::ProfilingFrameIterator::RegisterState
|
||||
ToRegisterState(CONTEXT* context)
|
||||
ToRegisterState(EMULATOR_CONTEXT* context)
|
||||
{
|
||||
JS::ProfilingFrameIterator::RegisterState state;
|
||||
state.fp = ContextToFP(context);
|
||||
state.pc = *ContextToPC(context);
|
||||
state.sp = ContextToSP(context);
|
||||
#if defined(__arm__) || defined(__aarch64__) || defined(__mips__)
|
||||
# if defined(__arm__) || defined(__aarch64__)
|
||||
state.lr = ContextToLR(context);
|
||||
#else
|
||||
state.lr = (void*)UINTPTR_MAX;
|
||||
#endif
|
||||
# endif
|
||||
return state;
|
||||
}
|
||||
#endif // XP_DARWIN
|
||||
|
||||
static JS::ProfilingFrameIterator::RegisterState
|
||||
ToRegisterState(CONTEXT* context)
|
||||
{
|
||||
#ifdef KNOWS_MACHINE_STATE
|
||||
JS::ProfilingFrameIterator::RegisterState state;
|
||||
state.fp = ContextToFP(context);
|
||||
state.pc = *ContextToPC(context);
|
||||
state.sp = ContextToSP(context);
|
||||
# if defined(__arm__) || defined(__aarch64__) || defined(__mips__)
|
||||
state.lr = ContextToLR(context);
|
||||
# endif
|
||||
return state;
|
||||
#else
|
||||
MOZ_CRASH();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(WASM_HUGE_MEMORY)
|
||||
MOZ_COLD static void
|
||||
|
@ -567,7 +653,7 @@ AddressOfFPRegisterSlot(CONTEXT* context, FloatRegisters::Encoding encoding)
|
|||
}
|
||||
|
||||
MOZ_COLD static void*
|
||||
AddressOfGPRegisterSlot(CONTEXT* context, Registers::Code code)
|
||||
AddressOfGPRegisterSlot(EMULATOR_CONTEXT* context, Registers::Code code)
|
||||
{
|
||||
switch (code) {
|
||||
case X86Encoding::rax: return &RAX_sig(context);
|
||||
|
@ -592,7 +678,7 @@ AddressOfGPRegisterSlot(CONTEXT* context, Registers::Code code)
|
|||
}
|
||||
# else
|
||||
MOZ_COLD static void*
|
||||
AddressOfFPRegisterSlot(CONTEXT* context, FloatRegisters::Encoding encoding)
|
||||
AddressOfFPRegisterSlot(EMULATOR_CONTEXT* context, FloatRegisters::Encoding encoding)
|
||||
{
|
||||
switch (encoding) {
|
||||
case X86Encoding::xmm0: return &context->float_.__fpu_xmm0;
|
||||
|
@ -617,7 +703,7 @@ AddressOfFPRegisterSlot(CONTEXT* context, FloatRegisters::Encoding encoding)
|
|||
}
|
||||
|
||||
MOZ_COLD static void*
|
||||
AddressOfGPRegisterSlot(CONTEXT* context, Registers::Code code)
|
||||
AddressOfGPRegisterSlot(EMULATOR_CONTEXT* context, Registers::Code code)
|
||||
{
|
||||
switch (code) {
|
||||
case X86Encoding::rax: return &context->thread.__rax;
|
||||
|
@ -643,20 +729,20 @@ AddressOfGPRegisterSlot(CONTEXT* context, Registers::Code code)
|
|||
# endif // !XP_DARWIN
|
||||
#elif defined(JS_CODEGEN_ARM64)
|
||||
MOZ_COLD static void*
|
||||
AddressOfFPRegisterSlot(CONTEXT* context, FloatRegisters::Encoding encoding)
|
||||
AddressOfFPRegisterSlot(EMULATOR_CONTEXT* context, FloatRegisters::Encoding encoding)
|
||||
{
|
||||
MOZ_CRASH("NYI - asm.js not supported yet on this platform");
|
||||
}
|
||||
|
||||
MOZ_COLD static void*
|
||||
AddressOfGPRegisterSlot(CONTEXT* context, Registers::Code code)
|
||||
AddressOfGPRegisterSlot(EMULATOR_CONTEXT* context, Registers::Code code)
|
||||
{
|
||||
MOZ_CRASH("NYI - asm.js not supported yet on this platform");
|
||||
}
|
||||
#endif
|
||||
|
||||
MOZ_COLD static void
|
||||
SetRegisterToCoercedUndefined(CONTEXT* context, size_t size,
|
||||
SetRegisterToCoercedUndefined(EMULATOR_CONTEXT* context, size_t size,
|
||||
const Disassembler::OtherOperand& value)
|
||||
{
|
||||
if (value.kind() == Disassembler::OtherOperand::FPR)
|
||||
|
@ -666,7 +752,7 @@ SetRegisterToCoercedUndefined(CONTEXT* context, size_t size,
|
|||
}
|
||||
|
||||
MOZ_COLD static void
|
||||
SetRegisterToLoadedValue(CONTEXT* context, SharedMem<void*> addr, size_t size,
|
||||
SetRegisterToLoadedValue(EMULATOR_CONTEXT* context, SharedMem<void*> addr, size_t size,
|
||||
const Disassembler::OtherOperand& value)
|
||||
{
|
||||
if (value.kind() == Disassembler::OtherOperand::FPR)
|
||||
|
@ -676,14 +762,14 @@ SetRegisterToLoadedValue(CONTEXT* context, SharedMem<void*> addr, size_t size,
|
|||
}
|
||||
|
||||
MOZ_COLD static void
|
||||
SetRegisterToLoadedValueSext32(CONTEXT* context, SharedMem<void*> addr, size_t size,
|
||||
SetRegisterToLoadedValueSext32(EMULATOR_CONTEXT* context, SharedMem<void*> addr, size_t size,
|
||||
const Disassembler::OtherOperand& value)
|
||||
{
|
||||
SetGPRegToLoadedValueSext32(addr, size, AddressOfGPRegisterSlot(context, value.gpr()));
|
||||
}
|
||||
|
||||
MOZ_COLD static void
|
||||
StoreValueFromRegister(CONTEXT* context, SharedMem<void*> addr, size_t size,
|
||||
StoreValueFromRegister(EMULATOR_CONTEXT* context, SharedMem<void*> addr, size_t size,
|
||||
const Disassembler::OtherOperand& value)
|
||||
{
|
||||
if (value.kind() == Disassembler::OtherOperand::FPR)
|
||||
|
@ -695,7 +781,7 @@ StoreValueFromRegister(CONTEXT* context, SharedMem<void*> addr, size_t size,
|
|||
}
|
||||
|
||||
MOZ_COLD static uint8_t*
|
||||
ComputeAccessAddress(CONTEXT* context, const Disassembler::ComplexAddress& address)
|
||||
ComputeAccessAddress(EMULATOR_CONTEXT* context, const Disassembler::ComplexAddress& address)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(!address.isPCRelative(), "PC-relative addresses not supported yet");
|
||||
|
||||
|
@ -720,7 +806,7 @@ ComputeAccessAddress(CONTEXT* context, const Disassembler::ComplexAddress& addre
|
|||
}
|
||||
|
||||
MOZ_COLD static void
|
||||
HandleMemoryAccess(CONTEXT* context, uint8_t* pc, uint8_t* faultingAddress,
|
||||
HandleMemoryAccess(EMULATOR_CONTEXT* context, uint8_t* pc, uint8_t* faultingAddress,
|
||||
const ModuleSegment* segment, const Instance& instance, JitActivation* activation,
|
||||
uint8_t** ppc)
|
||||
{
|
||||
|
@ -732,7 +818,7 @@ HandleMemoryAccess(CONTEXT* context, uint8_t* pc, uint8_t* faultingAddress,
|
|||
// experimental SIMD.js or Atomics. When these are converted to
|
||||
// non-experimental wasm features, this case, as well as outOfBoundsCode,
|
||||
// can be removed.
|
||||
activation->startWasmTrap(wasm::Trap::OutOfBounds, 0, ToRegisterState(context));
|
||||
MOZ_ALWAYS_TRUE(activation->startWasmInterrupt(ToRegisterState(context)));
|
||||
*ppc = segment->outOfBoundsCode();
|
||||
return;
|
||||
}
|
||||
|
@ -876,7 +962,7 @@ HandleMemoryAccess(CONTEXT* context, uint8_t* pc, uint8_t* faultingAddress,
|
|||
#else // WASM_HUGE_MEMORY
|
||||
|
||||
MOZ_COLD static void
|
||||
HandleMemoryAccess(CONTEXT* context, uint8_t* pc, uint8_t* faultingAddress,
|
||||
HandleMemoryAccess(EMULATOR_CONTEXT* context, uint8_t* pc, uint8_t* faultingAddress,
|
||||
const ModuleSegment* segment, const Instance& instance, JitActivation* activation,
|
||||
uint8_t** ppc)
|
||||
{
|
||||
|
@ -885,7 +971,7 @@ HandleMemoryAccess(CONTEXT* context, uint8_t* pc, uint8_t* faultingAddress,
|
|||
const MemoryAccess* memoryAccess = instance.code().lookupMemoryAccess(pc);
|
||||
if (!memoryAccess) {
|
||||
// See explanation in the WASM_HUGE_MEMORY HandleMemoryAccess.
|
||||
activation->startWasmTrap(wasm::Trap::OutOfBounds, 0, ToRegisterState(context));
|
||||
MOZ_ALWAYS_TRUE(activation->startWasmInterrupt(ToRegisterState(context)));
|
||||
*ppc = segment->outOfBoundsCode();
|
||||
return;
|
||||
}
|
||||
|
@ -933,8 +1019,33 @@ HandleFault(PEXCEPTION_POINTERS exception)
|
|||
MOZ_ASSERT(activation);
|
||||
|
||||
const Instance* instance = LookupFaultingInstance(*moduleSegment, pc, ContextToFP(context));
|
||||
if (!instance)
|
||||
return false;
|
||||
if (!instance) {
|
||||
// On Windows, it is possible for InterruptRunningJitCode to execute
|
||||
// between a faulting instruction and the handling of the fault due
|
||||
// to InterruptRunningJitCode's use of SuspendThread. When this happens,
|
||||
// after ResumeThread, the exception handler is called with pc equal to
|
||||
// ModuleSegment.interrupt, which is logically wrong. The Right Thing would
|
||||
// be for the OS to make fault-handling atomic (so that CONTEXT.pc was
|
||||
// always the logically-faulting pc). Fortunately, we can detect this
|
||||
// case and silence the exception ourselves (the exception will
|
||||
// retrigger after the interrupt jumps back to resumePC).
|
||||
return activation->isWasmInterrupted() &&
|
||||
pc == moduleSegment->interruptCode() &&
|
||||
moduleSegment->containsCodePC(activation->wasmInterruptResumePC());
|
||||
}
|
||||
|
||||
// In the same race-with-interrupt situation above, it's *also* possible
|
||||
// that the reported 'pc' is the pre-interrupt pc, not post-interrupt
|
||||
// moduleSegment->interruptCode (this may be windows-version-specific). In
|
||||
// this case, lookupTrap()/lookupMemoryAccess() will all succeed causing the
|
||||
// pc to be redirected *again* (to a trap stub), leading to the interrupt
|
||||
// stub never being called. Since the goal of the async interrupt is to break
|
||||
// out iloops and trapping does just that, this is fine, we just clear the
|
||||
// "interrupted" state.
|
||||
if (activation->isWasmInterrupted()) {
|
||||
MOZ_ASSERT(activation->wasmInterruptResumePC() == pc);
|
||||
activation->finishWasmInterrupt();
|
||||
}
|
||||
|
||||
if (record->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) {
|
||||
Trap trap;
|
||||
|
@ -1014,7 +1125,7 @@ HandleMachException(JSContext* cx, const ExceptionRequest& request)
|
|||
mach_port_t cxThread = request.body.thread.name;
|
||||
|
||||
// Read out the JSRuntime thread's register state.
|
||||
CONTEXT context;
|
||||
EMULATOR_CONTEXT context;
|
||||
# if defined(__x86_64__)
|
||||
unsigned int thread_state_count = x86_THREAD_STATE64_COUNT;
|
||||
unsigned int float_state_count = x86_FLOAT_STATE64_COUNT;
|
||||
|
@ -1335,7 +1446,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->startWasmTrap(wasm::Trap::UnalignedAccess, 0, ToRegisterState(context));
|
||||
MOZ_ALWAYS_TRUE(activation->startWasmInterrupt(ToRegisterState(context)));
|
||||
*ppc = moduleSegment->unalignedAccessCode();
|
||||
return true;
|
||||
}
|
||||
|
@ -1384,8 +1495,119 @@ WasmFaultHandler(int signum, siginfo_t* info, void* context)
|
|||
}
|
||||
# endif // XP_WIN || XP_DARWIN || assume unix
|
||||
|
||||
#if defined(ANDROID) && defined(MOZ_LINKER)
|
||||
extern "C" MFBT_API bool IsSignalHandlingBroken();
|
||||
static void
|
||||
RedirectIonBackedgesToInterruptCheck(JSContext* cx)
|
||||
{
|
||||
if (!cx->runtime()->hasJitRuntime())
|
||||
return;
|
||||
jit::JitRuntime* jitRuntime = cx->runtime()->jitRuntime();
|
||||
Zone* zone = cx->zoneRaw();
|
||||
if (zone && !zone->isAtomsZone()) {
|
||||
// If the backedge list is being mutated, the pc must be in C++ code and
|
||||
// thus not in a JIT iloop. We assume that the interrupt flag will be
|
||||
// checked at least once before entering JIT code (if not, no big deal;
|
||||
// the browser will just request another interrupt in a second).
|
||||
if (!jitRuntime->preventBackedgePatching()) {
|
||||
jit::JitZoneGroup* jzg = zone->group()->jitZoneGroup;
|
||||
jzg->patchIonBackedges(cx, jit::JitZoneGroup::BackedgeInterruptCheck);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
wasm::InInterruptibleCode(JSContext* cx, uint8_t* pc, const ModuleSegment** ms)
|
||||
{
|
||||
// Only interrupt in function code so that the frame iterators have the
|
||||
// invariant that resumePC always has a function CodeRange and we can't
|
||||
// get into any weird interrupt-during-interrupt-stub cases.
|
||||
|
||||
if (!cx->compartment())
|
||||
return false;
|
||||
|
||||
const CodeSegment* cs = LookupCodeSegment(pc);
|
||||
if (!cs || !cs->isModule())
|
||||
return false;
|
||||
|
||||
*ms = cs->asModule();
|
||||
return !!(*ms)->code().lookupFuncRange(pc);
|
||||
}
|
||||
|
||||
// The return value indicates whether the PC was changed, not whether there was
|
||||
// a failure.
|
||||
static bool
|
||||
RedirectJitCodeToInterruptCheck(JSContext* cx, CONTEXT* context)
|
||||
{
|
||||
// Jitcode may only be modified on the runtime's active thread.
|
||||
if (cx != cx->runtime()->activeContext())
|
||||
return false;
|
||||
|
||||
// The faulting thread is suspended so we can access cx fields that can
|
||||
// normally only be accessed by the cx's active thread.
|
||||
AutoNoteSingleThreadedRegion anstr;
|
||||
|
||||
RedirectIonBackedgesToInterruptCheck(cx);
|
||||
|
||||
#ifdef JS_SIMULATOR
|
||||
uint8_t* pc = cx->simulator()->get_pc_as<uint8_t*>();
|
||||
#else
|
||||
uint8_t* pc = *ContextToPC(context);
|
||||
#endif
|
||||
|
||||
const ModuleSegment* moduleSegment = nullptr;
|
||||
if (!InInterruptibleCode(cx, pc, &moduleSegment))
|
||||
return false;
|
||||
|
||||
#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();
|
||||
|
||||
// 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->isWasmInterrupted())
|
||||
return false;
|
||||
|
||||
if (!activation->startWasmInterrupt(ToRegisterState(context)))
|
||||
return false;
|
||||
|
||||
*ContextToPC(context) = moduleSegment->interruptCode();
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if !defined(XP_WIN)
|
||||
// For the interrupt signal, pick a signal number that:
|
||||
// - is not otherwise used by mozilla or standard libraries
|
||||
// - defaults to nostop and noprint on gdb/lldb so that noone is bothered
|
||||
// SIGVTALRM a relative of SIGALRM, so intended for user code, but, unlike
|
||||
// SIGALRM, not used anywhere else in Mozilla.
|
||||
static const int sInterruptSignal = SIGVTALRM;
|
||||
|
||||
static void
|
||||
JitInterruptHandler(int signum, siginfo_t* info, void* context)
|
||||
{
|
||||
if (JSContext* cx = TlsContext.get()) {
|
||||
|
||||
#if defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS32) || defined(JS_SIMULATOR_MIPS64)
|
||||
SimulatorProcess::ICacheCheckingDisableCount++;
|
||||
#endif
|
||||
|
||||
RedirectJitCodeToInterruptCheck(cx, (CONTEXT*)context);
|
||||
|
||||
#if defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS32) || defined(JS_SIMULATOR_MIPS64)
|
||||
SimulatorProcess::cacheInvalidatedBySignalHandler_ = true;
|
||||
SimulatorProcess::ICacheCheckingDisableCount--;
|
||||
#endif
|
||||
|
||||
cx->finishHandlingJitInterrupt();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool sTriedInstallSignalHandlers = false;
|
||||
|
@ -1399,35 +1621,73 @@ ProcessHasSignalHandlers()
|
|||
return sHaveSignalHandlers;
|
||||
sTriedInstallSignalHandlers = true;
|
||||
|
||||
#if defined(ANDROID) && defined(MOZ_LINKER)
|
||||
#if defined(ANDROID)
|
||||
# if !defined(__aarch64__)
|
||||
// Before Android 4.4 (SDK version 19), there is a bug
|
||||
// https://android-review.googlesource.com/#/c/52333
|
||||
// in Bionic's pthread_join which causes pthread_join to return early when
|
||||
// pthread_kill is used (on any thread). Nobody expects the pthread_cond_wait
|
||||
// EINTRquisition.
|
||||
char version_string[PROP_VALUE_MAX];
|
||||
PodArrayZero(version_string);
|
||||
if (__system_property_get("ro.build.version.sdk", version_string) > 0) {
|
||||
if (atol(version_string) < 19)
|
||||
return false;
|
||||
}
|
||||
# endif
|
||||
# if defined(MOZ_LINKER)
|
||||
// Signal handling is broken on some android systems.
|
||||
if (IsSignalHandlingBroken())
|
||||
return false;
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// The interrupt handler allows the active thread to be paused from another
|
||||
// thread (see InterruptRunningJitCode).
|
||||
#if defined(XP_WIN)
|
||||
// Windows uses SuspendThread to stop the active thread from another thread.
|
||||
#else
|
||||
struct sigaction interruptHandler;
|
||||
interruptHandler.sa_flags = SA_SIGINFO;
|
||||
interruptHandler.sa_sigaction = &JitInterruptHandler;
|
||||
sigemptyset(&interruptHandler.sa_mask);
|
||||
struct sigaction prev;
|
||||
if (sigaction(sInterruptSignal, &interruptHandler, &prev))
|
||||
MOZ_CRASH("unable to install interrupt handler");
|
||||
|
||||
// There shouldn't be any other handlers installed for sInterruptSignal. If
|
||||
// there are, we could always forward, but we need to understand what we're
|
||||
// doing to avoid problematic interference.
|
||||
if ((prev.sa_flags & SA_SIGINFO && prev.sa_sigaction) ||
|
||||
(prev.sa_handler != SIG_DFL && prev.sa_handler != SIG_IGN))
|
||||
{
|
||||
MOZ_CRASH("contention for interrupt signal");
|
||||
}
|
||||
#endif // defined(XP_WIN)
|
||||
|
||||
// Initalize ThreadLocal flag used by WasmFaultHandler
|
||||
sAlreadyInSignalHandler.infallibleInit();
|
||||
|
||||
// Install a SIGSEGV handler to handle safely-out-of-bounds asm.js heap
|
||||
// access and/or unaligned accesses.
|
||||
#if defined(XP_WIN)
|
||||
# if defined(MOZ_ASAN)
|
||||
# if defined(XP_WIN)
|
||||
# if defined(MOZ_ASAN)
|
||||
// Under ASan we need to let the ASan runtime's ShadowExceptionHandler stay
|
||||
// in the first handler position. This requires some coordination with
|
||||
// MemoryProtectionExceptionHandler::isDisabled().
|
||||
const bool firstHandler = false;
|
||||
# else
|
||||
# else
|
||||
// Otherwise, WasmFaultHandler needs to go first, so that we can recover
|
||||
// from wasm faults and continue execution without triggering handlers
|
||||
// such as MemoryProtectionExceptionHandler that assume we are crashing.
|
||||
const bool firstHandler = true;
|
||||
# endif
|
||||
# endif
|
||||
if (!AddVectoredExceptionHandler(firstHandler, WasmFaultHandler))
|
||||
return false;
|
||||
#elif defined(XP_DARWIN)
|
||||
# elif defined(XP_DARWIN)
|
||||
// OSX handles seg faults via the Mach exception handler above, so don't
|
||||
// install WasmFaultHandler.
|
||||
#else
|
||||
# else
|
||||
// SA_NODEFER allows us to reenter the signal handler if we crash while
|
||||
// handling the signal, and fall through to the Breakpad handler by testing
|
||||
// handlingSegFault.
|
||||
|
@ -1440,7 +1700,7 @@ ProcessHasSignalHandlers()
|
|||
if (sigaction(SIGSEGV, &faultHandler, &sPrevSEGVHandler))
|
||||
MOZ_CRASH("unable to install segv handler");
|
||||
|
||||
# if defined(JS_CODEGEN_ARM)
|
||||
# if defined(JS_CODEGEN_ARM)
|
||||
// On Arm Handle Unaligned Accesses
|
||||
struct sigaction busHandler;
|
||||
busHandler.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK;
|
||||
|
@ -1448,7 +1708,7 @@ ProcessHasSignalHandlers()
|
|||
sigemptyset(&busHandler.sa_mask);
|
||||
if (sigaction(SIGBUS, &busHandler, &sPrevSIGBUSHandler))
|
||||
MOZ_CRASH("unable to install sigbus handler");
|
||||
# endif
|
||||
# endif
|
||||
|
||||
// Install a handler to handle the instructions that are emitted to implement
|
||||
// wasm traps.
|
||||
|
@ -1458,7 +1718,7 @@ ProcessHasSignalHandlers()
|
|||
sigemptyset(&wasmTrapHandler.sa_mask);
|
||||
if (sigaction(kWasmTrapSignal, &wasmTrapHandler, &sPrevWasmTrapHandler))
|
||||
MOZ_CRASH("unable to install wasm trap handler");
|
||||
#endif
|
||||
# endif
|
||||
|
||||
sHaveSignalHandlers = true;
|
||||
return true;
|
||||
|
@ -1486,3 +1746,61 @@ wasm::HaveSignalHandlers()
|
|||
MOZ_ASSERT(sTriedInstallSignalHandlers);
|
||||
return sHaveSignalHandlers;
|
||||
}
|
||||
|
||||
// JSRuntime::requestInterrupt sets interrupt_ (which is checked frequently by
|
||||
// C++ code at every Baseline JIT loop backedge) and jitStackLimit_ (which is
|
||||
// checked at every Baseline and Ion JIT function prologue). The remaining
|
||||
// sources of potential iloops (Ion loop backedges and all wasm code) are
|
||||
// handled by this function:
|
||||
// 1. Ion loop backedges are patched to instead point to a stub that handles
|
||||
// the interrupt;
|
||||
// 2. if the active thread's pc is inside wasm code, the pc is updated to point
|
||||
// to a stub that handles the interrupt.
|
||||
void
|
||||
js::InterruptRunningJitCode(JSContext* cx)
|
||||
{
|
||||
// If signal handlers weren't installed, then Ion and wasm emit normal
|
||||
// interrupt checks and don't need asynchronous interruption.
|
||||
if (!HaveSignalHandlers())
|
||||
return;
|
||||
|
||||
// Do nothing if we're already handling an interrupt here, to avoid races
|
||||
// below and in JitRuntime::patchIonBackedges.
|
||||
if (!cx->startHandlingJitInterrupt())
|
||||
return;
|
||||
|
||||
// If we are on context's thread, then: pc is not in wasm code (so nothing
|
||||
// to do for wasm) and we can patch Ion backedges without any special
|
||||
// synchronization.
|
||||
if (cx == TlsContext.get()) {
|
||||
RedirectIonBackedgesToInterruptCheck(cx);
|
||||
cx->finishHandlingJitInterrupt();
|
||||
return;
|
||||
}
|
||||
|
||||
// We are not on the runtime's active thread, so to do 1 and 2 above, we need
|
||||
// to halt the runtime's active thread first.
|
||||
#if defined(XP_WIN)
|
||||
// On Windows, we can simply suspend the active thread and work directly on
|
||||
// its context from this thread. SuspendThread can sporadically fail if the
|
||||
// thread is in the middle of a syscall. Rather than retrying in a loop,
|
||||
// just wait for the next request for interrupt.
|
||||
HANDLE thread = (HANDLE)cx->threadNative();
|
||||
if (SuspendThread(thread) != (DWORD)-1) {
|
||||
CONTEXT context;
|
||||
context.ContextFlags = CONTEXT_FULL;
|
||||
if (GetThreadContext(thread, &context)) {
|
||||
if (RedirectJitCodeToInterruptCheck(cx, &context))
|
||||
SetThreadContext(thread, &context);
|
||||
}
|
||||
ResumeThread(thread);
|
||||
}
|
||||
cx->finishHandlingJitInterrupt();
|
||||
#else
|
||||
// On Unix, we instead deliver an async signal to the active thread which
|
||||
// halts the thread and callers our JitInterruptHandler (which has already
|
||||
// been installed by EnsureSignalHandlersInstalled).
|
||||
pthread_t thread = (pthread_t)cx->threadNative();
|
||||
pthread_kill(thread, sInterruptSignal);
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -30,6 +30,11 @@
|
|||
#include "wasm/WasmTypes.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
// Force any currently-executing asm.js/ion code to call HandleExecutionInterrupt.
|
||||
extern void
|
||||
InterruptRunningJitCode(JSContext* cx);
|
||||
|
||||
namespace wasm {
|
||||
|
||||
// Ensure the given JSRuntime is set up to use signals. Failure to enable signal
|
||||
|
@ -38,11 +43,18 @@ namespace wasm {
|
|||
MOZ_MUST_USE bool
|
||||
EnsureSignalHandlers(JSContext* cx);
|
||||
|
||||
// Return whether signals can be used in this process for asm.js/wasm
|
||||
// out-of-bounds.
|
||||
// Return whether signals can be used in this process for interrupts or
|
||||
// asm.js/wasm out-of-bounds.
|
||||
bool
|
||||
HaveSignalHandlers();
|
||||
|
||||
class ModuleSegment;
|
||||
|
||||
// Returns true if wasm code is on top of the activation stack (and fills out
|
||||
// the code segment outparam in this case), or false otherwise.
|
||||
bool
|
||||
InInterruptibleCode(JSContext* cx, uint8_t* pc, const ModuleSegment** ms);
|
||||
|
||||
#if defined(XP_DARWIN)
|
||||
// On OSX we are forced to use the lower-level Mach exception mechanism instead
|
||||
// of Unix signals. Mach exceptions are not handled on the victim's stack but
|
||||
|
@ -67,14 +79,33 @@ class MachExceptionHandler
|
|||
};
|
||||
#endif
|
||||
|
||||
// On trap, the bytecode offset to be reported in callstacks is saved.
|
||||
// Typed wrappers encapsulating the data saved by the signal handler on async
|
||||
// interrupt or trap. On interrupt, the PC at which to resume is saved. On trap,
|
||||
// the bytecode offset to be reported in callstacks is saved.
|
||||
|
||||
struct InterruptData
|
||||
{
|
||||
// The pc to use for unwinding purposes which is kept consistent with fp at
|
||||
// call boundaries.
|
||||
void* unwindPC;
|
||||
|
||||
// The pc at which we should return if the interrupt doesn't stop execution.
|
||||
void* resumePC;
|
||||
|
||||
InterruptData(void* unwindPC, void* resumePC)
|
||||
: unwindPC(unwindPC), resumePC(resumePC)
|
||||
{}
|
||||
};
|
||||
|
||||
struct TrapData
|
||||
{
|
||||
void* resumePC;
|
||||
void* unwoundPC;
|
||||
void* pc;
|
||||
Trap trap;
|
||||
uint32_t bytecodeOffset;
|
||||
|
||||
TrapData(void* pc, Trap trap, uint32_t bytecodeOffset)
|
||||
: pc(pc), trap(trap), bytecodeOffset(bytecodeOffset)
|
||||
{}
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
|
|
|
@ -1130,6 +1130,8 @@ GenerateImportJitExit(MacroAssembler& masm, const FuncImport& fi, Label* throwLa
|
|||
// need to worry about rooting anymore.
|
||||
// - or the value needs to be rooted, but nothing can cause a GC between
|
||||
// here and CoerceInPlace, which roots before coercing to a primitive.
|
||||
// In particular, this is true because wasm::InInterruptibleCode will
|
||||
// return false when PC is in the jit exit.
|
||||
|
||||
// The JIT callee clobbers all registers, including WasmTlsReg and
|
||||
// FramePointer, so restore those here. During this sequence of
|
||||
|
@ -1358,26 +1360,6 @@ wasm::GenerateBuiltinThunk(MacroAssembler& masm, ABIFunctionType abiType, ExitRe
|
|||
return FinishOffsets(masm, offsets);
|
||||
}
|
||||
|
||||
#if defined(JS_CODEGEN_ARM)
|
||||
static const LiveRegisterSet RegsToPreserve(
|
||||
GeneralRegisterSet(Registers::AllMask & ~((uint32_t(1) << Registers::sp) |
|
||||
(uint32_t(1) << Registers::pc))),
|
||||
FloatRegisterSet(FloatRegisters::AllDoubleMask));
|
||||
static_assert(!SupportsSimd, "high lanes of SIMD registers need to be saved too.");
|
||||
#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
|
||||
static const LiveRegisterSet RegsToPreserve(
|
||||
GeneralRegisterSet(Registers::AllMask & ~((uint32_t(1) << Registers::k0) |
|
||||
(uint32_t(1) << Registers::k1) |
|
||||
(uint32_t(1) << Registers::sp) |
|
||||
(uint32_t(1) << Registers::zero))),
|
||||
FloatRegisterSet(FloatRegisters::AllDoubleMask));
|
||||
static_assert(!SupportsSimd, "high lanes of SIMD registers need to be saved too.");
|
||||
#else
|
||||
static const LiveRegisterSet RegsToPreserve(
|
||||
GeneralRegisterSet(Registers::AllMask & ~(uint32_t(1) << Registers::StackPointer)),
|
||||
FloatRegisterSet(FloatRegisters::AllMask));
|
||||
#endif
|
||||
|
||||
// Generate a stub which calls WasmReportTrap() and can be executed by having
|
||||
// the signal handler redirect PC from any trapping instruction.
|
||||
static bool
|
||||
|
@ -1387,37 +1369,16 @@ GenerateTrapExit(MacroAssembler& masm, Label* throwLabel, Offsets* offsets)
|
|||
|
||||
offsets->begin = masm.currentOffset();
|
||||
|
||||
// Traps can only happen at well-defined program points. However, since
|
||||
// traps may resume and the optimal assumption for the surrounding code is
|
||||
// that registers are not clobbered, we need to preserve all registers in
|
||||
// the trap exit. One simplifying assumption is that flags may be clobbered.
|
||||
// Push a dummy word to use as return address below.
|
||||
masm.push(ImmWord(0));
|
||||
masm.setFramePushed(0);
|
||||
masm.PushRegsInMask(RegsToPreserve);
|
||||
|
||||
// We know that StackPointer is word-aligned, but not necessarily
|
||||
// stack-aligned, so we need to align it dynamically.
|
||||
Register preAlignStackPointer = ABINonVolatileReg;
|
||||
masm.moveStackPtrTo(preAlignStackPointer);
|
||||
masm.andToStackPtr(Imm32(~(ABIStackAlignment - 1)));
|
||||
if (ShadowStackSpace)
|
||||
masm.subFromStackPtr(Imm32(ShadowStackSpace));
|
||||
|
||||
masm.assertStackAlignment(ABIStackAlignment);
|
||||
masm.call(SymbolicAddress::HandleTrap);
|
||||
masm.call(SymbolicAddress::ReportTrap);
|
||||
|
||||
// WasmHandleTrap returns null if control should transfer to the throw stub.
|
||||
masm.branchTestPtr(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
|
||||
|
||||
// Otherwise, the return value is the TrapData::resumePC we must jump to.
|
||||
// We must restore register state before jumping, which will clobber
|
||||
// ReturnReg, so store ReturnReg in the above-reserved stack slot which we
|
||||
// use to jump to via ret.
|
||||
masm.moveToStackPtr(preAlignStackPointer);
|
||||
masm.storePtr(ReturnReg, Address(masm.getStackPointer(), masm.framePushed()));
|
||||
masm.PopRegsInMask(RegsToPreserve);
|
||||
masm.ret();
|
||||
masm.jump(throwLabel);
|
||||
|
||||
return FinishOffsets(masm, offsets);
|
||||
}
|
||||
|
@ -1501,10 +1462,188 @@ GenerateUnalignedExit(MacroAssembler& masm, Label* throwLabel, Offsets* offsets)
|
|||
offsets);
|
||||
}
|
||||
|
||||
#if defined(JS_CODEGEN_ARM)
|
||||
static const LiveRegisterSet AllRegsExceptPCSP(
|
||||
GeneralRegisterSet(Registers::AllMask & ~((uint32_t(1) << Registers::sp) |
|
||||
(uint32_t(1) << Registers::pc))),
|
||||
FloatRegisterSet(FloatRegisters::AllDoubleMask));
|
||||
static_assert(!SupportsSimd, "high lanes of SIMD registers need to be saved too.");
|
||||
#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
|
||||
static const LiveRegisterSet AllUserRegsExceptSP(
|
||||
GeneralRegisterSet(Registers::AllMask & ~((uint32_t(1) << Registers::k0) |
|
||||
(uint32_t(1) << Registers::k1) |
|
||||
(uint32_t(1) << Registers::sp) |
|
||||
(uint32_t(1) << Registers::zero))),
|
||||
FloatRegisterSet(FloatRegisters::AllDoubleMask));
|
||||
static_assert(!SupportsSimd, "high lanes of SIMD registers need to be saved too.");
|
||||
#else
|
||||
static const LiveRegisterSet AllRegsExceptSP(
|
||||
GeneralRegisterSet(Registers::AllMask & ~(uint32_t(1) << Registers::StackPointer)),
|
||||
FloatRegisterSet(FloatRegisters::AllMask));
|
||||
#endif
|
||||
|
||||
// The async interrupt-callback exit is called from arbitrarily-interrupted wasm
|
||||
// code. It calls into the WasmHandleExecutionInterrupt to determine whether we must
|
||||
// really halt execution which can reenter the VM (e.g., to display the slow
|
||||
// script dialog). If execution is not interrupted, this stub must carefully
|
||||
// preserve *all* register state. If execution is interrupted, the entire
|
||||
// activation will be popped by the throw stub, so register state does not need
|
||||
// to be restored.
|
||||
static bool
|
||||
GenerateInterruptExit(MacroAssembler& masm, Label* throwLabel, Offsets* offsets)
|
||||
{
|
||||
masm.haltingAlign(CodeAlignment);
|
||||
|
||||
offsets->begin = masm.currentOffset();
|
||||
|
||||
#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
|
||||
// Be very careful here not to perturb the machine state before saving it
|
||||
// to the stack. In particular, add/sub instructions may set conditions in
|
||||
// the flags register.
|
||||
masm.push(Imm32(0)); // space used as return address, updated below
|
||||
masm.setFramePushed(0); // set to 0 now so that framePushed is offset of return address
|
||||
masm.PushFlags(); // after this we are safe to use sub
|
||||
masm.PushRegsInMask(AllRegsExceptSP); // save all GP/FP registers (except SP)
|
||||
|
||||
// We know that StackPointer is word-aligned, but not necessarily
|
||||
// stack-aligned, so we need to align it dynamically.
|
||||
masm.moveStackPtrTo(ABINonVolatileReg);
|
||||
masm.andToStackPtr(Imm32(~(ABIStackAlignment - 1)));
|
||||
if (ShadowStackSpace)
|
||||
masm.subFromStackPtr(Imm32(ShadowStackSpace));
|
||||
|
||||
// Make the call to C++, which preserves ABINonVolatileReg.
|
||||
masm.assertStackAlignment(ABIStackAlignment);
|
||||
masm.call(SymbolicAddress::HandleExecutionInterrupt);
|
||||
|
||||
// HandleExecutionInterrupt returns null if execution is interrupted and
|
||||
// the resumption pc otherwise.
|
||||
masm.branchTestPtr(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
|
||||
|
||||
// Restore the stack pointer then store resumePC into the stack slow that
|
||||
// will be popped by the 'ret' below.
|
||||
masm.moveToStackPtr(ABINonVolatileReg);
|
||||
masm.storePtr(ReturnReg, Address(StackPointer, masm.framePushed()));
|
||||
|
||||
// Restore the machine state to before the interrupt. After popping flags,
|
||||
// no instructions can be executed which set flags.
|
||||
masm.PopRegsInMask(AllRegsExceptSP);
|
||||
masm.PopFlags();
|
||||
|
||||
// Return to the resumePC stored into this stack slot above.
|
||||
MOZ_ASSERT(masm.framePushed() == 0);
|
||||
masm.ret();
|
||||
#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
|
||||
// Reserve space to store resumePC and HeapReg.
|
||||
masm.subFromStackPtr(Imm32(2 * sizeof(intptr_t)));
|
||||
// Set to zero so we can use masm.framePushed() below.
|
||||
masm.setFramePushed(0);
|
||||
|
||||
// Save all registers, except sp.
|
||||
masm.PushRegsInMask(AllUserRegsExceptSP);
|
||||
|
||||
// Save the stack pointer and FCSR in a non-volatile registers.
|
||||
masm.moveStackPtrTo(s0);
|
||||
masm.as_cfc1(s1, Assembler::FCSR);
|
||||
|
||||
// Align the stack.
|
||||
masm.ma_and(StackPointer, StackPointer, Imm32(~(ABIStackAlignment - 1)));
|
||||
|
||||
// Store HeapReg into the reserved space.
|
||||
masm.storePtr(HeapReg, Address(s0, masm.framePushed() + sizeof(intptr_t)));
|
||||
|
||||
# ifdef USES_O32_ABI
|
||||
// MIPS ABI requires rewserving stack for registes $a0 to $a3.
|
||||
masm.subFromStackPtr(Imm32(4 * sizeof(intptr_t)));
|
||||
# endif
|
||||
|
||||
masm.assertStackAlignment(ABIStackAlignment);
|
||||
masm.call(SymbolicAddress::HandleExecutionInterrupt);
|
||||
|
||||
masm.branchTestPtr(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
|
||||
|
||||
// This will restore stack to the address before the call.
|
||||
masm.moveToStackPtr(s0);
|
||||
|
||||
// Restore FCSR.
|
||||
masm.as_ctc1(s1, Assembler::FCSR);
|
||||
|
||||
// Store resumePC into the reserved space.
|
||||
masm.storePtr(ReturnReg, Address(s0, masm.framePushed()));
|
||||
|
||||
masm.PopRegsInMask(AllUserRegsExceptSP);
|
||||
|
||||
// Pop resumePC into PC. Clobber HeapReg to make the jump and restore it
|
||||
// during jump delay slot.
|
||||
masm.loadPtr(Address(StackPointer, 0), HeapReg);
|
||||
// Reclaim the reserve space.
|
||||
masm.addToStackPtr(Imm32(2 * sizeof(intptr_t)));
|
||||
masm.as_jr(HeapReg);
|
||||
masm.loadPtr(Address(StackPointer, -int32_t(sizeof(intptr_t))), HeapReg);
|
||||
#elif defined(JS_CODEGEN_ARM)
|
||||
{
|
||||
// Be careful not to clobber scratch registers before they are saved.
|
||||
ScratchRegisterScope scratch(masm);
|
||||
SecondScratchRegisterScope secondScratch(masm);
|
||||
|
||||
// Reserve a word to receive the return address.
|
||||
masm.as_alu(StackPointer, StackPointer, Imm8(4), OpSub);
|
||||
|
||||
// Set framePushed to 0 now so that framePushed can be used later as the
|
||||
// stack offset to the return-address space reserved above.
|
||||
masm.setFramePushed(0);
|
||||
|
||||
// Save all GP/FP registers (except PC and SP).
|
||||
masm.PushRegsInMask(AllRegsExceptPCSP);
|
||||
}
|
||||
|
||||
// Save SP, APSR and FPSCR in non-volatile registers.
|
||||
masm.as_mrs(r4);
|
||||
masm.as_vmrs(r5);
|
||||
masm.mov(sp, r6);
|
||||
|
||||
// We know that StackPointer is word-aligned, but not necessarily
|
||||
// stack-aligned, so we need to align it dynamically.
|
||||
masm.andToStackPtr(Imm32(~(ABIStackAlignment - 1)));
|
||||
|
||||
// Make the call to C++, which preserves the non-volatile registers.
|
||||
masm.assertStackAlignment(ABIStackAlignment);
|
||||
masm.call(SymbolicAddress::HandleExecutionInterrupt);
|
||||
|
||||
// HandleExecutionInterrupt returns null if execution is interrupted and
|
||||
// the resumption pc otherwise.
|
||||
masm.branchTestPtr(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
|
||||
|
||||
// Restore the stack pointer then store resumePC into the stack slot that
|
||||
// will be popped by the 'ret' below.
|
||||
masm.mov(r6, sp);
|
||||
masm.storePtr(ReturnReg, Address(sp, masm.framePushed()));
|
||||
|
||||
// Restore the machine state to before the interrupt. After popping flags,
|
||||
// no instructions can be executed which set flags.
|
||||
masm.as_vmsr(r5);
|
||||
masm.as_msr(r4);
|
||||
masm.PopRegsInMask(AllRegsExceptPCSP);
|
||||
|
||||
// Return to the resumePC stored into this stack slot above.
|
||||
MOZ_ASSERT(masm.framePushed() == 0);
|
||||
masm.ret();
|
||||
#elif defined(JS_CODEGEN_ARM64)
|
||||
MOZ_CRASH();
|
||||
#elif defined (JS_CODEGEN_NONE)
|
||||
MOZ_CRASH();
|
||||
#else
|
||||
# error "Unknown architecture!"
|
||||
#endif
|
||||
|
||||
return FinishOffsets(masm, offsets);
|
||||
}
|
||||
|
||||
// Generate a stub that restores the stack pointer to what it was on entry to
|
||||
// the wasm activation, sets the return register to 'false' and then executes a
|
||||
// return which will return from this wasm activation to the caller. This stub
|
||||
// should only be called after the caller has reported an error.
|
||||
// should only be called after the caller has reported an error (or, in the case
|
||||
// of the interrupt stub, intends to interrupt execution).
|
||||
static bool
|
||||
GenerateThrowStub(MacroAssembler& masm, Label* throwLabel, Offsets* offsets)
|
||||
{
|
||||
|
@ -1514,7 +1653,8 @@ GenerateThrowStub(MacroAssembler& masm, Label* throwLabel, Offsets* offsets)
|
|||
|
||||
offsets->begin = masm.currentOffset();
|
||||
|
||||
// Conservatively, the stack pointer can be unaligned and we must align it
|
||||
// The throw stub can be jumped to from an async interrupt that is halting
|
||||
// execution. Thus the stack pointer can be unaligned and we must align it
|
||||
// dynamically.
|
||||
masm.andToStackPtr(Imm32(~(ABIStackAlignment - 1)));
|
||||
if (ShadowStackSpace)
|
||||
|
@ -1659,7 +1799,6 @@ wasm::GenerateStubs(const ModuleEnvironment& env, const FuncImportVector& import
|
|||
case Trap::IndirectCallBadSig:
|
||||
case Trap::ImpreciseSimdConversion:
|
||||
case Trap::StackOverflow:
|
||||
case Trap::CheckInterrupt:
|
||||
case Trap::ThrowReported:
|
||||
break;
|
||||
// The TODO list of "old" traps to convert to new traps:
|
||||
|
@ -1696,12 +1835,19 @@ wasm::GenerateStubs(const ModuleEnvironment& env, const FuncImportVector& import
|
|||
if (!code->codeRanges.emplaceBack(CodeRange::TrapExit, offsets))
|
||||
return false;
|
||||
|
||||
CallableOffsets callableOffsets;
|
||||
if (!GenerateDebugTrapStub(masm, &throwLabel, &callableOffsets))
|
||||
if (!GenerateInterruptExit(masm, &throwLabel, &offsets))
|
||||
return false;
|
||||
if (!code->codeRanges.emplaceBack(CodeRange::DebugTrap, callableOffsets))
|
||||
if (!code->codeRanges.emplaceBack(CodeRange::Interrupt, offsets))
|
||||
return false;
|
||||
|
||||
{
|
||||
CallableOffsets offsets;
|
||||
if (!GenerateDebugTrapStub(masm, &throwLabel, &offsets))
|
||||
return false;
|
||||
if (!code->codeRanges.emplaceBack(CodeRange::DebugTrap, offsets))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GenerateThrowStub(masm, &throwLabel, &offsets))
|
||||
return false;
|
||||
if (!code->codeRanges.emplaceBack(CodeRange::Throw, offsets))
|
||||
|
|
|
@ -784,6 +784,7 @@ CodeRange::CodeRange(Kind kind, Offsets offsets)
|
|||
case UnalignedExit:
|
||||
case TrapExit:
|
||||
case Throw:
|
||||
case Interrupt:
|
||||
break;
|
||||
default:
|
||||
MOZ_CRASH("should use more specific constructor");
|
||||
|
@ -911,23 +912,3 @@ wasm::CreateTlsData(uint32_t globalDataLength)
|
|||
|
||||
return UniqueTlsData(tlsData);
|
||||
}
|
||||
|
||||
void
|
||||
TlsData::setInterrupt()
|
||||
{
|
||||
interrupt = true;
|
||||
stackLimit = UINTPTR_MAX;
|
||||
}
|
||||
|
||||
bool
|
||||
TlsData::isInterrupted() const
|
||||
{
|
||||
return interrupt || stackLimit == UINTPTR_MAX;
|
||||
}
|
||||
|
||||
void
|
||||
TlsData::resetInterrupt(JSContext* cx)
|
||||
{
|
||||
interrupt = false;
|
||||
stackLimit = cx->stackLimitForJitCode(JS::StackForUntrustedScript);
|
||||
}
|
||||
|
|
|
@ -85,6 +85,12 @@ using mozilla::PodEqual;
|
|||
using mozilla::Some;
|
||||
using mozilla::Unused;
|
||||
|
||||
typedef Vector<uint32_t, 0, SystemAllocPolicy> Uint32Vector;
|
||||
typedef Vector<uint8_t, 0, SystemAllocPolicy> Bytes;
|
||||
typedef UniquePtr<Bytes> UniqueBytes;
|
||||
typedef UniquePtr<const Bytes> UniqueConstBytes;
|
||||
typedef Vector<char, 0, SystemAllocPolicy> UTF8Bytes;
|
||||
|
||||
typedef int8_t I8x16[16];
|
||||
typedef int16_t I16x8[8];
|
||||
typedef int32_t I32x4[4];
|
||||
|
@ -98,13 +104,6 @@ class Module;
|
|||
class Instance;
|
||||
class Table;
|
||||
|
||||
typedef Vector<uint32_t, 0, SystemAllocPolicy> Uint32Vector;
|
||||
typedef Vector<uint8_t, 0, SystemAllocPolicy> Bytes;
|
||||
typedef UniquePtr<Bytes> UniqueBytes;
|
||||
typedef UniquePtr<const Bytes> UniqueConstBytes;
|
||||
typedef Vector<char, 0, SystemAllocPolicy> UTF8Bytes;
|
||||
typedef Vector<Instance*, 0, SystemAllocPolicy> InstanceVector;
|
||||
|
||||
// To call Vector::podResizeToFit, a type must specialize mozilla::IsPod
|
||||
// which is pretty verbose to do within js::wasm, so factor that process out
|
||||
// into a macro.
|
||||
|
@ -929,10 +928,6 @@ enum class Trap
|
|||
// the same over-recursed error as JS.
|
||||
StackOverflow,
|
||||
|
||||
// The wasm execution has potentially run too long and the engine must call
|
||||
// CheckForInterrupt(). This trap is resumable.
|
||||
CheckInterrupt,
|
||||
|
||||
// Signal an error that was reported in C++ code.
|
||||
ThrowReported,
|
||||
|
||||
|
@ -1069,6 +1064,7 @@ class CodeRange
|
|||
OutOfBoundsExit, // stub jumped to by non-standard asm.js SIMD/Atomics
|
||||
UnalignedExit, // stub jumped to by wasm Atomics and non-standard
|
||||
// ARM unaligned trap
|
||||
Interrupt, // stub executes asynchronously to interrupt wasm
|
||||
Throw // special stack-unwinding stub jumped to by other stubs
|
||||
};
|
||||
|
||||
|
@ -1377,9 +1373,10 @@ enum class SymbolicAddress
|
|||
LogD,
|
||||
PowD,
|
||||
ATan2D,
|
||||
HandleExecutionInterrupt,
|
||||
HandleDebugTrap,
|
||||
HandleThrow,
|
||||
HandleTrap,
|
||||
ReportTrap,
|
||||
OldReportTrap,
|
||||
ReportOutOfBounds,
|
||||
ReportUnalignedAccess,
|
||||
|
@ -1532,19 +1529,9 @@ struct TlsData
|
|||
// The containing JSContext.
|
||||
JSContext* cx;
|
||||
|
||||
// Usually equal to cx->stackLimitForJitCode(JS::StackForUntrustedScript),
|
||||
// but can be racily set to trigger immediate trap as an opportunity to
|
||||
// CheckForInterrupt without an additional branch.
|
||||
Atomic<uintptr_t, mozilla::Relaxed> stackLimit;
|
||||
|
||||
// Set to 1 when wasm should call CheckForInterrupt.
|
||||
Atomic<uint32_t, mozilla::Relaxed> interrupt;
|
||||
|
||||
// Methods to set, test and clear the above two fields. Both interrupt
|
||||
// fields are Relaxed and so no consistency/ordering can be assumed.
|
||||
void setInterrupt();
|
||||
bool isInterrupted() const;
|
||||
void resetInterrupt(JSContext* cx);
|
||||
// The native stack limit which is checked by prologues. Shortcut for
|
||||
// cx->stackLimitForJitCode(JS::StackForUntrustedScript).
|
||||
uintptr_t stackLimit;
|
||||
|
||||
// Pointer that should be freed (due to padding before the TlsData).
|
||||
void* allocatedBase;
|
||||
|
|
|
@ -2393,11 +2393,6 @@ JSReporter::CollectReports(WindowPaths* windowPaths,
|
|||
KIND_OTHER, rtStats.runtime.tracelogger,
|
||||
"The memory used for the tracelogger, including the graph and events.");
|
||||
|
||||
// Report the numbers for memory used by wasm Runtime state.
|
||||
REPORT_BYTES(NS_LITERAL_CSTRING("wasm-runtime"),
|
||||
KIND_OTHER, rtStats.runtime.wasmRuntime,
|
||||
"The memory used for wasm runtime bookkeeping.");
|
||||
|
||||
// Report the numbers for memory outside of compartments.
|
||||
|
||||
REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/gc-heap/unused-chunks"),
|
||||
|
|
Загрузка…
Ссылка в новой задаче