1 // Copyright 2017 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.h" 6 #include "src/builtins/builtins-utils.h" 7 #include "src/builtins/builtins.h" 8 #include "src/code-factory.h" 9 #include "src/code-stub-assembler.h" 10 #include "src/frames-inl.h" 11 12 namespace v8 { 13 namespace internal { 14 15 namespace { 16 17 // Describe fields of Context associated with the AsyncIterator unwrap closure. 18 class ValueUnwrapContext { 19 public: 20 enum Fields { kDoneSlot = Context::MIN_CONTEXT_SLOTS, kLength }; 21 }; 22 23 class AsyncFromSyncBuiltinsAssembler : public AsyncBuiltinsAssembler { 24 public: 25 explicit AsyncFromSyncBuiltinsAssembler(CodeAssemblerState* state) 26 : AsyncBuiltinsAssembler(state) {} 27 28 void ThrowIfNotAsyncFromSyncIterator(Node* const context, Node* const object, 29 Label* if_exception, 30 Variable* var_exception, 31 const char* method_name); 32 33 typedef std::function<void(Node* const context, Node* const promise, 34 Label* if_exception)> 35 UndefinedMethodHandler; 36 void Generate_AsyncFromSyncIteratorMethod( 37 Node* const context, Node* const iterator, Node* const sent_value, 38 Handle<Name> method_name, UndefinedMethodHandler&& if_method_undefined, 39 const char* operation_name, 40 Label::Type reject_label_type = Label::kDeferred, 41 Node* const initial_exception_value = nullptr); 42 43 Node* AllocateAsyncIteratorValueUnwrapContext(Node* native_context, 44 Node* done); 45 46 // Load "value" and "done" from an iterator result object. If an exception 47 // is thrown at any point, jumps to te `if_exception` label with exception 48 // stored in `var_exception`. 49 // 50 // Returns a Pair of Nodes, whose first element is the value of the "value" 51 // property, and whose second element is the value of the "done" property, 52 // converted to a Boolean if needed. 53 std::pair<Node*, Node*> LoadIteratorResult(Node* const context, 54 Node* const native_context, 55 Node* const iter_result, 56 Label* if_exception, 57 Variable* var_exception); 58 59 Node* CreateUnwrapClosure(Node* const native_context, Node* const done); 60 }; 61 62 void AsyncFromSyncBuiltinsAssembler::ThrowIfNotAsyncFromSyncIterator( 63 Node* const context, Node* const object, Label* if_exception, 64 Variable* var_exception, const char* method_name) { 65 Label if_receiverisincompatible(this, Label::kDeferred), done(this); 66 67 GotoIf(TaggedIsSmi(object), &if_receiverisincompatible); 68 Branch(HasInstanceType(object, JS_ASYNC_FROM_SYNC_ITERATOR_TYPE), &done, 69 &if_receiverisincompatible); 70 71 Bind(&if_receiverisincompatible); 72 { 73 // If Type(O) is not Object, or if O does not have a [[SyncIterator]] 74 // internal slot, then 75 76 // Let badIteratorError be a new TypeError exception. 77 Node* const error = 78 MakeTypeError(MessageTemplate::kIncompatibleMethodReceiver, context, 79 CStringConstant(method_name), object); 80 81 // Perform ! Call(promiseCapability.[[Reject]], undefined, 82 // badIteratorError ). 83 var_exception->Bind(error); 84 Goto(if_exception); 85 } 86 87 Bind(&done); 88 } 89 90 void AsyncFromSyncBuiltinsAssembler::Generate_AsyncFromSyncIteratorMethod( 91 Node* const context, Node* const iterator, Node* const sent_value, 92 Handle<Name> method_name, UndefinedMethodHandler&& if_method_undefined, 93 const char* operation_name, Label::Type reject_label_type, 94 Node* const initial_exception_value) { 95 Node* const native_context = LoadNativeContext(context); 96 Node* const promise = AllocateAndInitJSPromise(context); 97 98 Variable var_exception(this, MachineRepresentation::kTagged, 99 initial_exception_value == nullptr 100 ? UndefinedConstant() 101 : initial_exception_value); 102 Label reject_promise(this, reject_label_type); 103 104 ThrowIfNotAsyncFromSyncIterator(context, iterator, &reject_promise, 105 &var_exception, operation_name); 106 107 Node* const sync_iterator = 108 LoadObjectField(iterator, JSAsyncFromSyncIterator::kSyncIteratorOffset); 109 110 Node* const method = GetProperty(context, sync_iterator, method_name); 111 112 if (if_method_undefined) { 113 Label if_isnotundefined(this); 114 115 GotoIfNot(IsUndefined(method), &if_isnotundefined); 116 if_method_undefined(native_context, promise, &reject_promise); 117 118 Bind(&if_isnotundefined); 119 } 120 121 Node* const iter_result = CallJS(CodeFactory::Call(isolate()), context, 122 method, sync_iterator, sent_value); 123 GotoIfException(iter_result, &reject_promise, &var_exception); 124 125 Node* value; 126 Node* done; 127 std::tie(value, done) = LoadIteratorResult( 128 context, native_context, iter_result, &reject_promise, &var_exception); 129 Node* const wrapper = AllocateAndInitJSPromise(context); 130 131 // Perform ! Call(valueWrapperCapability.[[Resolve]], undefined, 132 // throwValue ). 133 InternalResolvePromise(context, wrapper, value); 134 135 // Let onFulfilled be a new built-in function object as defined in 136 // Async Iterator Value Unwrap Functions. 137 // Set onFulfilled.[[Done]] to throwDone. 138 Node* const on_fulfilled = CreateUnwrapClosure(native_context, done); 139 140 // Perform ! PerformPromiseThen(valueWrapperCapability.[[Promise]], 141 // onFulfilled, undefined, promiseCapability). 142 Node* const undefined = UndefinedConstant(); 143 InternalPerformPromiseThen(context, wrapper, on_fulfilled, undefined, promise, 144 undefined, undefined); 145 Return(promise); 146 147 Bind(&reject_promise); 148 { 149 Node* const exception = var_exception.value(); 150 InternalPromiseReject(context, promise, exception, TrueConstant()); 151 152 Return(promise); 153 } 154 } 155 156 std::pair<Node*, Node*> AsyncFromSyncBuiltinsAssembler::LoadIteratorResult( 157 Node* const context, Node* const native_context, Node* const iter_result, 158 Label* if_exception, Variable* var_exception) { 159 Label if_fastpath(this), if_slowpath(this), merge(this), to_boolean(this), 160 done(this), if_notanobject(this, Label::kDeferred); 161 GotoIf(TaggedIsSmi(iter_result), &if_notanobject); 162 163 Node* const iter_result_map = LoadMap(iter_result); 164 GotoIfNot(IsJSReceiverMap(iter_result_map), &if_notanobject); 165 166 Node* const fast_iter_result_map = 167 LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX); 168 169 Variable var_value(this, MachineRepresentation::kTagged); 170 Variable var_done(this, MachineRepresentation::kTagged); 171 Branch(WordEqual(iter_result_map, fast_iter_result_map), &if_fastpath, 172 &if_slowpath); 173 174 Bind(&if_fastpath); 175 { 176 var_value.Bind( 177 LoadObjectField(iter_result, JSIteratorResult::kValueOffset)); 178 var_done.Bind(LoadObjectField(iter_result, JSIteratorResult::kDoneOffset)); 179 Goto(&merge); 180 } 181 182 Bind(&if_slowpath); 183 { 184 // Let nextValue be IteratorValue(nextResult). 185 // IfAbruptRejectPromise(nextValue, promiseCapability). 186 Node* const value = 187 GetProperty(context, iter_result, factory()->value_string()); 188 GotoIfException(value, if_exception, var_exception); 189 190 // Let nextDone be IteratorComplete(nextResult). 191 // IfAbruptRejectPromise(nextDone, promiseCapability). 192 Node* const done = 193 GetProperty(context, iter_result, factory()->done_string()); 194 GotoIfException(done, if_exception, var_exception); 195 196 var_value.Bind(value); 197 var_done.Bind(done); 198 Goto(&merge); 199 } 200 201 Bind(&if_notanobject); 202 { 203 // Sync iterator result is not an object --- Produce a TypeError and jump 204 // to the `if_exception` path. 205 Node* const error = MakeTypeError( 206 MessageTemplate::kIteratorResultNotAnObject, context, iter_result); 207 var_exception->Bind(error); 208 Goto(if_exception); 209 } 210 211 Bind(&merge); 212 // Ensure `iterResult.done` is a Boolean. 213 GotoIf(TaggedIsSmi(var_done.value()), &to_boolean); 214 Branch(IsBoolean(var_done.value()), &done, &to_boolean); 215 216 Bind(&to_boolean); 217 { 218 Node* const result = 219 CallStub(CodeFactory::ToBoolean(isolate()), context, var_done.value()); 220 var_done.Bind(result); 221 Goto(&done); 222 } 223 224 Bind(&done); 225 return std::make_pair(var_value.value(), var_done.value()); 226 } 227 228 Node* AsyncFromSyncBuiltinsAssembler::CreateUnwrapClosure(Node* native_context, 229 Node* done) { 230 Node* const map = LoadContextElement( 231 native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX); 232 Node* const on_fulfilled_shared = LoadContextElement( 233 native_context, Context::ASYNC_ITERATOR_VALUE_UNWRAP_SHARED_FUN); 234 CSA_ASSERT(this, 235 HasInstanceType(on_fulfilled_shared, SHARED_FUNCTION_INFO_TYPE)); 236 Node* const closure_context = 237 AllocateAsyncIteratorValueUnwrapContext(native_context, done); 238 return AllocateFunctionWithMapAndContext(map, on_fulfilled_shared, 239 closure_context); 240 } 241 242 Node* AsyncFromSyncBuiltinsAssembler::AllocateAsyncIteratorValueUnwrapContext( 243 Node* native_context, Node* done) { 244 CSA_ASSERT(this, IsNativeContext(native_context)); 245 CSA_ASSERT(this, IsBoolean(done)); 246 247 Node* const context = 248 CreatePromiseContext(native_context, ValueUnwrapContext::kLength); 249 StoreContextElementNoWriteBarrier(context, ValueUnwrapContext::kDoneSlot, 250 done); 251 return context; 252 } 253 } // namespace 254 255 // https://tc39.github.io/proposal-async-iteration/ 256 // Section #sec-%asyncfromsynciteratorprototype%.next 257 TF_BUILTIN(AsyncFromSyncIteratorPrototypeNext, AsyncFromSyncBuiltinsAssembler) { 258 Node* const iterator = Parameter(0); 259 Node* const value = Parameter(1); 260 Node* const context = Parameter(4); 261 262 Generate_AsyncFromSyncIteratorMethod( 263 context, iterator, value, factory()->next_string(), 264 UndefinedMethodHandler(), "[Async-from-Sync Iterator].prototype.next"); 265 } 266 267 // https://tc39.github.io/proposal-async-iteration/ 268 // Section #sec-%asyncfromsynciteratorprototype%.return 269 TF_BUILTIN(AsyncFromSyncIteratorPrototypeReturn, 270 AsyncFromSyncBuiltinsAssembler) { 271 Node* const iterator = Parameter(0); 272 Node* const value = Parameter(1); 273 Node* const context = Parameter(4); 274 275 auto if_return_undefined = [=](Node* const native_context, 276 Node* const promise, Label* if_exception) { 277 // If return is undefined, then 278 // Let iterResult be ! CreateIterResultObject(value, true) 279 Node* const iter_result = 280 CallStub(CodeFactory::CreateIterResultObject(isolate()), context, value, 281 TrueConstant()); 282 283 // Perform ! Call(promiseCapability.[[Resolve]], undefined, iterResult ). 284 // IfAbruptRejectPromise(nextDone, promiseCapability). 285 // Return promiseCapability.[[Promise]]. 286 PromiseFulfill(context, promise, iter_result, v8::Promise::kFulfilled); 287 Return(promise); 288 }; 289 290 Generate_AsyncFromSyncIteratorMethod( 291 context, iterator, value, factory()->return_string(), if_return_undefined, 292 "[Async-from-Sync Iterator].prototype.return"); 293 } 294 295 // https://tc39.github.io/proposal-async-iteration/ 296 // Section #sec-%asyncfromsynciteratorprototype%.throw 297 TF_BUILTIN(AsyncFromSyncIteratorPrototypeThrow, 298 AsyncFromSyncBuiltinsAssembler) { 299 Node* const iterator = Parameter(0); 300 Node* const reason = Parameter(1); 301 Node* const context = Parameter(4); 302 303 auto if_throw_undefined = [=](Node* const native_context, Node* const promise, 304 Label* if_exception) { Goto(if_exception); }; 305 306 Generate_AsyncFromSyncIteratorMethod( 307 context, iterator, reason, factory()->throw_string(), if_throw_undefined, 308 "[Async-from-Sync Iterator].prototype.throw", Label::kNonDeferred, 309 reason); 310 } 311 312 TF_BUILTIN(AsyncIteratorValueUnwrap, AsyncFromSyncBuiltinsAssembler) { 313 Node* const value = Parameter(1); 314 Node* const context = Parameter(4); 315 316 Node* const done = LoadContextElement(context, ValueUnwrapContext::kDoneSlot); 317 CSA_ASSERT(this, IsBoolean(done)); 318 319 Node* const unwrapped_value = CallStub( 320 CodeFactory::CreateIterResultObject(isolate()), context, value, done); 321 322 Return(unwrapped_value); 323 } 324 325 } // namespace internal 326 } // namespace v8 327