JavascriptAsyncGenerator.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Copyright (c) 2021 ChakraCore Project Contributors. All rights reserved.
  4. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  5. //-------------------------------------------------------------------------------------------------------
  6. #include "RuntimeLibraryPch.h"
  7. #include "Language/InterpreterStackFrame.h"
  8. using namespace Js;
  9. JavascriptAsyncGenerator* JavascriptAsyncGenerator::New(
  10. Recycler* recycler,
  11. DynamicType* generatorType,
  12. Arguments& args,
  13. ScriptFunction* scriptFunction)
  14. {
  15. // InterpreterStackFrame takes a pointer to the args, so copy them to the recycler
  16. // heap and use that buffer for the asyncgenerator's InterpreterStackFrame
  17. Field(Var)* argValuesCopy = nullptr;
  18. if (args.Info.Count > 0)
  19. {
  20. argValuesCopy = RecyclerNewArray(recycler, Field(Var), args.Info.Count);
  21. CopyArray(argValuesCopy, args.Info.Count, args.Values, args.Info.Count);
  22. }
  23. Arguments heapArgs(args.Info, unsafe_write_barrier_cast<Var*>(argValuesCopy));
  24. auto* requestQueue = RecyclerNew(recycler, JavascriptAsyncGenerator::RequestQueue, recycler);
  25. JavascriptAsyncGenerator* generator = RecyclerNewFinalized(
  26. recycler,
  27. JavascriptAsyncGenerator,
  28. generatorType,
  29. heapArgs,
  30. scriptFunction,
  31. requestQueue);
  32. auto* library = scriptFunction->GetLibrary();
  33. generator->onFulfilled = library->CreateAsyncGeneratorCallbackFunction(
  34. EntryAwaitFulfilledCallback,
  35. generator);
  36. generator->onRejected = library->CreateAsyncGeneratorCallbackFunction(
  37. EntryAwaitRejectedCallback,
  38. generator);
  39. return generator;
  40. }
  41. Var JavascriptAsyncGenerator::EntryNext(RecyclableObject* function, CallInfo callInfo, ...)
  42. {
  43. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  44. ARGUMENTS(args, callInfo);
  45. auto* scriptContext = function->GetScriptContext();
  46. auto* library = scriptContext->GetLibrary();
  47. AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, _u("AsyncGenerator.prototype.next"));
  48. Var thisValue = args[0];
  49. Var input = args.Info.Count > 1 ? args[1] : library->GetUndefined();
  50. return EnqueueRequest(
  51. thisValue,
  52. scriptContext,
  53. input,
  54. ResumeYieldKind::Normal,
  55. _u("AsyncGenerator.prototype.next"));
  56. }
  57. Var JavascriptAsyncGenerator::EntryReturn(RecyclableObject* function, CallInfo callInfo, ...)
  58. {
  59. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  60. ARGUMENTS(args, callInfo);
  61. auto* scriptContext = function->GetScriptContext();
  62. auto* library = scriptContext->GetLibrary();
  63. AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, _u("AsyncGenerator.prototype.return"));
  64. Var thisValue = args[0];
  65. Var input = args.Info.Count > 1 ? args[1] : library->GetUndefined();
  66. return EnqueueRequest(
  67. thisValue,
  68. scriptContext,
  69. input,
  70. ResumeYieldKind::Return,
  71. _u("AsyncGenerator.prototype.return"));
  72. }
  73. Var JavascriptAsyncGenerator::EntryThrow(RecyclableObject* function, CallInfo callInfo, ...)
  74. {
  75. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  76. ARGUMENTS(args, callInfo);
  77. auto* scriptContext = function->GetScriptContext();
  78. auto* library = scriptContext->GetLibrary();
  79. AUTO_TAG_NATIVE_LIBRARY_ENTRY(function, callInfo, _u("AsyncGenerator.prototype.throw"));
  80. Var thisValue = args[0];
  81. Var input = args.Info.Count > 1 ? args[1] : library->GetUndefined();
  82. return EnqueueRequest(
  83. thisValue,
  84. scriptContext,
  85. input,
  86. ResumeYieldKind::Throw,
  87. _u("AsyncGenerator.prototype.throw"));
  88. }
  89. Var JavascriptAsyncGenerator::EntryAwaitFulfilledCallback(
  90. RecyclableObject* function,
  91. CallInfo callInfo, ...)
  92. {
  93. auto* scriptContext = function->GetScriptContext();
  94. PROBE_STACK(scriptContext, Js::Constants::MinStackDefault);
  95. ARGUMENTS(args, callInfo);
  96. AssertOrFailFast(args.Info.Count > 1);
  97. Var value = args[1];
  98. auto* callbackFn = VarTo<AsyncGeneratorCallbackFunction>(function);
  99. JavascriptAsyncGenerator* generator = callbackFn->generator;
  100. PendingState state = generator->pendingState;
  101. generator->pendingState = PendingState::None;
  102. switch (state)
  103. {
  104. case PendingState::Await:
  105. generator->ResumeCoroutine(value, ResumeYieldKind::Normal);
  106. break;
  107. case PendingState::AwaitReturn:
  108. generator->ResumeCoroutine(value, ResumeYieldKind::Return);
  109. break;
  110. case PendingState::Yield:
  111. generator->ResolveNext(value);
  112. break;
  113. default:
  114. AssertMsg(false, "Expected an async generator pending state");
  115. break;
  116. }
  117. return scriptContext->GetLibrary()->GetUndefined();
  118. }
  119. Var JavascriptAsyncGenerator::EntryAwaitRejectedCallback(
  120. RecyclableObject* function,
  121. CallInfo callInfo, ...)
  122. {
  123. auto* scriptContext = function->GetScriptContext();
  124. PROBE_STACK(scriptContext, Js::Constants::MinStackDefault);
  125. ARGUMENTS(args, callInfo);
  126. AssertOrFailFast(args.Info.Count > 1);
  127. Var value = args[1];
  128. auto* callbackFn = VarTo<AsyncGeneratorCallbackFunction>(function);
  129. JavascriptAsyncGenerator* generator = callbackFn->generator;
  130. PendingState state = generator->pendingState;
  131. generator->pendingState = PendingState::None;
  132. switch (state)
  133. {
  134. case PendingState::Await:
  135. case PendingState::AwaitReturn:
  136. generator->ResumeCoroutine(value, ResumeYieldKind::Throw);
  137. break;
  138. case PendingState::Yield:
  139. generator->RejectNext(value);
  140. break;
  141. default:
  142. AssertMsg(false, "Expected an async generator pending state");
  143. break;
  144. }
  145. return scriptContext->GetLibrary()->GetUndefined();
  146. }
  147. Var JavascriptAsyncGenerator::EnqueueRequest(
  148. Var thisValue,
  149. ScriptContext* scriptContext,
  150. Var input,
  151. ResumeYieldKind resumeKind,
  152. const char16* apiNameForErrorMessage)
  153. {
  154. auto* promise = JavascriptPromise::CreateEnginePromise(scriptContext);
  155. if (!VarIs<JavascriptAsyncGenerator>(thisValue))
  156. {
  157. auto* library = scriptContext->GetLibrary();
  158. auto* error = library->CreateTypeError();
  159. JavascriptError::SetErrorMessage(
  160. error,
  161. JSERR_NeedObjectOfType,
  162. scriptContext,
  163. apiNameForErrorMessage,
  164. _u("AsyncGenerator"));
  165. promise->Reject(error, scriptContext);
  166. }
  167. else
  168. {
  169. auto* request = RecyclerNew(
  170. scriptContext->GetRecycler(),
  171. AsyncGeneratorRequest,
  172. input,
  173. resumeKind,
  174. promise);
  175. auto* generator = UnsafeVarTo<JavascriptAsyncGenerator>(thisValue);
  176. generator->PushRequest(request);
  177. generator->ResumeNext();
  178. }
  179. return promise;
  180. }
  181. void JavascriptAsyncGenerator::ResumeNext()
  182. {
  183. if (IsExecuting() || this->pendingState != PendingState::None || !HasRequest())
  184. return;
  185. auto* scriptContext = GetScriptContext();
  186. auto* library = scriptContext->GetLibrary();
  187. AsyncGeneratorRequest* next = PeekRequest();
  188. if (next->kind != ResumeYieldKind::Normal)
  189. {
  190. if (IsSuspendedStart())
  191. SetCompleted();
  192. if (next->kind == ResumeYieldKind::Return)
  193. {
  194. if (IsCompleted()) UnwrapValue(next->data, PendingState::Yield);
  195. else UnwrapValue(next->data, PendingState::AwaitReturn);
  196. }
  197. else
  198. {
  199. if (IsCompleted()) RejectNext(next->data);
  200. else ResumeCoroutine(next->data, next->kind);
  201. }
  202. }
  203. else
  204. {
  205. if (IsCompleted()) ResolveNext(library->GetUndefined());
  206. else ResumeCoroutine(next->data, next->kind);
  207. }
  208. }
  209. void JavascriptAsyncGenerator::ResumeCoroutine(Var value, ResumeYieldKind resumeKind)
  210. {
  211. Assert(this->pendingState == PendingState::None);
  212. RecyclableObject* result = nullptr;
  213. try
  214. {
  215. // Call the internal (sync) generator entry point
  216. result = VarTo<RecyclableObject>(this->CallGenerator(value, resumeKind));
  217. }
  218. catch (const JavascriptException& err)
  219. {
  220. RejectNext(err.GetAndClear()->GetThrownObject(nullptr));
  221. return;
  222. }
  223. Var resultValue = JavascriptOperators::GetProperty(
  224. result,
  225. PropertyIds::value,
  226. GetScriptContext());
  227. if (JavascriptOperators::GetTypeId(result) == TypeIds_AwaitObject)
  228. {
  229. UnwrapValue(resultValue, PendingState::Await);
  230. return;
  231. }
  232. if (IsCompleted())
  233. {
  234. // If the generator is completed, then resolve immediately. Return
  235. // values are unwrapped explicitly by the code generated for the
  236. // return statement.
  237. ResolveNext(resultValue);
  238. }
  239. else
  240. {
  241. // Otherwise, await the yielded value
  242. UnwrapValue(resultValue, PendingState::Yield);
  243. }
  244. }
  245. void JavascriptAsyncGenerator::ResolveNext(Var value)
  246. {
  247. auto* scriptContext = GetScriptContext();
  248. auto* library = scriptContext->GetLibrary();
  249. Var result = library->CreateIteratorResultObject(value, IsCompleted());
  250. ShiftRequest()->promise->Resolve(result, scriptContext);
  251. ResumeNext();
  252. }
  253. void JavascriptAsyncGenerator::RejectNext(Var reason)
  254. {
  255. SetCompleted();
  256. ShiftRequest()->promise->Reject(reason, GetScriptContext());
  257. ResumeNext();
  258. }
  259. void JavascriptAsyncGenerator::UnwrapValue(Var value, PendingState pendingState)
  260. {
  261. this->pendingState = pendingState;
  262. auto* scriptContext = GetScriptContext();
  263. auto* promise = JavascriptPromise::InternalPromiseResolve(value, scriptContext);
  264. auto* unused = JavascriptPromise::UnusedPromiseCapability(scriptContext);
  265. JavascriptPromise::PerformPromiseThen(promise, unused, onFulfilled, onRejected, scriptContext);
  266. }
  267. template<>
  268. bool Js::VarIsImpl<JavascriptAsyncGenerator>(RecyclableObject* obj)
  269. {
  270. return JavascriptOperators::GetTypeId(obj) == TypeIds_AsyncGenerator;
  271. }
  272. template<>
  273. bool Js::VarIsImpl<AsyncGeneratorCallbackFunction>(RecyclableObject* obj)
  274. {
  275. return VarIs<JavascriptFunction>(obj) && (
  276. VirtualTableInfo<AsyncGeneratorCallbackFunction>::HasVirtualTable(obj) ||
  277. VirtualTableInfo<CrossSiteObject<AsyncGeneratorCallbackFunction>>::HasVirtualTable(obj)
  278. );
  279. }