tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

WasmPI.h (13881B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 *
      4 * Copyright 2016 Mozilla Foundation
      5 *
      6 * Licensed under the Apache License, Version 2.0 (the "License");
      7 * you may not use this file except in compliance with the License.
      8 * You may obtain a copy of the License at
      9 *
     10 *     http://www.apache.org/licenses/LICENSE-2.0
     11 *
     12 * Unless required by applicable law or agreed to in writing, software
     13 * distributed under the License is distributed on an "AS IS" BASIS,
     14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15 * See the License for the specific language governing permissions and
     16 * limitations under the License.
     17 */
     18 
     19 #ifndef wasm_pi_h
     20 #define wasm_pi_h
     21 
     22 #include "js/TypeDecls.h"
     23 #include "vm/NativeObject.h"
     24 #include "vm/PromiseObject.h"
     25 #include "wasm/WasmAnyRef.h"
     26 #include "wasm/WasmTypeDef.h"
     27 
     28 // [SMDOC] JS Promise Integration
     29 //
     30 // The API provides relatively efficient and relatively ergonomic interop
     31 // between JavaScript promises and WebAssembly but works under the constraint
     32 // that the only changes are to the JS API and not to the core wasm.
     33 //
     34 // Secondary (suspendable) stacks are introduced at the entrance into the wasm
     35 // code -- a promising function. A suspendable stack can contain/store only
     36 // wasm frames and be part of one activation. If there is a need to execute
     37 // a JS script, the stack must be switched back (to the main stack).
     38 //
     39 // There is a special exit from the suspendable stack where it is expected to
     40 // receive a promise from a JS script -- a suspending function/import. If wasm
     41 // code calls such import, the suspendable stack will be unlinked from
     42 // the current activation allowing the main stack to continue returning to
     43 // the event loop.
     44 //
     45 // Here is a small example that uses JS Promise Integration API:
     46 //
     47 //     const suspending = new WebAssembly.Suspending(async () => 42)
     48 //     const ins = wasmTextEval(`(module
     49 //         (import "" "suspending" (func $imp (result i32)))
     50 //         (func (export "entry") (result i32) (call $imp))
     51 //     )`, {"": { suspending, }})
     52 //     const promising = WebAssembly.promising(ins.exports.entry)
     53 //     assertEq(await promising(), 42)
     54 //
     55 // The states transitions can be described by the following diagram:
     56 //
     57 //               Invoke
     58 //               Promising                Promise
     59 //     +-------+ Export     +----------+  Resolved  +---------+
     60 //     |Initial+----------->|Wasm Logic|<-----------+Suspended|
     61 //     +-------+            ++-+------++            +---------+
     62 //                           | | ^    |Invoke            ^ Suspending Function
     63 //                Return from| | |    |Suspending        | Returns a Promise
     64 //     +--------+ Wasm Call  | | |    |Import       +----+---+
     65 //     |Finished|<-----------+ | |    +------------>|JS Logic|
     66 //     +--------+              | |                  +----+---+
     67 //                +------------+ |                       | Re-entry
     68 //                |Invoke Other  |Return                 +------>
     69 //                |Import     +--+-----+
     70 //                +---------->|JS Logic|
     71 //                            +--------+
     72 //
     73 // The Invoke Promising Export transition creates a suspendable stack,
     74 // switches to it, and continues execution of wasm code there. When the callee
     75 // frame is popped, the promise is returned to the JS caller.
     76 //
     77 // The Invoke Suspending Import switches stack to the main one and sets
     78 // the suspended stack aside. It is expected that the suspending promise
     79 // is returned by JS. The callee returns to the moment the promising call was
     80 // instantiated.
     81 //
     82 // The Return from Wasm Call transition destroys the suspendable stack,
     83 // continues execution on the main stack, and resolves the promising promise
     84 // with the results of the call.
     85 //
     86 // The Promise Resolve transition is invoked when the suspending promise is
     87 // resolved, which wakes the suspendable stack and extends the main one.
     88 // The execution will continue on the suspendable stack that returns the
     89 // resolution values to the wasm code as return values.
     90 //
     91 // The Invoke Other Import transition temporary switches to the main stack and
     92 // invokes the JS code. The suspendable stack will not be removed from the
     93 // chain of frames.
     94 //
     95 // Notice that calling wasm and then invoking a suspendable import from
     96 // the main stack is not allowed. For example, re-exporting $imp, from
     97 // the small example above, and calling it directly from the JS main thread
     98 // will fail.
     99 //
    100 // The `new WebAssembly.Suspending(fn)` logic is implemented in a Wasm module
    101 // generated by `SuspendingFunctionModuleFactory` utility (see WasmPI.cpp).
    102 // The `WebAssembly.promising(wasmfn)` logic is implemented in a Wasm module
    103 // generated by `PromisingFunctionModuleFactory` utility.
    104 
    105 namespace js {
    106 
    107 class WasmStructObject;
    108 
    109 namespace wasm {
    110 
    111 class Context;
    112 
    113 #ifdef ENABLE_WASM_JSPI
    114 
    115 enum SuspenderState : int32_t {
    116  // The suspender's has no stack or the stack has finished and has been
    117  // destroyed.
    118  Moribund,
    119  // The suspender's stack hasn't been entered yet.
    120  Initial,
    121  // The suspender's stack is active and is the currently active stack.
    122  Active,
    123  // The suspender's stack has been suspended and is no longer the active stack
    124  // and isn't linked to the active stack.
    125  Suspended,
    126  // The suspender's stack has switched back to the main stack for a call. It
    127  // is not the active call stack, but is linked to by the active stack.
    128  CalledOnMain,
    129 };
    130 
    131 class SuspenderObject : public NativeObject {
    132 public:
    133  static const JSClass class_;
    134 
    135  enum {
    136    StateSlot,
    137 
    138    PromisingPromiseSlot,
    139    SuspendingReturnTypeSlot,
    140 
    141    StackMemorySlot,
    142    MainFPSlot,
    143    MainSPSlot,
    144    SuspendableFPSlot,
    145    SuspendableSPSlot,
    146    SuspendableExitFPSlot,
    147    SuspendedRASlot,
    148    MainExitFPSlot,
    149 
    150    SlotCount,
    151  };
    152 
    153  enum class ReturnType : int32_t { Unknown, Promise, Exception };
    154 
    155  static SuspenderObject* create(JSContext* cx);
    156 
    157  SuspenderState state() const {
    158    return (SuspenderState)getFixedSlot(StateSlot).toInt32();
    159  }
    160  void setState(SuspenderState state) {
    161    setFixedSlot(StateSlot, JS::Int32Value((int32_t)state));
    162  }
    163 
    164  // This suspender can be traced if it's not 'Initial' or 'Moribund'.
    165  bool isTraceable() const {
    166    SuspenderState current = state();
    167    return current == SuspenderState::Active ||
    168           current == SuspenderState::Suspended ||
    169           current == SuspenderState::CalledOnMain;
    170  }
    171  bool isMoribund() const { return state() == SuspenderState::Moribund; }
    172  bool isActive() const { return state() == SuspenderState::Active; }
    173  bool isSuspended() const { return state() == SuspenderState::Suspended; }
    174  bool isCalledOnMain() const {
    175    return state() == SuspenderState::CalledOnMain;
    176  }
    177 
    178  PromiseObject* promisingPromise() const {
    179    return &getFixedSlot(PromisingPromiseSlot).toObject().as<PromiseObject>();
    180  }
    181  void setPromisingPromise(Handle<PromiseObject*> promise) {
    182    setFixedSlot(PromisingPromiseSlot, ObjectOrNullValue(promise));
    183  }
    184 
    185  ReturnType suspendingReturnType() const {
    186    return ReturnType(getFixedSlot(SuspendingReturnTypeSlot).toInt32());
    187  }
    188  void setSuspendingReturnType(ReturnType type) {
    189    // The SuspendingReturnTypeSlot will change after result is defined,
    190    // and becomes invalid after GetSuspendingPromiseResult. The assert is
    191    // checking if the result was processed by GetSuspendingPromiseResult.
    192    MOZ_ASSERT((type == ReturnType::Unknown) !=
    193               (suspendingReturnType() == ReturnType::Unknown));
    194 
    195    setFixedSlot(SuspendingReturnTypeSlot, Int32Value(int32_t(type)));
    196  }
    197 
    198  // Pointer to the beginning of the stack memory allocation.
    199  void* stackMemory() const {
    200    return getFixedSlot(StackMemorySlot).toPrivate();
    201  }
    202  void setStackMemory(void* stackMemory) {
    203    setFixedSlot(StackMemorySlot, PrivateValue(stackMemory));
    204  }
    205 
    206  // The logical beginning or bottom of the stack, which is the physically
    207  // highest memory address in the stack allocation.
    208  JS::NativeStackLimit stackMemoryBase() const {
    209    return ((uintptr_t)stackMemory()) + SuspendableStackPlusRedZoneSize;
    210  }
    211  // The logical end or top of the stack for system code, which is the
    212  // physically lowest memory address in the stack allocation. This does not
    213  // include any 'red zone' space, and so it is not safe to use if a stub
    214  // or OS interrupt handler could run on the stack. Use
    215  // `stackMemoryLimitForJit` instead.
    216  JS::NativeStackLimit stackMemoryLimitForSystem() const {
    217    return JS::NativeStackLimit(stackMemory());
    218  }
    219  // The logical end or top of the stack for JIT code, which is the
    220  // physically lowest memory address in the stack allocation. This does
    221  // include 'red zone' space for running stubs or OS interrupt handlers.
    222  JS::NativeStackLimit stackMemoryLimitForJit() const {
    223    return stackMemoryLimitForSystem() + SuspendableRedZoneSize;
    224  }
    225 
    226  bool hasStackAddress(const void* stackAddress) const {
    227    MOZ_ASSERT(!isMoribund());
    228    void* base = stackMemory();
    229    return (uintptr_t)base <= (uintptr_t)stackAddress &&
    230           (uintptr_t)stackAddress <
    231               (uintptr_t)base + SuspendableStackPlusRedZoneSize;
    232  }
    233 
    234  // Stored main stack FP register.
    235  void* mainFP() const {
    236    MOZ_ASSERT(isActive());
    237    return getFixedSlot(MainFPSlot).toPrivate();
    238  }
    239  // Stored main stack SP register.
    240  void* mainSP() const {
    241    MOZ_ASSERT(isActive());
    242    return getFixedSlot(MainSPSlot).toPrivate();
    243  }
    244  // Stored main stack exit/top frame pointer.
    245  void* mainExitFP() const {
    246    MOZ_ASSERT(isSuspended());
    247    return getFixedSlot(MainExitFPSlot).toPrivate();
    248  }
    249  // Stored suspendable stack FP register.
    250  void* suspendableFP() const {
    251    MOZ_ASSERT(isSuspended());
    252    return getFixedSlot(SuspendableFPSlot).toPrivate();
    253  }
    254  // Stored suspendable stack SP register.
    255  void* suspendableSP() const {
    256    MOZ_ASSERT(isSuspended());
    257    return getFixedSlot(SuspendableSPSlot).toPrivate();
    258  }
    259  // Stored return address for return to suspendable stack.
    260  void* suspendedReturnAddress() const {
    261    MOZ_ASSERT(isSuspended());
    262    return getFixedSlot(SuspendedRASlot).toPrivate();
    263  }
    264  // Stored suspendable stack exit/bottom frame pointer.
    265  void* suspendableExitFP() const {
    266    // We always have the root frame when we've been entered into, which is
    267    // when we're traceable.
    268    MOZ_ASSERT(isTraceable());
    269    return getFixedSlot(SuspendableExitFPSlot).toPrivate();
    270  }
    271 
    272  static constexpr size_t offsetOfState() {
    273    return getFixedSlotOffset(StateSlot);
    274  }
    275  static constexpr size_t offsetOfStackMemory() {
    276    return getFixedSlotOffset(StackMemorySlot);
    277  }
    278  static constexpr size_t offsetOfMainFP() {
    279    return getFixedSlotOffset(MainFPSlot);
    280  }
    281  static constexpr size_t offsetOfMainSP() {
    282    return getFixedSlotOffset(MainSPSlot);
    283  }
    284  static constexpr size_t offsetOfSuspendableFP() {
    285    return getFixedSlotOffset(SuspendableFPSlot);
    286  }
    287  static constexpr size_t offsetOfSuspendableSP() {
    288    return getFixedSlotOffset(SuspendableSPSlot);
    289  }
    290  static constexpr size_t offsetOfSuspendableExitFP() {
    291    return getFixedSlotOffset(SuspendableExitFPSlot);
    292  }
    293  static constexpr size_t offsetOfMainExitFP() {
    294    return getFixedSlotOffset(MainExitFPSlot);
    295  }
    296  static constexpr size_t offsetOfSuspendedReturnAddress() {
    297    return getFixedSlotOffset(SuspendedRASlot);
    298  }
    299 
    300  void setMoribund(JSContext* cx);
    301  void setActive(JSContext* cx);
    302  void setSuspended(JSContext* cx);
    303 
    304  void enter(JSContext* cx);
    305  void suspend(JSContext* cx);
    306  void resume(JSContext* cx);
    307  void leave(JSContext* cx);
    308  void unwind(JSContext* cx);
    309 
    310  void releaseStackMemory();
    311 
    312  // Modifies frames to inject the suspendable stack back into the main one.
    313  void forwardToSuspendable();
    314 
    315 private:
    316  static const JSClassOps classOps_;
    317  static const ClassExtension classExt_;
    318 
    319  static void finalize(JS::GCContext* gcx, JSObject* obj);
    320  static void trace(JSTracer* trc, JSObject* obj);
    321  static size_t moved(JSObject* obj, JSObject* old);
    322 };
    323 
    324 JSFunction* WasmSuspendingFunctionCreate(JSContext* cx, HandleObject func,
    325                                         wasm::ValTypeVector&& params,
    326                                         wasm::ValTypeVector&& results);
    327 
    328 JSFunction* WasmSuspendingFunctionCreate(JSContext* cx, HandleObject func,
    329                                         const FuncType& type);
    330 
    331 JSFunction* WasmPromisingFunctionCreate(JSContext* cx, HandleObject func,
    332                                        wasm::ValTypeVector&& params,
    333                                        wasm::ValTypeVector&& results);
    334 
    335 SuspenderObject* CurrentSuspender(Instance* instance, int reserved);
    336 
    337 SuspenderObject* CreateSuspender(Instance* instance, int reserved);
    338 
    339 PromiseObject* CreatePromisingPromise(Instance* instance,
    340                                      SuspenderObject* suspender);
    341 
    342 JSObject* GetSuspendingPromiseResult(Instance* instance, void* result,
    343                                     SuspenderObject* suspender);
    344 
    345 void* AddPromiseReactions(Instance* instance, SuspenderObject* suspender,
    346                          void* result, JSFunction* continueOnSuspendable);
    347 
    348 void* ForwardExceptionToSuspended(Instance* instance,
    349                                  SuspenderObject* suspender, void* exception);
    350 
    351 int32_t SetPromisingPromiseResults(Instance* instance,
    352                                   SuspenderObject* suspender,
    353                                   WasmStructObject* results);
    354 
    355 void UpdateSuspenderState(Instance* instance, SuspenderObject* suspender,
    356                          UpdateSuspenderStateAction action);
    357 
    358 void TraceSuspendableStack(JSTracer* trc, SuspenderObject* suspender);
    359 
    360 #else
    361 
    362 // Provide an empty forward declaration to simplify some conditional
    363 // compilation code.
    364 class SuspenderObject;
    365 
    366 #endif  // ENABLE_WASM_JSPI
    367 
    368 }  // namespace wasm
    369 }  // namespace js
    370 
    371 #endif  // wasm_pi_h