summaryrefslogtreecommitdiff
path: root/deps/v8/src/builtins/promise-resolve.tq
blob: 768691ecaa48368f6e7978a77e58205739a9a7eb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
// 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-gen.h'

namespace runtime {
extern transitioning runtime
ResolvePromise(implicit context: Context)(JSPromise, JSAny): JSAny;
}

namespace promise {
extern macro ConstructorStringConstant(): String;
const kConstructorString: String = ConstructorStringConstant();

// https://tc39.es/ecma262/#sec-promise.resolve
transitioning javascript builtin
PromiseResolveTrampoline(
    js-implicit context: NativeContext, receiver: JSAny)(value: JSAny): JSAny {
  // 1. Let C be the this value.
  // 2. If Type(C) is not Object, throw a TypeError exception.
  const receiver = Cast<JSReceiver>(receiver) otherwise
  ThrowTypeError(MessageTemplate::kCalledOnNonObject, 'PromiseResolve');

  // 3. Return ? PromiseResolve(C, x).
  return PromiseResolve(receiver, value);
}

transitioning builtin
PromiseResolve(implicit context: Context)(
    constructor: JSReceiver, value: JSAny): JSAny {
  const nativeContext = LoadNativeContext(context);
  const promiseFun = *NativeContextSlot(
      nativeContext, ContextSlot::PROMISE_FUNCTION_INDEX);
  try {
    // Check if {value} is a JSPromise.
    const value = Cast<JSPromise>(value) otherwise NeedToAllocate;

    // We can skip the "constructor" lookup on {value} if it's [[Prototype]]
    // is the (initial) Promise.prototype and the @@species protector is
    // intact, as that guards the lookup path for "constructor" on
    // JSPromise instances which have the (initial) Promise.prototype.
    const promisePrototype =
        *NativeContextSlot(
        nativeContext, ContextSlot::PROMISE_PROTOTYPE_INDEX);
    // Check that Torque load elimination works.
    static_assert(nativeContext == LoadNativeContext(context));
    if (value.map.prototype != promisePrototype) {
      goto SlowConstructor;
    }

    if (IsPromiseSpeciesProtectorCellInvalid()) goto SlowConstructor;

    // If the {constructor} is the Promise function, we just immediately
    // return the {value} here and don't bother wrapping it into a
    // native Promise.
    if (promiseFun != constructor) goto SlowConstructor;
    return value;
  } label SlowConstructor deferred {
    // At this point, value or/and constructor are not native promises, but
    // they could be of the same subclass.
    const valueConstructor = GetProperty(value, kConstructorString);
    if (valueConstructor != constructor) goto NeedToAllocate;
    return value;
  } label NeedToAllocate {
    if (promiseFun == constructor) {
      // This adds a fast path for native promises that don't need to
      // create NewPromiseCapability.
      const result = NewJSPromise();
      ResolvePromise(context, result, value);
      return result;
    } else
      deferred {
        const capability = NewPromiseCapability(constructor, True);
        const resolve = UnsafeCast<Callable>(capability.resolve);
        Call(context, resolve, Undefined, value);
        return capability.promise;
      }
  }
}

extern macro IsJSReceiverMap(Map): bool;
extern macro JSAnyIsNotPrimitiveMap(Map): bool;

extern macro IsPromiseThenProtectorCellInvalid(): bool;

extern macro ThenStringConstant(): String;

const kThenString: String = ThenStringConstant();

// https://tc39.es/ecma262/#sec-promise-resolve-functions
transitioning builtin
ResolvePromise(implicit context: Context)(
    promise: JSPromise, resolution: JSAny): JSAny {
  // 7. If SameValue(resolution, promise) is true, then
  // If promise hook is enabled or the debugger is active, let
  // the runtime handle this operation, which greatly reduces
  // the complexity here and also avoids a couple of back and
  // forth between JavaScript and C++ land.
  // We also let the runtime handle it if promise == resolution.
  // We can use pointer comparison here, since the {promise} is guaranteed
  // to be a JSPromise inside this function and thus is reference comparable.
  if (IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() ||
      TaggedEqual(promise, resolution))
    deferred {
      return runtime::ResolvePromise(promise, resolution);
    }

  let then: Object = Undefined;
  try {
    // 8. If Type(resolution) is not Object, then
    // 8.a Return FulfillPromise(promise, resolution).
    if (TaggedIsSmi(resolution)) {
      return FulfillPromise(promise, resolution);
    }

    const heapResolution = UnsafeCast<HeapObject>(resolution);
    const resolutionMap = heapResolution.map;
    if (!JSAnyIsNotPrimitiveMap(resolutionMap)) {
      return FulfillPromise(promise, resolution);
    }

    // We can skip the "then" lookup on {resolution} if its [[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 (IsForceSlowPath()) {
      goto Slow;
    }

    if (IsPromiseThenProtectorCellInvalid()) {
      goto Slow;
    }

    const nativeContext = LoadNativeContext(context);
    if (!IsJSPromiseMap(resolutionMap)) {
      // We can skip the lookup of "then" if the {resolution} is a (newly
      // created) IterResultObject, as the Promise#then() protector also
      // ensures that the intrinsic %ObjectPrototype% doesn't contain any
      // "then" property. This helps to avoid negative lookups on iterator
      // results from async generators.
      dcheck(IsJSReceiverMap(resolutionMap));
      dcheck(!IsPromiseThenProtectorCellInvalid());
      if (resolutionMap ==
          *NativeContextSlot(
              nativeContext, ContextSlot::ITERATOR_RESULT_MAP_INDEX)) {
        return FulfillPromise(promise, resolution);
      } else {
        goto Slow;
      }
    }

    const promisePrototype =
        *NativeContextSlot(
        nativeContext, ContextSlot::PROMISE_PROTOTYPE_INDEX);
    if (resolutionMap.prototype == promisePrototype) {
      // The {resolution} is a native Promise in this case.
      then = *NativeContextSlot(nativeContext, ContextSlot::PROMISE_THEN_INDEX);
      // Check that Torque load elimination works.
      static_assert(nativeContext == LoadNativeContext(context));
      goto Enqueue;
    }
    goto Slow;
  } label Slow deferred {
    // 9. Let then be Get(resolution, "then").
    // 10. If then is an abrupt completion, then
    try {
      then = GetProperty(resolution, kThenString);
    } catch (e, _message) {
      // a. Return RejectPromise(promise, then.[[Value]]).
      return RejectPromise(promise, e, False);
    }

    // 11. Let thenAction be then.[[Value]].
    // 12. If IsCallable(thenAction) is false, then
    if (!Is<Callable>(then)) {
      // a. Return FulfillPromise(promise, resolution).
      return FulfillPromise(promise, resolution);
    }
    goto Enqueue;
  } label Enqueue {
    // 13. Let job be NewPromiseResolveThenableJob(promise, resolution,
    //                                             thenAction).
    const task = NewPromiseResolveThenableJobTask(
        promise, UnsafeCast<JSReceiver>(resolution),
        UnsafeCast<Callable>(then));

    // 14. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]).
    // 15. Return undefined.
    return EnqueueMicrotask(task.context, task);
  }
}
}