1 // Copyright 2016 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "src/builtins/builtins-async-gen.h"
6
7 #include "src/builtins/builtins-utils-gen.h"
8 #include "src/heap/factory-inl.h"
9 #include "src/objects/js-generator.h"
10 #include "src/objects/js-promise.h"
11 #include "src/objects/shared-function-info.h"
12
13 namespace v8 {
14 namespace internal {
15
16 using compiler::Node;
17
18 namespace {
19 // Describe fields of Context associated with the AsyncIterator unwrap closure.
20 class ValueUnwrapContext {
21 public:
22 enum Fields { kDoneSlot = Context::MIN_CONTEXT_SLOTS, kLength };
23 };
24
25 } // namespace
26
AwaitOld(TNode<Context> context,TNode<JSGeneratorObject> generator,TNode<Object> value,TNode<JSPromise> outer_promise,TNode<SharedFunctionInfo> on_resolve_sfi,TNode<SharedFunctionInfo> on_reject_sfi,TNode<Oddball> is_predicted_as_caught)27 TNode<Object> AsyncBuiltinsAssembler::AwaitOld(
28 TNode<Context> context, TNode<JSGeneratorObject> generator,
29 TNode<Object> value, TNode<JSPromise> outer_promise,
30 TNode<SharedFunctionInfo> on_resolve_sfi,
31 TNode<SharedFunctionInfo> on_reject_sfi,
32 TNode<Oddball> is_predicted_as_caught) {
33 const TNode<NativeContext> native_context = LoadNativeContext(context);
34
35 static const int kWrappedPromiseOffset =
36 FixedArray::SizeFor(Context::MIN_CONTEXT_EXTENDED_SLOTS);
37 static const int kResolveClosureOffset =
38 kWrappedPromiseOffset + JSPromise::kSizeWithEmbedderFields;
39 static const int kRejectClosureOffset =
40 kResolveClosureOffset + JSFunction::kSizeWithoutPrototype;
41 static const int kTotalSize =
42 kRejectClosureOffset + JSFunction::kSizeWithoutPrototype;
43
44 TNode<HeapObject> base = AllocateInNewSpace(kTotalSize);
45 TNode<Context> closure_context = UncheckedCast<Context>(base);
46 {
47 // Initialize the await context, storing the {generator} as extension.
48 TNode<Map> map = CAST(
49 LoadContextElement(native_context, Context::AWAIT_CONTEXT_MAP_INDEX));
50 StoreMapNoWriteBarrier(closure_context, map);
51 StoreObjectFieldNoWriteBarrier(
52 closure_context, Context::kLengthOffset,
53 SmiConstant(Context::MIN_CONTEXT_EXTENDED_SLOTS));
54 const TNode<Object> empty_scope_info =
55 LoadContextElement(native_context, Context::SCOPE_INFO_INDEX);
56 StoreContextElementNoWriteBarrier(
57 closure_context, Context::SCOPE_INFO_INDEX, empty_scope_info);
58 StoreContextElementNoWriteBarrier(closure_context, Context::PREVIOUS_INDEX,
59 native_context);
60 StoreContextElementNoWriteBarrier(closure_context, Context::EXTENSION_INDEX,
61 generator);
62 }
63
64 // Let promiseCapability be ! NewPromiseCapability(%Promise%).
65 const TNode<JSFunction> promise_fun =
66 CAST(LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX));
67 CSA_ASSERT(this, IsFunctionWithPrototypeSlotMap(LoadMap(promise_fun)));
68 const TNode<Map> promise_map = CAST(
69 LoadObjectField(promise_fun, JSFunction::kPrototypeOrInitialMapOffset));
70 // Assert that the JSPromise map has an instance size is
71 // JSPromise::kSizeWithEmbedderFields.
72 CSA_ASSERT(this,
73 IntPtrEqual(LoadMapInstanceSizeInWords(promise_map),
74 IntPtrConstant(JSPromise::kSizeWithEmbedderFields /
75 kTaggedSize)));
76 TNode<JSPromise> promise;
77 {
78 // Initialize Promise
79 TNode<HeapObject> wrapped_value =
80 InnerAllocate(base, kWrappedPromiseOffset);
81 StoreMapNoWriteBarrier(wrapped_value, promise_map);
82 StoreObjectFieldRoot(wrapped_value, JSPromise::kPropertiesOrHashOffset,
83 RootIndex::kEmptyFixedArray);
84 StoreObjectFieldRoot(wrapped_value, JSPromise::kElementsOffset,
85 RootIndex::kEmptyFixedArray);
86 promise = CAST(wrapped_value);
87 PromiseInit(promise);
88 }
89
90 // Initialize resolve handler
91 TNode<HeapObject> on_resolve = InnerAllocate(base, kResolveClosureOffset);
92 InitializeNativeClosure(closure_context, native_context, on_resolve,
93 on_resolve_sfi);
94
95 // Initialize reject handler
96 TNode<HeapObject> on_reject = InnerAllocate(base, kRejectClosureOffset);
97 InitializeNativeClosure(closure_context, native_context, on_reject,
98 on_reject_sfi);
99
100 TVARIABLE(HeapObject, var_throwaway, UndefinedConstant());
101
102 // Deal with PromiseHooks and debug support in the runtime. This
103 // also allocates the throwaway promise, which is only needed in
104 // case of PromiseHooks or debugging.
105 Label if_debugging(this, Label::kDeferred), do_resolve_promise(this);
106 Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
107 &if_debugging, &do_resolve_promise);
108 BIND(&if_debugging);
109 var_throwaway =
110 CAST(CallRuntime(Runtime::kAwaitPromisesInitOld, context, value, promise,
111 outer_promise, on_reject, is_predicted_as_caught));
112 Goto(&do_resolve_promise);
113 BIND(&do_resolve_promise);
114
115 // Perform ! Call(promiseCapability.[[Resolve]], undefined, « promise »).
116 CallBuiltin(Builtins::kResolvePromise, context, promise, value);
117
118 return CallBuiltin(Builtins::kPerformPromiseThen, context, promise,
119 on_resolve, on_reject, var_throwaway.value());
120 }
121
AwaitOptimized(TNode<Context> context,TNode<JSGeneratorObject> generator,TNode<JSPromise> promise,TNode<JSPromise> outer_promise,TNode<SharedFunctionInfo> on_resolve_sfi,TNode<SharedFunctionInfo> on_reject_sfi,TNode<Oddball> is_predicted_as_caught)122 TNode<Object> AsyncBuiltinsAssembler::AwaitOptimized(
123 TNode<Context> context, TNode<JSGeneratorObject> generator,
124 TNode<JSPromise> promise, TNode<JSPromise> outer_promise,
125 TNode<SharedFunctionInfo> on_resolve_sfi,
126 TNode<SharedFunctionInfo> on_reject_sfi,
127 TNode<Oddball> is_predicted_as_caught) {
128 const TNode<NativeContext> native_context = LoadNativeContext(context);
129
130 static const int kResolveClosureOffset =
131 FixedArray::SizeFor(Context::MIN_CONTEXT_EXTENDED_SLOTS);
132 static const int kRejectClosureOffset =
133 kResolveClosureOffset + JSFunction::kSizeWithoutPrototype;
134 static const int kTotalSize =
135 kRejectClosureOffset + JSFunction::kSizeWithoutPrototype;
136
137 // 2. Let promise be ? PromiseResolve(« promise »).
138 // We skip this step, because promise is already guaranteed to be a
139 // JSPRomise at this point.
140
141 TNode<HeapObject> base = AllocateInNewSpace(kTotalSize);
142 TNode<Context> closure_context = UncheckedCast<Context>(base);
143 {
144 // Initialize the await context, storing the {generator} as extension.
145 TNode<Map> map = CAST(
146 LoadContextElement(native_context, Context::AWAIT_CONTEXT_MAP_INDEX));
147 StoreMapNoWriteBarrier(closure_context, map);
148 StoreObjectFieldNoWriteBarrier(
149 closure_context, Context::kLengthOffset,
150 SmiConstant(Context::MIN_CONTEXT_EXTENDED_SLOTS));
151 const TNode<Object> empty_scope_info =
152 LoadContextElement(native_context, Context::SCOPE_INFO_INDEX);
153 StoreContextElementNoWriteBarrier(
154 closure_context, Context::SCOPE_INFO_INDEX, empty_scope_info);
155 StoreContextElementNoWriteBarrier(closure_context, Context::PREVIOUS_INDEX,
156 native_context);
157 StoreContextElementNoWriteBarrier(closure_context, Context::EXTENSION_INDEX,
158 generator);
159 }
160
161 // Initialize resolve handler
162 TNode<HeapObject> on_resolve = InnerAllocate(base, kResolveClosureOffset);
163 InitializeNativeClosure(closure_context, native_context, on_resolve,
164 on_resolve_sfi);
165
166 // Initialize reject handler
167 TNode<HeapObject> on_reject = InnerAllocate(base, kRejectClosureOffset);
168 InitializeNativeClosure(closure_context, native_context, on_reject,
169 on_reject_sfi);
170
171 TVARIABLE(HeapObject, var_throwaway, UndefinedConstant());
172
173 // Deal with PromiseHooks and debug support in the runtime. This
174 // also allocates the throwaway promise, which is only needed in
175 // case of PromiseHooks or debugging.
176 Label if_debugging(this, Label::kDeferred), do_perform_promise_then(this);
177 Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
178 &if_debugging, &do_perform_promise_then);
179 BIND(&if_debugging);
180 var_throwaway =
181 CAST(CallRuntime(Runtime::kAwaitPromisesInit, context, promise, promise,
182 outer_promise, on_reject, is_predicted_as_caught));
183 Goto(&do_perform_promise_then);
184 BIND(&do_perform_promise_then);
185
186 return CallBuiltin(Builtins::kPerformPromiseThen, native_context, promise,
187 on_resolve, on_reject, var_throwaway.value());
188 }
189
Await(TNode<Context> context,TNode<JSGeneratorObject> generator,TNode<Object> value,TNode<JSPromise> outer_promise,TNode<SharedFunctionInfo> on_resolve_sfi,TNode<SharedFunctionInfo> on_reject_sfi,TNode<Oddball> is_predicted_as_caught)190 TNode<Object> AsyncBuiltinsAssembler::Await(
191 TNode<Context> context, TNode<JSGeneratorObject> generator,
192 TNode<Object> value, TNode<JSPromise> outer_promise,
193 TNode<SharedFunctionInfo> on_resolve_sfi,
194 TNode<SharedFunctionInfo> on_reject_sfi,
195 TNode<Oddball> is_predicted_as_caught) {
196 TVARIABLE(Object, result);
197 Label if_old(this), if_new(this), done(this),
198 if_slow_constructor(this, Label::kDeferred);
199
200 // We do the `PromiseResolve(%Promise%,value)` avoiding to unnecessarily
201 // create wrapper promises. Now if {value} is already a promise with the
202 // intrinsics %Promise% constructor as its "constructor", we don't need
203 // to allocate the wrapper promise and can just use the `AwaitOptimized`
204 // logic.
205 GotoIf(TaggedIsSmi(value), &if_old);
206 TNode<HeapObject> value_object = CAST(value);
207 const TNode<Map> value_map = LoadMap(value_object);
208 GotoIfNot(IsJSPromiseMap(value_map), &if_old);
209 // We can skip the "constructor" lookup on {value} if it's [[Prototype]]
210 // is the (initial) Promise.prototype and the @@species protector is
211 // intact, as that guards the lookup path for "constructor" on
212 // JSPromise instances which have the (initial) Promise.prototype.
213 const TNode<NativeContext> native_context = LoadNativeContext(context);
214 const TNode<Object> promise_prototype =
215 LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_INDEX);
216 GotoIfNot(TaggedEqual(LoadMapPrototype(value_map), promise_prototype),
217 &if_slow_constructor);
218 Branch(IsPromiseSpeciesProtectorCellInvalid(), &if_slow_constructor, &if_new);
219
220 // At this point, {value} doesn't have the initial promise prototype or
221 // the promise @@species protector was invalidated, but {value} could still
222 // have the %Promise% as its "constructor", so we need to check that as well.
223 BIND(&if_slow_constructor);
224 {
225 const TNode<Object> value_constructor =
226 GetProperty(context, value, isolate()->factory()->constructor_string());
227 const TNode<Object> promise_function =
228 LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
229 Branch(TaggedEqual(value_constructor, promise_function), &if_new, &if_old);
230 }
231
232 BIND(&if_old);
233 result = AwaitOld(context, generator, value, outer_promise, on_resolve_sfi,
234 on_reject_sfi, is_predicted_as_caught);
235 Goto(&done);
236
237 BIND(&if_new);
238 result =
239 AwaitOptimized(context, generator, CAST(value), outer_promise,
240 on_resolve_sfi, on_reject_sfi, is_predicted_as_caught);
241 Goto(&done);
242
243 BIND(&done);
244 return result.value();
245 }
246
InitializeNativeClosure(TNode<Context> context,TNode<NativeContext> native_context,TNode<HeapObject> function,TNode<SharedFunctionInfo> shared_info)247 void AsyncBuiltinsAssembler::InitializeNativeClosure(
248 TNode<Context> context, TNode<NativeContext> native_context,
249 TNode<HeapObject> function, TNode<SharedFunctionInfo> shared_info) {
250 TNode<Map> function_map = CAST(LoadContextElement(
251 native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX));
252 // Ensure that we don't have to initialize prototype_or_initial_map field of
253 // JSFunction.
254 CSA_ASSERT(this,
255 IntPtrEqual(LoadMapInstanceSizeInWords(function_map),
256 IntPtrConstant(JSFunction::kSizeWithoutPrototype /
257 kTaggedSize)));
258 STATIC_ASSERT(JSFunction::kSizeWithoutPrototype == 7 * kTaggedSize);
259 StoreMapNoWriteBarrier(function, function_map);
260 StoreObjectFieldRoot(function, JSObject::kPropertiesOrHashOffset,
261 RootIndex::kEmptyFixedArray);
262 StoreObjectFieldRoot(function, JSObject::kElementsOffset,
263 RootIndex::kEmptyFixedArray);
264 StoreObjectFieldRoot(function, JSFunction::kFeedbackCellOffset,
265 RootIndex::kManyClosuresCell);
266
267 StoreObjectFieldNoWriteBarrier(
268 function, JSFunction::kSharedFunctionInfoOffset, shared_info);
269 StoreObjectFieldNoWriteBarrier(function, JSFunction::kContextOffset, context);
270
271 // For the native closures that are initialized here (for `await`)
272 // we know that their SharedFunctionInfo::function_data(kAcquireLoad) slot
273 // contains a builtin index (as Smi), so there's no need to use
274 // CodeStubAssembler::GetSharedFunctionInfoCode() helper here,
275 // which almost doubles the size of `await` builtins (unnecessarily).
276 TNode<Smi> builtin_id = LoadObjectField<Smi>(
277 shared_info, SharedFunctionInfo::kFunctionDataOffset);
278 TNode<Code> code = LoadBuiltin(builtin_id);
279 StoreObjectFieldNoWriteBarrier(function, JSFunction::kCodeOffset, code);
280 }
281
CreateUnwrapClosure(TNode<NativeContext> native_context,TNode<Oddball> done)282 TNode<JSFunction> AsyncBuiltinsAssembler::CreateUnwrapClosure(
283 TNode<NativeContext> native_context, TNode<Oddball> done) {
284 const TNode<Map> map = CAST(LoadContextElement(
285 native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX));
286 const TNode<SharedFunctionInfo> on_fulfilled_shared =
287 AsyncIteratorValueUnwrapSharedFunConstant();
288 const TNode<Context> closure_context =
289 AllocateAsyncIteratorValueUnwrapContext(native_context, done);
290 return AllocateFunctionWithMapAndContext(map, on_fulfilled_shared,
291 closure_context);
292 }
293
AllocateAsyncIteratorValueUnwrapContext(TNode<NativeContext> native_context,TNode<Oddball> done)294 TNode<Context> AsyncBuiltinsAssembler::AllocateAsyncIteratorValueUnwrapContext(
295 TNode<NativeContext> native_context, TNode<Oddball> done) {
296 CSA_ASSERT(this, IsBoolean(done));
297
298 TNode<Context> context = AllocateSyntheticFunctionContext(
299 native_context, ValueUnwrapContext::kLength);
300 StoreContextElementNoWriteBarrier(context, ValueUnwrapContext::kDoneSlot,
301 done);
302 return context;
303 }
304
TF_BUILTIN(AsyncIteratorValueUnwrap,AsyncBuiltinsAssembler)305 TF_BUILTIN(AsyncIteratorValueUnwrap, AsyncBuiltinsAssembler) {
306 auto value = Parameter<Object>(Descriptor::kValue);
307 auto context = Parameter<Context>(Descriptor::kContext);
308
309 const TNode<Object> done =
310 LoadContextElement(context, ValueUnwrapContext::kDoneSlot);
311 CSA_ASSERT(this, IsBoolean(CAST(done)));
312
313 const TNode<Object> unwrapped_value =
314 CallBuiltin(Builtins::kCreateIterResultObject, context, value, done);
315
316 Return(unwrapped_value);
317 }
318
319 } // namespace internal
320 } // namespace v8
321