Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 2016 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
*
*
* 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_pi_h
#define wasm_pi_h
#include "mozilla/DoublyLinkedList.h" // for DoublyLinkedListElement
#include "js/TypeDecls.h"
#include "wasm/WasmAnyRef.h"
#include "wasm/WasmTypeDef.h"
// [SMDOC] JS Promise Integration
//
// The API provides relatively efficient and relatively ergonomic interop
// between JavaScript promises and WebAssembly but works under the constraint
// that the only changes are to the JS API and not to the core wasm.
//
// Secondary (suspendable) stacks are introduced at the entrance into the wasm
// code -- a promising function. A suspendable stack can contain/store only
// wasm frames and be part of one activation. If there is a need to execute
// a JS script, the stack must be switched back (to the main stack).
//
// There is a special exit from the suspendable stack where it is expected to
// receive a promise from a JS script -- a suspending function/import. If wasm
// code calls such import, the suspendable stack will be unlinked from
// the current activation allowing the main stack to continue returning to
// the event loop.
//
// Here is a small example that uses JS Promise Integration API:
//
// const suspending = new WebAssembly.Suspending(async () => 42)
// const ins = wasmTextEval(`(module
// (import "" "suspending" (func $imp (result i32)))
// (func (export "entry") (result i32) (call $imp))
// )`, {"": { suspending, }})
// const promising = WebAssembly.promising(ins.exports.entry)
// assertEq(await promising(), 42)
//
// The states transitions can be described by the following diagram:
//
// Invoke
// Promising Promise
// +-------+ Export +----------+ Resolved +---------+
// |Initial+----------->|Wasm Logic|<-----------+Suspended|
// +-------+ ++-+------++ +---------+
// | | ^ |Invoke ^ Suspending Function
// Return from| | | |Suspending | Returns a Promise
// +--------+ Wasm Call | | | |Import +----+---+
// |Finished|<-----------+ | | +------------>|JS Logic|
// +--------+ | | +----+---+
// +------------+ | | Re-entry
// |Invoke Other |Return +------>
// |Import +--+-----+
// +---------->|JS Logic|
// +--------+
//
// The Invoke Promising Export transition creates a suspendable stack,
// switches to it, and continues execution of wasm code there. When the callee
// frame is popped, the promise is returned to the JS caller.
//
// The Invoke Suspending Import switches stack to the main one and sets
// the suspended stack aside. It is expected that the suspending promise
// is returned by JS. The callee returns to the moment the promising call was
// instantiated.
//
// The Return from Wasm Call transition destroys the suspendable stack,
// continues execution on the main stack, and resolves the promising promise
// with the results of the call.
//
// The Promise Resolve transition is invoked when the suspending promise is
// resolved, which wakes the suspendable stack and extends the main one.
// The execution will continue on the suspendable stack that returns the
// resolution values to the wasm code as return values.
//
// The Invoke Other Import transition temporary switches to the main stack and
// invokes the JS code. The suspendable stack will not be removed from the
// chain of frames.
//
// Notice that calling wasm and then invoking a suspendable import from
// the main stack is not allowed. For example, re-exporting $imp, from
// the small example above, and calling it directly from the JS main thread
// will fail.
//
// The `new WebAssembly.Suspending(fn)` logic is implemented in a Wasm module
// generated by `SuspendingFunctionModuleFactory` utility (see WasmPI.cpp).
// The `WebAssembly.promising(wasmfn)` logic is implemented in a Wasm module
// generated by `PromisingFunctionModuleFactory` utility.
namespace js {
class PromiseObject;
class WasmStructObject;
namespace wasm {
class SuspenderContext;
class SuspenderObject;
static const uint32_t SuspenderObjectDataSlot = 0;
enum SuspenderState {
Initial,
Moribund,
Active,
Suspended,
};
class SuspenderObjectData
: public mozilla::DoublyLinkedListElement<SuspenderObjectData> {
void* stackMemory_;
// Stored main stack FP register.
void* mainFP_;
// Stored main stack SP register.
void* mainSP_;
// Stored suspendable stack FP register.
void* suspendableFP_;
// Stored suspendable stack SP register.
void* suspendableSP_;
// Stored suspendable stack exit/bottom frame pointer.
void* suspendableExitFP_;
// Stored return address for return to suspendable stack.
void* suspendedReturnAddress_;
// Stored main stack exit/top frame pointer.
void* mainExitFP_;
SuspenderState state_;
// Identify context that is holding suspended stack, otherwise nullptr.
SuspenderContext* suspendedBy_;
#if defined(_WIN32)
// The storage of main stack limits during stack switching.
// See updateTibFields and restoreTibFields below.
void* savedStackBase_;
void* savedStackLimit_;
#endif
public:
explicit SuspenderObjectData(void* stackMemory);
inline SuspenderState state() const { return state_; }
void setState(SuspenderState state) { state_ = state; }
inline bool traceable() const { return suspendedBy_ != nullptr; }
inline SuspenderContext* suspendedBy() const { return suspendedBy_; }
void setSuspendedBy(SuspenderContext* suspendedBy) {
suspendedBy_ = suspendedBy;
}
inline void* stackMemory() const { return stackMemory_; }
inline void* mainFP() const { return mainFP_; }
inline void* mainSP() const { return mainSP_; }
inline void* mainExitFP() const { return mainExitFP_; }
inline void* suspendableFP() const { return suspendableFP_; }
inline void* suspendableSP() const { return suspendableSP_; }
inline void* suspendableExitFP() const { return suspendableExitFP_; }
inline void* suspendedReturnAddress() const {
return suspendedReturnAddress_;
}
void releaseStackMemory();
#if defined(_WIN32)
void updateTIBStackFields();
void restoreTIBStackFields();
#endif
#if defined(JS_SIMULATOR_ARM64) || defined(JS_SIMULATOR_ARM)
void switchSimulatorToMain();
void switchSimulatorToSuspendable();
#endif
static constexpr size_t offsetOfMainFP() {
return offsetof(SuspenderObjectData, mainFP_);
}
static constexpr size_t offsetOfMainSP() {
return offsetof(SuspenderObjectData, mainSP_);
}
static constexpr size_t offsetOfSuspendableFP() {
return offsetof(SuspenderObjectData, suspendableFP_);
}
static constexpr size_t offsetOfSuspendableSP() {
return offsetof(SuspenderObjectData, suspendableSP_);
}
static constexpr size_t offsetOfSuspendableExitFP() {
return offsetof(SuspenderObjectData, suspendableExitFP_);
}
static constexpr size_t offsetOfMainExitFP() {
return offsetof(SuspenderObjectData, mainExitFP_);
}
static constexpr size_t offsetOfSuspendedReturnAddress() {
return offsetof(SuspenderObjectData, suspendedReturnAddress_);
}
};
#ifdef ENABLE_WASM_JSPI
using CallOnMainStackFn = bool (*)(void* data);
bool CallOnMainStack(JSContext* cx, CallOnMainStackFn fn, void* data);
JSFunction* WasmSuspendingFunctionCreate(JSContext* cx, HandleObject func,
wasm::ValTypeVector&& params,
wasm::ValTypeVector&& results);
JSFunction* WasmSuspendingFunctionCreate(JSContext* cx, HandleObject func,
const FuncType& type);
JSFunction* WasmPromisingFunctionCreate(JSContext* cx, HandleObject func,
wasm::ValTypeVector&& params,
wasm::ValTypeVector&& results);
SuspenderObject* CurrentSuspender(Instance* instance, int reserved);
SuspenderObject* CreateSuspender(Instance* instance, int reserved);
PromiseObject* CreatePromisingPromise(Instance* instance,
SuspenderObject* suspender);
JSObject* GetSuspendingPromiseResult(Instance* instance, void* result,
SuspenderObject* suspender);
void* AddPromiseReactions(Instance* instance, SuspenderObject* suspender,
void* result, JSFunction* continueOnSuspendable);
void* ForwardExceptionToSuspended(Instance* instance,
SuspenderObject* suspender, void* exception);
int32_t SetPromisingPromiseResults(Instance* instance,
SuspenderObject* suspender,
WasmStructObject* results);
void UpdateSuspenderState(Instance* instance, SuspenderObject* suspender,
UpdateSuspenderStateAction action);
#endif // ENABLE_WASM_JSPI
} // namespace wasm
} // namespace js
#endif // wasm_pi_h