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-utils-gen.h"
6#include "src/builtins/builtins.h"
7#include "src/codegen/code-factory.h"
8#include "src/codegen/code-stub-assembler.h"
9#include "src/execution/isolate.h"
10#include "src/objects/js-generator.h"
11#include "src/objects/objects-inl.h"
12
13namespace v8 {
14namespace internal {
15
16class GeneratorBuiltinsAssembler : public CodeStubAssembler {
17 public:
18  explicit GeneratorBuiltinsAssembler(compiler::CodeAssemblerState* state)
19      : CodeStubAssembler(state) {}
20
21 protected:
22  // Currently, AsyncModules in V8 are built on top of JSAsyncFunctionObjects
23  // with an initial yield. Thus, we need some way to 'resume' the
24  // underlying JSAsyncFunctionObject owned by an AsyncModule. To support this
25  // the body of resume is factored out below, and shared by JSGeneratorObject
26  // prototype methods as well as AsyncModuleEvaluate. The only difference
27  // between AsyncModuleEvaluate and JSGeneratorObject::PrototypeNext is
28  // the expected receiver.
29  void InnerResume(CodeStubArguments* args, TNode<JSGeneratorObject> receiver,
30                   TNode<Object> value, TNode<Context> context,
31                   JSGeneratorObject::ResumeMode resume_mode,
32                   char const* const method_name);
33  void GeneratorPrototypeResume(CodeStubArguments* args, TNode<Object> receiver,
34                                TNode<Object> value, TNode<Context> context,
35                                JSGeneratorObject::ResumeMode resume_mode,
36                                char const* const method_name);
37};
38
39void GeneratorBuiltinsAssembler::InnerResume(
40    CodeStubArguments* args, TNode<JSGeneratorObject> receiver,
41    TNode<Object> value, TNode<Context> context,
42    JSGeneratorObject::ResumeMode resume_mode, char const* const method_name) {
43  // Check if the {receiver} is running or already closed.
44  TNode<Smi> receiver_continuation =
45      LoadObjectField<Smi>(receiver, JSGeneratorObject::kContinuationOffset);
46  Label if_receiverisclosed(this, Label::kDeferred),
47      if_receiverisrunning(this, Label::kDeferred);
48  TNode<Smi> closed = SmiConstant(JSGeneratorObject::kGeneratorClosed);
49  GotoIf(SmiEqual(receiver_continuation, closed), &if_receiverisclosed);
50  DCHECK_LT(JSGeneratorObject::kGeneratorExecuting,
51            JSGeneratorObject::kGeneratorClosed);
52  GotoIf(SmiLessThan(receiver_continuation, closed), &if_receiverisrunning);
53
54  // Remember the {resume_mode} for the {receiver}.
55  StoreObjectFieldNoWriteBarrier(receiver, JSGeneratorObject::kResumeModeOffset,
56                                 SmiConstant(resume_mode));
57
58  // Resume the {receiver} using our trampoline.
59  // Close the generator if there was an exception.
60  TVARIABLE(Object, var_exception);
61  Label if_exception(this, Label::kDeferred), if_final_return(this);
62  TNode<Object> result;
63  {
64    compiler::ScopedExceptionHandler handler(this, &if_exception,
65                                             &var_exception);
66    result = CallStub(CodeFactory::ResumeGenerator(isolate()), context, value,
67                      receiver);
68  }
69
70  // If the generator is not suspended (i.e., its state is 'executing'),
71  // close it and wrap the return value in IteratorResult.
72  TNode<Smi> result_continuation =
73      LoadObjectField<Smi>(receiver, JSGeneratorObject::kContinuationOffset);
74
75  // The generator function should not close the generator by itself, let's
76  // check it is indeed not closed yet.
77  CSA_DCHECK(this, SmiNotEqual(result_continuation, closed));
78
79  TNode<Smi> executing = SmiConstant(JSGeneratorObject::kGeneratorExecuting);
80  GotoIf(SmiEqual(result_continuation, executing), &if_final_return);
81
82  args->PopAndReturn(result);
83
84  BIND(&if_final_return);
85  {
86    // Close the generator.
87    StoreObjectFieldNoWriteBarrier(
88        receiver, JSGeneratorObject::kContinuationOffset, closed);
89    // Return the wrapped result.
90    args->PopAndReturn(CallBuiltin(Builtin::kCreateIterResultObject, context,
91                                   result, TrueConstant()));
92  }
93
94  BIND(&if_receiverisclosed);
95  {
96    // The {receiver} is closed already.
97    TNode<Object> builtin_result;
98    switch (resume_mode) {
99      case JSGeneratorObject::kNext:
100        builtin_result = CallBuiltin(Builtin::kCreateIterResultObject, context,
101                                     UndefinedConstant(), TrueConstant());
102        break;
103      case JSGeneratorObject::kReturn:
104        builtin_result = CallBuiltin(Builtin::kCreateIterResultObject, context,
105                                     value, TrueConstant());
106        break;
107      case JSGeneratorObject::kThrow:
108        builtin_result = CallRuntime(Runtime::kThrow, context, value);
109        break;
110    }
111    args->PopAndReturn(builtin_result);
112  }
113
114  BIND(&if_receiverisrunning);
115  { ThrowTypeError(context, MessageTemplate::kGeneratorRunning); }
116
117  BIND(&if_exception);
118  {
119    StoreObjectFieldNoWriteBarrier(
120        receiver, JSGeneratorObject::kContinuationOffset, closed);
121    CallRuntime(Runtime::kReThrow, context, var_exception.value());
122    Unreachable();
123  }
124}
125
126void GeneratorBuiltinsAssembler::GeneratorPrototypeResume(
127    CodeStubArguments* args, TNode<Object> receiver, TNode<Object> value,
128    TNode<Context> context, JSGeneratorObject::ResumeMode resume_mode,
129    char const* const method_name) {
130  // Check if the {receiver} is actually a JSGeneratorObject.
131  ThrowIfNotInstanceType(context, receiver, JS_GENERATOR_OBJECT_TYPE,
132                         method_name);
133  TNode<JSGeneratorObject> generator = CAST(receiver);
134  InnerResume(args, generator, value, context, resume_mode, method_name);
135}
136
137TF_BUILTIN(AsyncModuleEvaluate, GeneratorBuiltinsAssembler) {
138  const int kValueArg = 0;
139
140  auto argc = UncheckedParameter<Int32T>(Descriptor::kJSActualArgumentsCount);
141  CodeStubArguments args(this, argc);
142
143  TNode<Object> receiver = args.GetReceiver();
144  TNode<Object> value = args.GetOptionalArgumentValue(kValueArg);
145  auto context = Parameter<Context>(Descriptor::kContext);
146
147  // AsyncModules act like JSAsyncFunctions. Thus we check here
148  // that the {receiver} is a JSAsyncFunction.
149  char const* const method_name = "[AsyncModule].evaluate";
150  ThrowIfNotInstanceType(context, receiver, JS_ASYNC_FUNCTION_OBJECT_TYPE,
151                         method_name);
152  TNode<JSAsyncFunctionObject> async_function = CAST(receiver);
153  InnerResume(&args, async_function, value, context, JSGeneratorObject::kNext,
154              method_name);
155}
156
157// ES6 #sec-generator.prototype.next
158TF_BUILTIN(GeneratorPrototypeNext, GeneratorBuiltinsAssembler) {
159  const int kValueArg = 0;
160
161  auto argc = UncheckedParameter<Int32T>(Descriptor::kJSActualArgumentsCount);
162  CodeStubArguments args(this, argc);
163
164  TNode<Object> receiver = args.GetReceiver();
165  TNode<Object> value = args.GetOptionalArgumentValue(kValueArg);
166  auto context = Parameter<Context>(Descriptor::kContext);
167
168  GeneratorPrototypeResume(&args, receiver, value, context,
169                           JSGeneratorObject::kNext,
170                           "[Generator].prototype.next");
171}
172
173// ES6 #sec-generator.prototype.return
174TF_BUILTIN(GeneratorPrototypeReturn, GeneratorBuiltinsAssembler) {
175  const int kValueArg = 0;
176
177  auto argc = UncheckedParameter<Int32T>(Descriptor::kJSActualArgumentsCount);
178  CodeStubArguments args(this, argc);
179
180  TNode<Object> receiver = args.GetReceiver();
181  TNode<Object> value = args.GetOptionalArgumentValue(kValueArg);
182  auto context = Parameter<Context>(Descriptor::kContext);
183
184  GeneratorPrototypeResume(&args, receiver, value, context,
185                           JSGeneratorObject::kReturn,
186                           "[Generator].prototype.return");
187}
188
189// ES6 #sec-generator.prototype.throw
190TF_BUILTIN(GeneratorPrototypeThrow, GeneratorBuiltinsAssembler) {
191  const int kExceptionArg = 0;
192
193  auto argc = UncheckedParameter<Int32T>(Descriptor::kJSActualArgumentsCount);
194  CodeStubArguments args(this, argc);
195
196  TNode<Object> receiver = args.GetReceiver();
197  TNode<Object> exception = args.GetOptionalArgumentValue(kExceptionArg);
198  auto context = Parameter<Context>(Descriptor::kContext);
199
200  GeneratorPrototypeResume(&args, receiver, exception, context,
201                           JSGeneratorObject::kThrow,
202                           "[Generator].prototype.throw");
203}
204
205// TODO(cbruni): Merge with corresponding bytecode handler.
206TF_BUILTIN(SuspendGeneratorBaseline, GeneratorBuiltinsAssembler) {
207  auto generator = Parameter<JSGeneratorObject>(Descriptor::kGeneratorObject);
208  auto context = LoadContextFromBaseline();
209  StoreJSGeneratorObjectContext(generator, context);
210  auto suspend_id = SmiTag(UncheckedParameter<IntPtrT>(Descriptor::kSuspendId));
211  StoreJSGeneratorObjectContinuation(generator, suspend_id);
212  // Store the bytecode offset in the [input_or_debug_pos] field, to be used by
213  // the inspector.
214  auto bytecode_offset =
215      SmiTag(UncheckedParameter<IntPtrT>(Descriptor::kBytecodeOffset));
216  // Avoid the write barrier by using the generic helper.
217  StoreObjectFieldNoWriteBarrier(
218      generator, JSGeneratorObject::kInputOrDebugPosOffset, bytecode_offset);
219
220  TNode<JSFunction> closure = LoadJSGeneratorObjectFunction(generator);
221  auto sfi = LoadJSFunctionSharedFunctionInfo(closure);
222  CSA_DCHECK(this,
223             Word32BinaryNot(IsSharedFunctionInfoDontAdaptArguments(sfi)));
224  TNode<IntPtrT> formal_parameter_count = Signed(ChangeUint32ToWord(
225      LoadSharedFunctionInfoFormalParameterCountWithoutReceiver(sfi)));
226
227  TNode<FixedArray> parameters_and_registers =
228      LoadJSGeneratorObjectParametersAndRegisters(generator);
229  auto parameters_and_registers_length =
230      SmiUntag(LoadFixedArrayBaseLength(parameters_and_registers));
231
232  // Copy over the function parameters
233  auto parameter_base_index = IntPtrConstant(
234      interpreter::Register::FromParameterIndex(0).ToOperand() + 1);
235  CSA_CHECK(this, UintPtrLessThan(formal_parameter_count,
236                                  parameters_and_registers_length));
237  auto parent_frame_pointer = LoadParentFramePointer();
238  BuildFastLoop<IntPtrT>(
239      IntPtrConstant(0), formal_parameter_count,
240      [=](TNode<IntPtrT> index) {
241        auto reg_index = IntPtrAdd(parameter_base_index, index);
242        TNode<Object> value = LoadFullTagged(parent_frame_pointer,
243                                             TimesSystemPointerSize(reg_index));
244        UnsafeStoreFixedArrayElement(parameters_and_registers, index, value);
245      },
246      1, IndexAdvanceMode::kPost);
247
248  // Iterate over register file and write values into array.
249  // The mapping of register to array index must match that used in
250  // BytecodeGraphBuilder::VisitResumeGenerator.
251  auto register_base_index =
252      IntPtrAdd(formal_parameter_count,
253                IntPtrConstant(interpreter::Register(0).ToOperand()));
254  auto register_count = UncheckedParameter<IntPtrT>(Descriptor::kRegisterCount);
255  auto end_index = IntPtrAdd(formal_parameter_count, register_count);
256  CSA_CHECK(this, UintPtrLessThan(end_index, parameters_and_registers_length));
257  BuildFastLoop<IntPtrT>(
258      formal_parameter_count, end_index,
259      [=](TNode<IntPtrT> index) {
260        auto reg_index = IntPtrSub(register_base_index, index);
261        TNode<Object> value = LoadFullTagged(parent_frame_pointer,
262                                             TimesSystemPointerSize(reg_index));
263        UnsafeStoreFixedArrayElement(parameters_and_registers, index, value);
264      },
265      1, IndexAdvanceMode::kPost);
266
267  // The return value is unused, defaulting to undefined.
268  Return(UndefinedConstant());
269}
270
271// TODO(cbruni): Merge with corresponding bytecode handler.
272TF_BUILTIN(ResumeGeneratorBaseline, GeneratorBuiltinsAssembler) {
273  auto generator = Parameter<JSGeneratorObject>(Descriptor::kGeneratorObject);
274  TNode<JSFunction> closure = LoadJSGeneratorObjectFunction(generator);
275  auto sfi = LoadJSFunctionSharedFunctionInfo(closure);
276  CSA_DCHECK(this,
277             Word32BinaryNot(IsSharedFunctionInfoDontAdaptArguments(sfi)));
278  TNode<IntPtrT> formal_parameter_count = Signed(ChangeUint32ToWord(
279      LoadSharedFunctionInfoFormalParameterCountWithoutReceiver(sfi)));
280
281  TNode<FixedArray> parameters_and_registers =
282      LoadJSGeneratorObjectParametersAndRegisters(generator);
283
284  // Iterate over array and write values into register file.  Also erase the
285  // array contents to not keep them alive artificially.
286  auto register_base_index =
287      IntPtrAdd(formal_parameter_count,
288                IntPtrConstant(interpreter::Register(0).ToOperand()));
289  auto register_count = UncheckedParameter<IntPtrT>(Descriptor::kRegisterCount);
290  auto end_index = IntPtrAdd(formal_parameter_count, register_count);
291  auto parameters_and_registers_length =
292      SmiUntag(LoadFixedArrayBaseLength(parameters_and_registers));
293  CSA_CHECK(this, UintPtrLessThan(end_index, parameters_and_registers_length));
294  auto parent_frame_pointer = LoadParentFramePointer();
295  BuildFastLoop<IntPtrT>(
296      formal_parameter_count, end_index,
297      [=](TNode<IntPtrT> index) {
298        TNode<Object> value =
299            UnsafeLoadFixedArrayElement(parameters_and_registers, index);
300        auto reg_index = IntPtrSub(register_base_index, index);
301        StoreFullTaggedNoWriteBarrier(parent_frame_pointer,
302                                      TimesSystemPointerSize(reg_index), value);
303        UnsafeStoreFixedArrayElement(parameters_and_registers, index,
304                                     StaleRegisterConstant(),
305                                     SKIP_WRITE_BARRIER);
306      },
307      1, IndexAdvanceMode::kPost);
308
309  Return(LoadJSGeneratorObjectInputOrDebugPos(generator));
310}
311
312}  // namespace internal
313}  // namespace v8
314