Bug 1711073 - wasm: Move Frame family to WasmFrame.h. r=jseward

Move the wasm::Frame family of types to their own
header and implementation. Switch consumers of headers
away from WasmTypes.h when they only need Frame/TlsData.

Depends on D117045

Differential Revision: https://phabricator.services.mozilla.com/D117046
This commit is contained in:
Ryan Hunt 2021-06-10 17:11:33 +00:00
Родитель 4c641fffb0
Коммит 31d3e6fae6
10 изменённых файлов: 530 добавлений и 447 удалений

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

@ -41,6 +41,8 @@
#include "vm/FunctionFlags.h"
#include "vm/JSObject.h"
#include "vm/StringType.h"
#include "wasm/WasmFrame.h"
#include "wasm/WasmTypes.h"
// [SMDOC] MacroAssembler multi-platform overview
//

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

@ -25,7 +25,7 @@
#include "vm/JSFunction.h"
#include "vm/JSScript.h"
#include "vm/SavedFrame.h"
#include "wasm/WasmTypes.h" // js::wasm::DebugFrame
#include "wasm/WasmFrame.h" // js::wasm::DebugFrame
namespace js {

177
js/src/wasm/WasmFrame.cpp Normal file
Просмотреть файл

@ -0,0 +1,177 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
*
* Copyright 2021 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "wasm/WasmFrame.h"
#include "jsmath.h"
#include "jit/JitFrames.h"
#include "jit/MacroAssembler.h"
#include "js/friend/ErrorMessages.h" // JSMSG_*
#include "vm/EnvironmentObject.h"
#include "vm/JSObject.h"
#include "wasm/WasmBaselineCompile.h"
#include "wasm/WasmInstance.h"
#include "wasm/WasmStubs.h"
#include "wasm/WasmTlsData.h"
#include "wasm/WasmTypes.h"
#include "vm/JSObject-inl.h"
#include "vm/NativeObject-inl.h"
using namespace js;
using namespace js::jit;
using namespace js::wasm;
/* static */
DebugFrame* DebugFrame::from(Frame* fp) {
MOZ_ASSERT(
GetNearestEffectiveTls(fp)->instance->code().metadata().debugEnabled);
auto* df =
reinterpret_cast<DebugFrame*>((uint8_t*)fp - DebugFrame::offsetOfFrame());
MOZ_ASSERT(GetNearestEffectiveTls(fp)->instance == df->instance());
return df;
}
void DebugFrame::alignmentStaticAsserts() {
// VS2017 doesn't consider offsetOfFrame() to be a constexpr, so we have
// to use offsetof directly. These asserts can't be at class-level
// because the type is incomplete.
static_assert(WasmStackAlignment >= Alignment,
"Aligned by ABI before pushing DebugFrame");
static_assert((offsetof(DebugFrame, frame_) + sizeof(Frame)) % Alignment == 0,
"Aligned after pushing DebugFrame");
#ifdef JS_CODEGEN_ARM64
// This constraint may or may not be necessary. If you hit this because
// you've changed the frame size then feel free to remove it, but be extra
// aware of possible problems.
static_assert(sizeof(DebugFrame) % 16 == 0, "ARM64 SP alignment");
#endif
}
Instance* DebugFrame::instance() const {
return GetNearestEffectiveTls(&frame_)->instance;
}
GlobalObject* DebugFrame::global() const {
return &instance()->object()->global();
}
bool DebugFrame::hasGlobal(const GlobalObject* global) const {
return global == &instance()->objectUnbarriered()->global();
}
JSObject* DebugFrame::environmentChain() const {
return &global()->lexicalEnvironment();
}
bool DebugFrame::getLocal(uint32_t localIndex, MutableHandleValue vp) {
ValTypeVector locals;
size_t argsLength;
StackResults stackResults;
if (!instance()->debug().debugGetLocalTypes(funcIndex(), &locals, &argsLength,
&stackResults)) {
return false;
}
ValTypeVector args;
MOZ_ASSERT(argsLength <= locals.length());
if (!args.append(locals.begin(), argsLength)) {
return false;
}
ArgTypeVector abiArgs(args, stackResults);
BaseLocalIter iter(locals, abiArgs, /* debugEnabled = */ true);
while (!iter.done() && iter.index() < localIndex) {
iter++;
}
MOZ_ALWAYS_TRUE(!iter.done());
uint8_t* frame = static_cast<uint8_t*>((void*)this) + offsetOfFrame();
void* dataPtr = frame - iter.frameOffset();
switch (iter.mirType()) {
case jit::MIRType::Int32:
vp.set(Int32Value(*static_cast<int32_t*>(dataPtr)));
break;
case jit::MIRType::Int64:
// Just display as a Number; it's ok if we lose some precision
vp.set(NumberValue((double)*static_cast<int64_t*>(dataPtr)));
break;
case jit::MIRType::Float32:
vp.set(NumberValue(JS::CanonicalizeNaN(*static_cast<float*>(dataPtr))));
break;
case jit::MIRType::Double:
vp.set(NumberValue(JS::CanonicalizeNaN(*static_cast<double*>(dataPtr))));
break;
case jit::MIRType::RefOrNull:
vp.set(ObjectOrNullValue(*(JSObject**)dataPtr));
break;
#ifdef ENABLE_WASM_SIMD
case jit::MIRType::Simd128:
vp.set(NumberValue(0));
break;
#endif
default:
MOZ_CRASH("local type");
}
return true;
}
bool DebugFrame::updateReturnJSValue(JSContext* cx) {
MutableHandleValue rval =
MutableHandleValue::fromMarkedLocation(&cachedReturnJSValue_);
rval.setUndefined();
flags_.hasCachedReturnJSValue = true;
ResultType resultType = instance()->debug().debugGetResultType(funcIndex());
Maybe<char*> stackResultsLoc;
if (ABIResultIter::HasStackResults(resultType)) {
stackResultsLoc = Some(static_cast<char*>(stackResultsPointer_));
}
DebugCodegen(DebugChannel::Function,
"wasm-function[%d] updateReturnJSValue [", funcIndex());
bool ok =
ResultsToJSValue(cx, resultType, registerResults_, stackResultsLoc, rval);
DebugCodegen(DebugChannel::Function, "]\n");
return ok;
}
HandleValue DebugFrame::returnValue() const {
MOZ_ASSERT(flags_.hasCachedReturnJSValue);
return HandleValue::fromMarkedLocation(&cachedReturnJSValue_);
}
void DebugFrame::clearReturnJSValue() {
flags_.hasCachedReturnJSValue = true;
cachedReturnJSValue_.setUndefined();
}
void DebugFrame::observe(JSContext* cx) {
if (!flags_.observing) {
instance()->debug().adjustEnterAndLeaveFrameTrapsState(
cx, /* enabled = */ true);
flags_.observing = true;
}
}
void DebugFrame::leave(JSContext* cx) {
if (flags_.observing) {
instance()->debug().adjustEnterAndLeaveFrameTrapsState(
cx, /* enabled = */ false);
flags_.observing = false;
}
}

342
js/src/wasm/WasmFrame.h Normal file
Просмотреть файл

@ -0,0 +1,342 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
*
* Copyright 2021 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef wasm_frame_h
#define wasm_frame_h
#include <stdint.h>
#include <type_traits>
#include "NamespaceImports.h"
#include "wasm/WasmConstants.h"
#include "wasm/WasmValue.h"
namespace js {
namespace wasm {
struct TlsData;
// Bit set as the lowest bit of a frame pointer, used in two different mutually
// exclusive situations:
// - either it's a low bit tag in a FramePointer value read from the
// Frame::callerFP of an inner wasm frame. This indicates the previous call
// frame has been set up by a JIT caller that directly called into a wasm
// function's body. This is only stored in Frame::callerFP for a wasm frame
// called from JIT code, and thus it can not appear in a JitActivation's
// exitFP.
// - or it's the low big tag set when exiting wasm code in JitActivation's
// exitFP.
constexpr uintptr_t ExitOrJitEntryFPTag = 0x1;
// wasm::Frame represents the bytes pushed by the call instruction and the
// fixed prologue generated by wasm::GenerateCallablePrologue.
//
// Across all architectures it is assumed that, before the call instruction, the
// stack pointer is WasmStackAlignment-aligned. Thus after the prologue, and
// before the function has made its stack reservation, the stack alignment is
// sizeof(Frame) % WasmStackAlignment.
//
// During MacroAssembler code generation, the bytes pushed after the wasm::Frame
// are counted by masm.framePushed. Thus, the stack alignment at any point in
// time is (sizeof(wasm::Frame) + masm.framePushed) % WasmStackAlignment.
class Frame {
// See GenerateCallableEpilogue for why this must be
// the first field of wasm::Frame (in a downward-growing stack).
// It's either the caller's Frame*, for wasm callers, or the JIT caller frame
// plus a tag otherwise.
uint8_t* callerFP_;
// The return address pushed by the call (in the case of ARM/MIPS the return
// address is pushed by the first instruction of the prologue).
void* returnAddress_;
public:
static constexpr uint32_t callerFPOffset() {
return offsetof(Frame, callerFP_);
}
static constexpr uint32_t returnAddressOffset() {
return offsetof(Frame, returnAddress_);
}
uint8_t* returnAddress() const {
return reinterpret_cast<uint8_t*>(returnAddress_);
}
void** addressOfReturnAddress() {
return reinterpret_cast<void**>(&returnAddress_);
}
uint8_t* rawCaller() const { return callerFP_; }
Frame* wasmCaller() const {
MOZ_ASSERT(!callerIsExitOrJitEntryFP());
return reinterpret_cast<Frame*>(callerFP_);
}
bool callerIsExitOrJitEntryFP() const {
return isExitOrJitEntryFP(callerFP_);
}
uint8_t* jitEntryCaller() const { return toJitEntryCaller(callerFP_); }
static const Frame* fromUntaggedWasmExitFP(const void* savedFP) {
MOZ_ASSERT(!isExitOrJitEntryFP(savedFP));
return reinterpret_cast<const Frame*>(savedFP);
}
static bool isExitOrJitEntryFP(const void* fp) {
return reinterpret_cast<uintptr_t>(fp) & ExitOrJitEntryFPTag;
}
static uint8_t* toJitEntryCaller(const void* fp) {
MOZ_ASSERT(isExitOrJitEntryFP(fp));
return reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(fp) &
~ExitOrJitEntryFPTag);
}
static uint8_t* addExitOrJitEntryFPTag(const Frame* fp) {
MOZ_ASSERT(!isExitOrJitEntryFP(fp));
return reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(fp) |
ExitOrJitEntryFPTag);
}
};
static_assert(!std::is_polymorphic_v<Frame>, "Frame doesn't need a vtable.");
static_assert(sizeof(Frame) == 2 * sizeof(void*),
"Frame is a two pointer structure");
class FrameWithTls : public Frame {
TlsData* calleeTls_;
TlsData* callerTls_;
public:
TlsData* calleeTls() { return calleeTls_; }
TlsData* callerTls() { return callerTls_; }
constexpr static uint32_t sizeWithoutFrame() {
return sizeof(wasm::FrameWithTls) - sizeof(wasm::Frame);
}
constexpr static uint32_t calleeTLSOffset() {
return offsetof(FrameWithTls, calleeTls_) - sizeof(wasm::Frame);
}
constexpr static uint32_t callerTLSOffset() {
return offsetof(FrameWithTls, callerTls_) - sizeof(wasm::Frame);
}
};
static_assert(FrameWithTls::calleeTLSOffset() == 0u,
"Callee tls stored right above the return address.");
static_assert(FrameWithTls::callerTLSOffset() == sizeof(void*),
"Caller tls stored right above the callee tls.");
static_assert(FrameWithTls::sizeWithoutFrame() == 2 * sizeof(void*),
"There are only two additional slots");
#if defined(JS_CODEGEN_ARM64)
static_assert(sizeof(Frame) % 16 == 0, "frame is aligned");
#endif
// A DebugFrame is a Frame with additional fields that are added after the
// normal function prologue by the baseline compiler. If a Module is compiled
// with debugging enabled, then all its code creates DebugFrames on the stack
// instead of just Frames. These extra fields are used by the Debugger API.
class DebugFrame {
// The register results field. Initialized only during the baseline
// compiler's return sequence to allow the debugger to inspect and
// modify the return values of a frame being debugged.
union SpilledRegisterResult {
private:
int32_t i32_;
int64_t i64_;
intptr_t ref_;
AnyRef anyref_;
float f32_;
double f64_;
#ifdef ENABLE_WASM_SIMD
V128 v128_;
#endif
#ifdef DEBUG
// Should we add a new value representation, this will remind us to update
// SpilledRegisterResult.
static inline void assertAllValueTypesHandled(ValType type) {
switch (type.kind()) {
case ValType::I32:
case ValType::I64:
case ValType::F32:
case ValType::F64:
case ValType::V128:
case ValType::Rtt:
return;
case ValType::Ref:
switch (type.refTypeKind()) {
case RefType::Func:
case RefType::Extern:
case RefType::Eq:
case RefType::TypeIndex:
return;
}
}
}
#endif
};
SpilledRegisterResult registerResults_[MaxRegisterResults];
// The returnValue() method returns a HandleValue pointing to this field.
js::Value cachedReturnJSValue_;
// If the function returns multiple results, this field is initialized
// to a pointer to the stack results.
void* stackResultsPointer_;
// The function index of this frame. Technically, this could be derived
// given a PC into this frame (which could lookup the CodeRange which has
// the function index), but this isn't always readily available.
uint32_t funcIndex_;
// Flags whose meaning are described below.
union Flags {
struct {
uint32_t observing : 1;
uint32_t isDebuggee : 1;
uint32_t prevUpToDate : 1;
uint32_t hasCachedSavedFrame : 1;
uint32_t hasCachedReturnJSValue : 1;
uint32_t hasSpilledRefRegisterResult : MaxRegisterResults;
};
uint32_t allFlags;
} flags_;
// Avoid -Wunused-private-field warnings.
protected:
#if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_ARM) || \
defined(JS_CODEGEN_X86) || defined(__wasi__)
// See alignmentStaticAsserts(). For MIPS32, ARM32 and X86 DebugFrame is only
// 4-byte aligned, so we add another word to get up to 8-byte
// alignment.
uint32_t padding_;
#endif
#if defined(ENABLE_WASM_SIMD) && defined(JS_CODEGEN_ARM64)
uint64_t padding_;
#endif
private:
// The Frame goes at the end since the stack grows down.
Frame frame_;
public:
static DebugFrame* from(Frame* fp);
Frame& frame() { return frame_; }
uint32_t funcIndex() const { return funcIndex_; }
Instance* instance() const;
GlobalObject* global() const;
bool hasGlobal(const GlobalObject* global) const;
JSObject* environmentChain() const;
bool getLocal(uint32_t localIndex, MutableHandleValue vp);
// The return value must be written from the unboxed representation in the
// results union into cachedReturnJSValue_ by updateReturnJSValue() before
// returnValue() can return a Handle to it.
bool hasCachedReturnJSValue() const { return flags_.hasCachedReturnJSValue; }
[[nodiscard]] bool updateReturnJSValue(JSContext* cx);
HandleValue returnValue() const;
void clearReturnJSValue();
// Once the debugger observes a frame, it must be notified via
// onLeaveFrame() before the frame is popped. Calling observe() ensures the
// leave frame traps are enabled. Both methods are idempotent so the caller
// doesn't have to worry about calling them more than once.
void observe(JSContext* cx);
void leave(JSContext* cx);
// The 'isDebugge' bit is initialized to false and set by the WebAssembly
// runtime right before a frame is exposed to the debugger, as required by
// the Debugger API. The bit is then used for Debugger-internal purposes
// afterwards.
bool isDebuggee() const { return flags_.isDebuggee; }
void setIsDebuggee() { flags_.isDebuggee = true; }
void unsetIsDebuggee() { flags_.isDebuggee = false; }
// These are opaque boolean flags used by the debugger to implement
// AbstractFramePtr. They are initialized to false and not otherwise read or
// written by wasm code or runtime.
bool prevUpToDate() const { return flags_.prevUpToDate; }
void setPrevUpToDate() { flags_.prevUpToDate = true; }
void unsetPrevUpToDate() { flags_.prevUpToDate = false; }
bool hasCachedSavedFrame() const { return flags_.hasCachedSavedFrame; }
void setHasCachedSavedFrame() { flags_.hasCachedSavedFrame = true; }
void clearHasCachedSavedFrame() { flags_.hasCachedSavedFrame = false; }
bool hasSpilledRegisterRefResult(size_t n) const {
uint32_t mask = hasSpilledRegisterRefResultBitMask(n);
return (flags_.allFlags & mask) != 0;
}
// DebugFrame is accessed directly by JIT code.
static constexpr size_t offsetOfRegisterResults() {
return offsetof(DebugFrame, registerResults_);
}
static constexpr size_t offsetOfRegisterResult(size_t n) {
MOZ_ASSERT(n < MaxRegisterResults);
return offsetOfRegisterResults() + n * sizeof(SpilledRegisterResult);
}
static constexpr size_t offsetOfCachedReturnJSValue() {
return offsetof(DebugFrame, cachedReturnJSValue_);
}
static constexpr size_t offsetOfStackResultsPointer() {
return offsetof(DebugFrame, stackResultsPointer_);
}
static constexpr size_t offsetOfFlags() {
return offsetof(DebugFrame, flags_);
}
static constexpr uint32_t hasSpilledRegisterRefResultBitMask(size_t n) {
MOZ_ASSERT(n < MaxRegisterResults);
union Flags flags = {.allFlags = 0};
flags.hasSpilledRefRegisterResult = 1 << n;
MOZ_ASSERT(flags.allFlags != 0);
return flags.allFlags;
}
static constexpr size_t offsetOfFuncIndex() {
return offsetof(DebugFrame, funcIndex_);
}
static constexpr size_t offsetOfFrame() {
return offsetof(DebugFrame, frame_);
}
// DebugFrames are aligned to 8-byte aligned, allowing them to be placed in
// an AbstractFramePtr.
static const unsigned Alignment = 8;
static void alignmentStaticAsserts();
};
} // namespace wasm
} // namespace js
#endif // wasm_frame_h

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

