promise-misc.tq 9.26 KB
Newer Older
1 2 3 4 5 6 7 8
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include 'src/builtins/builtins-promise.h'
#include 'src/builtins/builtins-promise-gen.h'

namespace runtime {
9 10
extern transitioning runtime
AllowDynamicFunction(implicit context: Context)(JSAny): JSAny;
11 12 13 14
}

// Unsafe functions that should be used very carefully.
namespace promise_internal {
15
extern macro PromiseBuiltinsAssembler::ZeroOutEmbedderOffsets(JSPromise): void;
16

17
extern macro PromiseBuiltinsAssembler::AllocateJSPromise(Context): HeapObject;
18 19 20
}

namespace promise {
21
extern macro IsFunctionWithPrototypeSlotMap(Map): bool;
22

23 24 25 26
@export
macro PromiseHasHandler(promise: JSPromise): bool {
  return promise.HasHandler();
}
27

28 29 30 31 32 33 34 35 36 37 38
@export
macro PromiseInit(promise: JSPromise): void {
  promise.reactions_or_result = kZero;
  promise.flags = SmiTag(JSPromiseFlags{
    status: PromiseState::kPending,
    has_handler: false,
    handled_hint: false,
    async_task_id: 0
  });
  promise_internal::ZeroOutEmbedderOffsets(promise);
}
39

40
macro InnerNewJSPromise(implicit context: Context)(): JSPromise {
41
  const promiseFun = *NativeContextSlot(ContextSlot::PROMISE_FUNCTION_INDEX);
42 43 44
  assert(IsFunctionWithPrototypeSlotMap(promiseFun.map));
  const promiseMap = UnsafeCast<Map>(promiseFun.prototype_or_initial_map);
  const promiseHeapObject = promise_internal::AllocateJSPromise(context);
45
  *UnsafeConstCast(&promiseHeapObject.map) = promiseMap;
46 47 48 49 50 51 52 53 54 55 56 57
  const promise = UnsafeCast<JSPromise>(promiseHeapObject);
  promise.properties_or_hash = kEmptyFixedArray;
  promise.elements = kEmptyFixedArray;
  promise.reactions_or_result = kZero;
  promise.flags = SmiTag(JSPromiseFlags{
    status: PromiseState::kPending,
    has_handler: false,
    handled_hint: false,
    async_task_id: 0
  });
  return promise;
}
58

59 60 61 62 63 64 65 66 67 68 69
macro NewPromiseFulfillReactionJobTask(implicit context: Context)(
    handlerContext: Context, argument: Object, handler: Callable|Undefined,
    promiseOrCapability: JSPromise|PromiseCapability|
    Undefined): PromiseFulfillReactionJobTask {
  const nativeContext = LoadNativeContext(handlerContext);
  return new PromiseFulfillReactionJobTask{
    map: PromiseFulfillReactionJobTaskMapConstant(),
    argument,
    context: handlerContext,
    handler,
    promise_or_capability: promiseOrCapability,
70
    continuation_preserved_embedder_data:
71 72
        *ContextSlot(
        nativeContext, ContextSlot::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX)
73 74
  };
}
75

76 77 78 79 80 81 82 83 84 85 86
macro NewPromiseRejectReactionJobTask(implicit context: Context)(
    handlerContext: Context, argument: Object, handler: Callable|Undefined,
    promiseOrCapability: JSPromise|PromiseCapability|
    Undefined): PromiseRejectReactionJobTask {
  const nativeContext = LoadNativeContext(handlerContext);
  return new PromiseRejectReactionJobTask{
    map: PromiseRejectReactionJobTaskMapConstant(),
    argument,
    context: handlerContext,
    handler,
    promise_or_capability: promiseOrCapability,
87
    continuation_preserved_embedder_data:
88 89
        *ContextSlot(
        nativeContext, ContextSlot::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX)
90 91
  };
}
92

93 94 95 96 97 98 99 100 101 102 103 104
// These allocate and initialize a promise with pending state and
// undefined fields.
//
// This uses the given parent as the parent promise for the promise
// init hook.
@export
transitioning macro NewJSPromise(implicit context: Context)(parent: Object):
    JSPromise {
  const instance = InnerNewJSPromise();
  PromiseInit(instance);
  if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) {
    runtime::PromiseHookInit(instance, parent);
105
  }
106 107
  return instance;
}
108

109 110 111 112 113 114
// This uses undefined as the parent promise for the promise init
// hook.
@export
transitioning macro NewJSPromise(implicit context: Context)(): JSPromise {
  return NewJSPromise(Undefined);
}
115

116 117 118 119 120 121
// This allocates and initializes a promise with the given state and
// fields.
@export
transitioning macro NewJSPromise(implicit context: Context)(
    status: constexpr PromiseState, result: JSAny): JSPromise {
  assert(status != PromiseState::kPending);
122

123 124 125 126
  const instance = InnerNewJSPromise();
  instance.reactions_or_result = result;
  instance.SetStatus(status);
  promise_internal::ZeroOutEmbedderOffsets(instance);
127

128 129
  if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) {
    runtime::PromiseHookInit(instance, Undefined);
130
  }
131 132
  return instance;
}
133

