blob: e73145acb85b5fddad1810996fb47dd402da7572 [file] [log] [blame]
/*
* Copyright (C) 2025 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#if ENABLE(WEBASSEMBLY)
#include <JavaScriptCore/CallFrame.h>
#include <JavaScriptCore/JSGlobalObject.h>
#include <JavaScriptCore/Register.h>
#include <JavaScriptCore/VMEntryRecord.h>
#include <wtf/TrailingArray.h>
#include <wtf/Vector.h>
namespace JSC {
// TERMINOLOGY: 'top' and 'bottom' are ambiguous terms when stacks are involved. To agree
// with the VM naming convention, we use these terms in the physical (memory address)
// sense, so the 'bottom' frame is the most recently executed one (top of the call stack).
// A fragment of the main execution stack copied to the heap as a unit. A slice may
// include one or more frames. In addition to the actual copied stack data, it carries
// metadata that will allow us to implant the slice back onto the execution stack and kick
// off the execution of the bottom frame. Instances are created by StackSlicer.
class EvacuatedStackSlice final : public TrailingArray<EvacuatedStackSlice, Register> {
WTF_DEPRECATED_MAKE_FAST_ALLOCATED(EvacuatedStackSlice);
friend class LLIntOffsetsExtractor;
public:
using Base = TrailingArray<EvacuatedStackSlice, Register>;
static std::unique_ptr<EvacuatedStackSlice> create(std::span<Register> stackSpan, Vector<unsigned>&& frameOffsets, const CallFrame* frametoReturnFromForEntry);
std::span<Register> slots();
// Offsets of frame records in the trailing array data, in units of Register size.
// Ordered from lowest to highest (shallowest to deepest frames).
const Vector<unsigned>& frameOffsets() const { return m_frameOffsets; }
// Copy the stack data captured by this instance to the memory location identified by
// 'base' and prepare it for execution by relocating all internal pointers. Link the
// top frame to return to the specified 'previousFrame' (running _exit_implanted_slice
// function implemented in InPlaceInterpreter.asm).
CallFrame* implant(Register* base, CallFrame* previousFrame);
// The PC to return to to enter the top (logically) frame of the slice.
// The value as it was on the original stack--if PAC is in use, signed by 'pacibsp'.
const void* entryPC() const { return m_entryPC; };
// The address of the original frame that contained entryPC. Saved for authenticating entryPC.
// 'void*' because the frame does not exist anymore--do not dereference!
const void* entryPCFrame() const { return m_entryPCFrame; }
void dump(PrintStream& out) const;
private:
friend class StackSlicerBase;
EvacuatedStackSlice(std::span<Register> stackSpan, Vector<unsigned>&& frameOffsets, const CallFrame* frameToReturnFromForEntry);
const Register* m_originalBase;
Vector<unsigned> m_frameOffsets;
const void* m_entryPC;
const void* m_entryPCFrame;
};
WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN
inline std::span<Register> EvacuatedStackSlice::slots()
{
return { &first(), size() };
}
WTF_ALLOW_UNSAFE_BUFFER_USAGE_END
// Authenticate 'returnPC' assuming is was stored in a frame pointed at by 'originalFP', and
// re-sign it so it can be used in a frame pointed at by 'newFP'.
void* relocateReturnPC(void* returnPC, const CallerFrameAndPC* originalFP, const CallerFrameAndPC* newFP);
// An abstract class with a set of utilities for implementing a concrete stack slicer.
// A stack slicer is a class driven by a StackVisitor via a StackSlicerFunctor. It
// walks the stack from a Suspending frame to a Promising or PinballHandler frame and
// moves the frames to the heap as a series of EvacuatedStackSlices. A concrete slicer
// class determines the policy of how exactly the frames on the stack are grouped into
// slices.
class StackSlicerBase {
public:
const String& errorMessage() const { return m_errorMessage; }
const Vector<std::unique_ptr<EvacuatedStackSlice>>& slices() const { return m_slices; }
// Return the accumulated slices in the order from top to bottom. This places the
// first slice to execute at the end of the vector, and the vector acts as a stack
// with pop=takeLast() and push=append().
Vector<std::unique_ptr<EvacuatedStackSlice>> reverseAndTakeSlices();
// After the slices are evacuated, we skip over them by returnin into this frame.
CallFrame* teleportFrame() const { return m_teleportFrame; }
protected:
// Create a slice for the stack area identified by the future bottom and top pointers.
// Include extra 'headroomSlotCount' registers above the actual frame top pointer.
// The amount of the headroom is dictated by the frame callee.
std::unique_ptr<EvacuatedStackSlice> evacuatePendingSlice(unsigned headroomSlotCount);
void commitPendingSliceWithAdditionalFrame(CallFrame*);
void commitPendingSlice();
String m_errorMessage { "?"_s };
Vector<std::unique_ptr<EvacuatedStackSlice>> m_slices;
const CallFrame* m_lastVisitedFrame { nullptr };
const Register* m_futureSliceBottom { nullptr };
const Register* m_futureSliceTop { nullptr };
const CallFrame* m_futureReturnFromFrame { nullptr };
Vector<CallFrame*> m_pendingFrameRecords;
CallFrame* m_teleportFrame;
};
template<typename T>
concept ConcreteStackSlicer =
std::derived_from<T, StackSlicerBase>
&& requires(T& t, VM& vm, StackVisitor& sv) {
// IterationStatus step(VM&, StackVisitor&);
{ t.step(vm, sv) } -> std::same_as<IterationStatus>;
};
// A concrete stack slicer that evacuates the stack as a single slice that contains all
// interesting frames.
class SlabSlicer final : public StackSlicerBase {
public:
IterationStatus step(VM&, StackVisitor&);
bool succeeded() const { return m_state == State::Success; }
bool didOverrun() const { return m_state == State::Overrun; }
private:
enum class State {
Initial,
Scanning,
ScannedJSToWasm,
// The following are the three final states
Success,
Overrun, // traversed all Wasm frames but did not reach limitFrame
Failure
};
State m_state { State::Initial };
};
// A concrete stack slicer that evacuates the stack such that each Wasm frame
// gets a slice of its own, except for the topmost and bottommost Wasm frames
// which are combined with the adjacent WasmToJS and JSToWasm frames.
class FragSlicer final : public StackSlicerBase {
public:
IterationStatus step(VM&, StackVisitor&);
bool succeeded() const { return m_state == State::Success; }
bool didOverrun() const { return m_state == State::Overrun; }
private:
enum class State {
Initial,
ScannedSuspending,
ScannedWasmToJS,
ScanningWasm,
ScannedJSToWasm,
// The following are the three final states
Success,
Overrun,
Failure
};
State m_state { State::Initial };
};
// A functor given to the standard StackVisitor to drive a concrete stack slicer.
template<ConcreteStackSlicer Slicer>
class StackSlicerFunctor final : UnwindFunctorBase {
public:
StackSlicerFunctor(VM& vm, Slicer& scanner)
: UnwindFunctorBase(vm)
, m_scanner(scanner)
{ }
IterationStatus operator() (StackVisitor&) const;
private:
Slicer& m_scanner;
};
template <ConcreteStackSlicer Slicer>
IterationStatus StackSlicerFunctor<Slicer>::operator() (StackVisitor& visitor) const
{
visitor.unwindToMachineCodeBlockFrame();
IterationStatus result = m_scanner.step(m_vm, visitor);
if (result == IterationStatus::Continue) {
auto* currentFrame = visitor->callFrame();
JSGlobalObject* lexicalGlobalObject = currentFrame->lexicalGlobalObject(m_vm);
notifyDebuggerOfUnwinding(lexicalGlobalObject, currentFrame);
copyCalleeSavesToEntryFrameCalleeSavesBuffer(visitor);
}
return result;
}
} // namespace JSC
#endif // ENABLE(WEBASSEMBLY)