@ -23,6 +23,7 @@
#include "vm/JSContext.h"
#include "wasm/WasmInstance.h"
#include "wasm/WasmStubs.h"
#include "wasm/WasmTlsData.h"
#include "jit/MacroAssembler-inl.h"

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

@ -21,6 +21,7 @@
#include "js/ProfilingFrameIterator.h"
#include "js/TypeDecls.h"
#include "wasm/WasmFrame.h"
#include "wasm/WasmTypes.h"
namespace js {
@ -37,6 +38,7 @@ namespace wasm {
class Code;
class CodeRange;
class DebugFrame;
struct TlsData;
class TypeIdDesc;
class Instance;
class ModuleSegment;

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

@ -36,9 +36,10 @@
#include "js/TypeDecls.h" // HandleValue, HandleObject, MutableHandleObject, MutableHandleFunction
#include "js/Vector.h" // JS::Vector
#include "js/WasmFeatures.h"
#include "vm/JSFunction.h" // JSFunction
#include "vm/NativeObject.h" // NativeObject
#include "wasm/WasmTypes.h" // MutableHandleWasmInstanceObject, wasm::*
#include "vm/JSFunction.h" // JSFunction
#include "vm/NativeObject.h" // NativeObject
#include "wasm/WasmTlsData.h" // UniqueTlsData
#include "wasm/WasmTypes.h" // MutableHandleWasmInstanceObject, wasm::*
class JSFreeOp;
class JSObject;

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

@ -550,145 +550,6 @@ size_t wasm::ComputeMappedSize(uint64_t maxSize) {
return boundsCheckLimit + GuardSize;
}
/* static */
DebugFrame* DebugFrame::from(Frame* fp) {
MOZ_ASSERT(
GetNearestEffectiveTls(fp)->instance->code().metadata().debugEnabled);
auto* df =
reinterpret_cast<DebugFrame*>((uint8_t*)fp - DebugFrame::offsetOfFrame());
MOZ_ASSERT(GetNearestEffectiveTls(fp)->instance == df->instance());
return df;
}
void DebugFrame::alignmentStaticAsserts() {
// VS2017 doesn't consider offsetOfFrame() to be a constexpr, so we have
// to use offsetof directly. These asserts can't be at class-level
// because the type is incomplete.
static_assert(WasmStackAlignment >= Alignment,
"Aligned by ABI before pushing DebugFrame");
static_assert((offsetof(DebugFrame, frame_) + sizeof(Frame)) % Alignment == 0,
"Aligned after pushing DebugFrame");
#ifdef JS_CODEGEN_ARM64
// This constraint may or may not be necessary. If you hit this because
// you've changed the frame size then feel free to remove it, but be extra
// aware of possible problems.
static_assert(sizeof(DebugFrame) % 16 == 0, "ARM64 SP alignment");
#endif
}
Instance* DebugFrame::instance() const {
return GetNearestEffectiveTls(&frame_)->instance;
}
GlobalObject* DebugFrame::global() const {
return &instance()->object()->global();
}
bool DebugFrame::hasGlobal(const GlobalObject* global) const {
return global == &instance()->objectUnbarriered()->global();
}
JSObject* DebugFrame::environmentChain() const {
return &global()->lexicalEnvironment();
}
bool DebugFrame::getLocal(uint32_t localIndex, MutableHandleValue vp) {
ValTypeVector locals;
size_t argsLength;
StackResults stackResults;
if (!instance()->debug().debugGetLocalTypes(funcIndex(), &locals, &argsLength,
&stackResults)) {
return false;
}
ValTypeVector args;
MOZ_ASSERT(argsLength <= locals.length());
if (!args.append(locals.begin(), argsLength)) {
return false;
}
ArgTypeVector abiArgs(args, stackResults);
BaseLocalIter iter(locals, abiArgs, /* debugEnabled = */ true);
while (!iter.done() && iter.index() < localIndex) {
iter++;
}
MOZ_ALWAYS_TRUE(!iter.done());
uint8_t* frame = static_cast<uint8_t*>((void*)this) + offsetOfFrame();
void* dataPtr = frame - iter.frameOffset();
switch (iter.mirType()) {
case jit::MIRType::Int32:
vp.set(Int32Value(*static_cast<int32_t*>(dataPtr)));
break;
case jit::MIRType::Int64:
// Just display as a Number; it's ok if we lose some precision
vp.set(NumberValue((double)*static_cast<int64_t*>(dataPtr)));
break;
case jit::MIRType::Float32:
vp.set(NumberValue(JS::CanonicalizeNaN(*static_cast<float*>(dataPtr))));
break;
case jit::MIRType::Double:
vp.set(NumberValue(JS::CanonicalizeNaN(*static_cast<double*>(dataPtr))));
break;
case jit::MIRType::RefOrNull:
vp.set(ObjectOrNullValue(*(JSObject**)dataPtr));
break;
#ifdef ENABLE_WASM_SIMD
case jit::MIRType::Simd128:
vp.set(NumberValue(0));
break;
#endif
default:
MOZ_CRASH("local type");
}
return true;
}
bool DebugFrame::updateReturnJSValue(JSContext* cx) {
MutableHandleValue rval =
MutableHandleValue::fromMarkedLocation(&cachedReturnJSValue_);
rval.setUndefined();
flags_.hasCachedReturnJSValue = true;
ResultType resultType = instance()->debug().debugGetResultType(funcIndex());
Maybe<char*> stackResultsLoc;
if (ABIResultIter::HasStackResults(resultType)) {
stackResultsLoc = Some(static_cast<char*>(stackResultsPointer_));
}
DebugCodegen(DebugChannel::Function,
"wasm-function[%d] updateReturnJSValue [", funcIndex());
bool ok =
ResultsToJSValue(cx, resultType, registerResults_, stackResultsLoc, rval);
DebugCodegen(DebugChannel::Function, "]\n");
return ok;
}
HandleValue DebugFrame::returnValue() const {
MOZ_ASSERT(flags_.hasCachedReturnJSValue);
return HandleValue::fromMarkedLocation(&cachedReturnJSValue_);
}
void DebugFrame::clearReturnJSValue() {
flags_.hasCachedReturnJSValue = true;
cachedReturnJSValue_.setUndefined();
}
void DebugFrame::observe(JSContext* cx) {
if (!flags_.observing) {
instance()->debug().adjustEnterAndLeaveFrameTrapsState(
cx, /* enabled = */ true);
flags_.observing = true;
}
}
void DebugFrame::leave(JSContext* cx) {
if (flags_.observing) {
instance()->debug().adjustEnterAndLeaveFrameTrapsState(
cx, /* enabled = */ false);
flags_.observing = false;
}
}
bool TrapSiteVectorArray::empty() const {
for (Trap trap : MakeEnumeratedRange(Trap::Limit)) {
if (!(*this)[trap].empty()) {

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

@ -79,19 +79,6 @@ class Module;
class Instance;
class Table;
// Bit set as the lowest bit of a frame pointer, used in two different mutually
// exclusive situations:
// - either it's a low bit tag in a FramePointer value read from the
// Frame::callerFP of an inner wasm frame. This indicates the previous call
// frame has been set up by a JIT caller that directly called into a wasm
// function's body. This is only stored in Frame::callerFP for a wasm frame
// called from JIT code, and thus it can not appear in a JitActivation's
// exitFP.
// - or it's the low big tag set when exiting wasm code in JitActivation's
// exitFP.
constexpr uintptr_t ExitOrJitEntryFPTag = 0x1;
// Exception tags are used to uniquely identify exceptions. They are stored
// in a vector in Instances and used by both WebAssembly.Exception for import
// and export, and by the representation of thrown exceptions.
@ -1593,297 +1580,6 @@ static const uint32_t MaxInlineMemoryFillLength = 0;
static_assert(MaxInlineMemoryCopyLength < MinOffsetGuardLimit, "precondition");
static_assert(MaxInlineMemoryFillLength < MinOffsetGuardLimit, "precondition");
// wasm::Frame represents the bytes pushed by the call instruction and the
// fixed prologue generated by wasm::GenerateCallablePrologue.
//
// Across all architectures it is assumed that, before the call instruction, the
// stack pointer is WasmStackAlignment-aligned. Thus after the prologue, and
// before the function has made its stack reservation, the stack alignment is
// sizeof(Frame) % WasmStackAlignment.
//
// During MacroAssembler code generation, the bytes pushed after the wasm::Frame
// are counted by masm.framePushed. Thus, the stack alignment at any point in
// time is (sizeof(wasm::Frame) + masm.framePushed) % WasmStackAlignment.
class Frame {
// See GenerateCallableEpilogue for why this must be
// the first field of wasm::Frame (in a downward-growing stack).
// It's either the caller's Frame*, for wasm callers, or the JIT caller frame
// plus a tag otherwise.
uint8_t* callerFP_;
// The return address pushed by the call (in the case of ARM/MIPS the return
// address is pushed by the first instruction of the prologue).
void* returnAddress_;
public:
static constexpr uint32_t callerFPOffset() {
return offsetof(Frame, callerFP_);
}
static constexpr uint32_t returnAddressOffset() {
return offsetof(Frame, returnAddress_);
}
uint8_t* returnAddress() const {
return reinterpret_cast<uint8_t*>(returnAddress_);
}
void** addressOfReturnAddress() {
return reinterpret_cast<void**>(&returnAddress_);
}
uint8_t* rawCaller() const { return callerFP_; }
Frame* wasmCaller() const {
MOZ_ASSERT(!callerIsExitOrJitEntryFP());
return reinterpret_cast<Frame*>(callerFP_);
}
bool callerIsExitOrJitEntryFP() const {
return isExitOrJitEntryFP(callerFP_);
}
uint8_t* jitEntryCaller() const { return toJitEntryCaller(callerFP_); }
static const Frame* fromUntaggedWasmExitFP(const void* savedFP) {
MOZ_ASSERT(!isExitOrJitEntryFP(savedFP));
return reinterpret_cast<const Frame*>(savedFP);
}
static bool isExitOrJitEntryFP(const void* fp) {
return reinterpret_cast<uintptr_t>(fp) & ExitOrJitEntryFPTag;
}
static uint8_t* toJitEntryCaller(const void* fp) {
MOZ_ASSERT(isExitOrJitEntryFP(fp));
return reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(fp) &
~ExitOrJitEntryFPTag);
}
static uint8_t* addExitOrJitEntryFPTag(const Frame* fp) {
MOZ_ASSERT(!isExitOrJitEntryFP(fp));
return reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(fp) |
ExitOrJitEntryFPTag);
}
};
static_assert(!std::is_polymorphic_v<Frame>, "Frame doesn't need a vtable.");
static_assert(sizeof(Frame) == 2 * sizeof(void*),
"Frame is a two pointer structure");
class FrameWithTls : public Frame {
TlsData* calleeTls_;
TlsData* callerTls_;
public:
TlsData* calleeTls() { return calleeTls_; }
TlsData* callerTls() { return callerTls_; }
constexpr static uint32_t sizeWithoutFrame() {
return sizeof(wasm::FrameWithTls) - sizeof(wasm::Frame);
}
constexpr static uint32_t calleeTLSOffset() {
return offsetof(FrameWithTls, calleeTls_) - sizeof(wasm::Frame);
}
constexpr static uint32_t callerTLSOffset() {
return offsetof(FrameWithTls, callerTls_) - sizeof(wasm::Frame);
}
};
static_assert(FrameWithTls::calleeTLSOffset() == 0u,
"Callee tls stored right above the return address.");
static_assert(FrameWithTls::callerTLSOffset() == sizeof(void*),
"Caller tls stored right above the callee tls.");
static_assert(FrameWithTls::sizeWithoutFrame() == 2 * sizeof(void*),
"There are only two additional slots");
#if defined(JS_CODEGEN_ARM64)
static_assert(sizeof(Frame) % 16 == 0, "frame is aligned");
#endif
// A DebugFrame is a Frame with additional fields that are added after the
// normal function prologue by the baseline compiler. If a Module is compiled
// with debugging enabled, then all its code creates DebugFrames on the stack
// instead of just Frames. These extra fields are used by the Debugger API.
class DebugFrame {
// The register results field. Initialized only during the baseline
// compiler's return sequence to allow the debugger to inspect and
// modify the return values of a frame being debugged.
union SpilledRegisterResult {
private:
int32_t i32_;
int64_t i64_;
intptr_t ref_;
AnyRef anyref_;
float f32_;
double f64_;
#ifdef ENABLE_WASM_SIMD
V128 v128_;
#endif
#ifdef DEBUG
// Should we add a new value representation, this will remind us to update
// SpilledRegisterResult.
static inline void assertAllValueTypesHandled(ValType type) {
switch (type.kind()) {
case ValType::I32:
case ValType::I64:
case ValType::F32:
case ValType::F64:
case ValType::V128:
case ValType::Rtt:
return;
case ValType::Ref:
switch (type.refTypeKind()) {
case RefType::Func:
case RefType::Extern:
case RefType::Eq:
case RefType::TypeIndex:
return;
}
}
}
#endif
};
SpilledRegisterResult registerResults_[MaxRegisterResults];
// The returnValue() method returns a HandleValue pointing to this field.
js::Value cachedReturnJSValue_;
// If the function returns multiple results, this field is initialized
// to a pointer to the stack results.
void* stackResultsPointer_;
// The function index of this frame. Technically, this could be derived
// given a PC into this frame (which could lookup the CodeRange which has
// the function index), but this isn't always readily available.
uint32_t funcIndex_;
// Flags whose meaning are described below.
union Flags {
struct {
uint32_t observing : 1;
uint32_t isDebuggee : 1;
uint32_t prevUpToDate : 1;
uint32_t hasCachedSavedFrame : 1;
uint32_t hasCachedReturnJSValue : 1;
uint32_t hasSpilledRefRegisterResult : MaxRegisterResults;
};
uint32_t allFlags;
} flags_;
// Avoid -Wunused-private-field warnings.
protected:
#if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_ARM) || \
defined(JS_CODEGEN_X86) || defined(__wasi__)
// See alignmentStaticAsserts(). For MIPS32, ARM32 and X86 DebugFrame is only
// 4-byte aligned, so we add another word to get up to 8-byte
// alignment.
uint32_t padding_;
#endif
#if defined(ENABLE_WASM_SIMD) && defined(JS_CODEGEN_ARM64)
uint64_t padding_;
#endif
private:
// The Frame goes at the end since the stack grows down.
Frame frame_;
public:
static DebugFrame* from(Frame* fp);
Frame& frame() { return frame_; }
uint32_t funcIndex() const { return funcIndex_; }
Instance* instance() const;
GlobalObject* global() const;
bool hasGlobal(const GlobalObject* global) const;
JSObject* environmentChain() const;
bool getLocal(uint32_t localIndex, MutableHandleValue vp);
// The return value must be written from the unboxed representation in the
// results union into cachedReturnJSValue_ by updateReturnJSValue() before
// returnValue() can return a Handle to it.
bool hasCachedReturnJSValue() const { return flags_.hasCachedReturnJSValue; }
[[nodiscard]] bool updateReturnJSValue(JSContext* cx);
HandleValue returnValue() const;
void clearReturnJSValue();
// Once the debugger observes a frame, it must be notified via
// onLeaveFrame() before the frame is popped. Calling observe() ensures the
// leave frame traps are enabled. Both methods are idempotent so the caller
// doesn't have to worry about calling them more than once.
void observe(JSContext* cx);
void leave(JSContext* cx);
// The 'isDebugge' bit is initialized to false and set by the WebAssembly
// runtime right before a frame is exposed to the debugger, as required by
// the Debugger API. The bit is then used for Debugger-internal purposes
// afterwards.
bool isDebuggee() const { return flags_.isDebuggee; }
void setIsDebuggee() { flags_.isDebuggee = true; }
void unsetIsDebuggee() { flags_.isDebuggee = false; }
// These are opaque boolean flags used by the debugger to implement
// AbstractFramePtr. They are initialized to false and not otherwise read or
// written by wasm code or runtime.
bool prevUpToDate() const { return flags_.prevUpToDate; }
void setPrevUpToDate() { flags_.prevUpToDate = true; }
void unsetPrevUpToDate() { flags_.prevUpToDate = false; }
bool hasCachedSavedFrame() const { return flags_.hasCachedSavedFrame; }
void setHasCachedSavedFrame() { flags_.hasCachedSavedFrame = true; }
void clearHasCachedSavedFrame() { flags_.hasCachedSavedFrame = false; }
bool hasSpilledRegisterRefResult(size_t n) const {
uint32_t mask = hasSpilledRegisterRefResultBitMask(n);
return (flags_.allFlags & mask) != 0;
}
// DebugFrame is accessed directly by JIT code.
static constexpr size_t offsetOfRegisterResults() {
return offsetof(DebugFrame, registerResults_);
}
static constexpr size_t offsetOfRegisterResult(size_t n) {
MOZ_ASSERT(n < MaxRegisterResults);
return offsetOfRegisterResults() + n * sizeof(SpilledRegisterResult);
}
static constexpr size_t offsetOfCachedReturnJSValue() {
return offsetof(DebugFrame, cachedReturnJSValue_);
}
static constexpr size_t offsetOfStackResultsPointer() {
return offsetof(DebugFrame, stackResultsPointer_);
}
static constexpr size_t offsetOfFlags() {
return offsetof(DebugFrame, flags_);
}
static constexpr uint32_t hasSpilledRegisterRefResultBitMask(size_t n) {
MOZ_ASSERT(n < MaxRegisterResults);
union Flags flags = {.allFlags = 0};
flags.hasSpilledRefRegisterResult = 1 << n;
MOZ_ASSERT(flags.allFlags != 0);
return flags.allFlags;
}
static constexpr size_t offsetOfFuncIndex() {
return offsetof(DebugFrame, funcIndex_);
}
static constexpr size_t offsetOfFrame() {
return offsetof(DebugFrame, frame_);
}
// DebugFrames are aligned to 8-byte aligned, allowing them to be placed in
// an AbstractFramePtr.
static const unsigned Alignment = 8;
static void alignmentStaticAsserts();
};
// Verbose logging support.
extern void Log(JSContext* cx, const char* fmt, ...) MOZ_FORMAT_PRINTF(2, 3);

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

@ -27,6 +27,7 @@ UNIFIED_SOURCES += [
"WasmCompile.cpp",
"WasmContext.cpp",
"WasmDebug.cpp",
"WasmFrame.cpp",
"WasmFrameIter.cpp",
"WasmGC.cpp",
"WasmGenerator.cpp",