134 135 136 137 138 139 140 141 142 143 144 145
macro NewPromiseReaction(implicit context: Context)(
    handlerContext: Context, next: Zero|PromiseReaction,
    promiseOrCapability: JSPromise|PromiseCapability|Undefined,
    fulfillHandler: Callable|Undefined,
    rejectHandler: Callable|Undefined): PromiseReaction {
  const nativeContext = LoadNativeContext(handlerContext);
  return new PromiseReaction{
    map: PromiseReactionMapConstant(),
    next: next,
    reject_handler: rejectHandler,
    fulfill_handler: fulfillHandler,
    promise_or_capability: promiseOrCapability,
146
    continuation_preserved_embedder_data:
147 148
        *ContextSlot(
        nativeContext, ContextSlot::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX)
149 150
  };
}
151

152
extern macro PromiseResolveThenableJobTaskMapConstant(): Map;
153

154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
// https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob
macro NewPromiseResolveThenableJobTask(implicit context: Context)(
    promiseToResolve: JSPromise, thenable: JSReceiver,
    then: Callable): PromiseResolveThenableJobTask {
  // 2. Let getThenRealmResult be GetFunctionRealm(then).
  // 3. If getThenRealmResult is a normal completion, then let thenRealm be
  //    getThenRealmResult.[[Value]].
  // 4. Otherwise, let thenRealm be null.
  //
  // The only cases where |thenRealm| can be null is when |then| is a revoked
  // Proxy object, which would throw when it is called anyway. So instead of
  // setting the context to null as the spec does, we just use the current
  // realm.
  const thenContext: Context = ExtractHandlerContext(then);
  const nativeContext = LoadNativeContext(thenContext);
169

170 171 172 173 174 175 176 177 178 179 180
  // 1. Let job be a new Job abstract closure with no parameters that
  //    captures promiseToResolve, thenable, and then...
  // 5. Return { [[Job]]: job, [[Realm]]: thenRealm }.
  return new PromiseResolveThenableJobTask{
    map: PromiseResolveThenableJobTaskMapConstant(),
    context: nativeContext,
    promise_to_resolve: promiseToResolve,
    thenable,
    then
  };
}
181

182 183 184 185 186 187
struct InvokeThenOneArgFunctor {
  transitioning
  macro Call(
      nativeContext: NativeContext, then: JSAny, receiver: JSAny, arg1: JSAny,
      _arg2: JSAny): JSAny {
    return Call(nativeContext, then, receiver, arg1);
188
  }
189
}
190

191 192 193 194 195 196
struct InvokeThenTwoArgFunctor {
  transitioning
  macro Call(
      nativeContext: NativeContext, then: JSAny, receiver: JSAny, arg1: JSAny,
      arg2: JSAny): JSAny {
    return Call(nativeContext, then, receiver, arg1, arg2);
197
  }
198
}
199

200 201 202 203 204 205 206 207 208 209 210
transitioning
macro InvokeThen<F: type>(implicit context: Context)(
    nativeContext: NativeContext, receiver: JSAny, arg1: JSAny, arg2: JSAny,
    callFunctor: F): JSAny {
  // We can skip the "then" lookup on {receiver} if it's [[Prototype]]
  // is the (initial) Promise.prototype and the Promise#then protector
  // is intact, as that guards the lookup path for the "then" property
  // on JSPromise instances which have the (initial) %PromisePrototype%.
  if (!Is<Smi>(receiver) &&
      IsPromiseThenLookupChainIntact(
          nativeContext, UnsafeCast<HeapObject>(receiver).map)) {
211 212
    const then =
        *NativeContextSlot(nativeContext, ContextSlot::PROMISE_THEN_INDEX);
213 214 215 216
    return callFunctor.Call(nativeContext, then, receiver, arg1, arg2);
  } else
    deferred {
      const then = UnsafeCast<JSAny>(GetProperty(receiver, kThenString));
217
      return callFunctor.Call(nativeContext, then, receiver, arg1, arg2);
218 219
    }
}
220

221 222 223 224 225 226
transitioning
macro InvokeThen(implicit context: Context)(
    nativeContext: NativeContext, receiver: JSAny, arg: JSAny): JSAny {
  return InvokeThen(
      nativeContext, receiver, arg, Undefined, InvokeThenOneArgFunctor{});
}
227

228 229 230 231 232 233 234
transitioning
macro InvokeThen(implicit context: Context)(
    nativeContext: NativeContext, receiver: JSAny, arg1: JSAny,
    arg2: JSAny): JSAny {
  return InvokeThen(
      nativeContext, receiver, arg1, arg2, InvokeThenTwoArgFunctor{});
}
235

236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
transitioning
macro BranchIfAccessCheckFailed(implicit context: Context)(
    nativeContext: NativeContext, promiseConstructor: JSAny,
    executor: JSAny): void labels IfNoAccess {
  try {
    // If executor is a bound function, load the bound function until we've
    // reached an actual function.
    let foundExecutor = executor;
    while (true) {
      typeswitch (foundExecutor) {
        case (f: JSFunction): {
          // Load the context from the function and compare it to the Promise
          // constructor's context. If they match, everything is fine,
          // otherwise, bail out to the runtime.
          const functionContext = f.context;
          const nativeFunctionContext = LoadNativeContext(functionContext);
          if (TaggedEqual(nativeContext, nativeFunctionContext)) {
            goto HasAccess;
          } else {
255 256 257
            goto CallRuntime;
          }
        }
258 259 260 261 262 263
        case (b: JSBoundFunction): {
          foundExecutor = b.bound_target_function;
        }
        case (Object): {
          goto CallRuntime;
        }
264
      }
265 266 267 268 269 270 271 272
    }
  } label CallRuntime deferred {
    const result = runtime::AllowDynamicFunction(promiseConstructor);
    if (result != True) {
      goto IfNoAccess;
    }
  } label HasAccess {}
}
273
}