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
13 namespace v8 {
14 namespace internal {
15
16 class GeneratorBuiltinsAssembler : public CodeStubAssembler {
17 public:
GeneratorBuiltinsAssembler(compiler::CodeAssemblerState * state)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
InnerResume(CodeStubArguments * args,TNode<JSGeneratorObject> receiver,TNode<Object> value,TNode<Context> context,JSGeneratorObject::ResumeMode resume_mode,char const * const method_name)39 void 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
GeneratorPrototypeResume(CodeStubArguments * args,TNode<Object> receiver,TNode<Object> value,TNode<Context> context,JSGeneratorObject::ResumeMode resume_mode,char const * const method_name)126 void 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
TF_BUILTIN(AsyncModuleEvaluate,GeneratorBuiltinsAssembler)137 TF_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
TF_BUILTIN(GeneratorPrototypeNext,GeneratorBuiltinsAssembler)158 TF_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
TF_BUILTIN(GeneratorPrototypeReturn,GeneratorBuiltinsAssembler)174 TF_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
TF_BUILTIN(GeneratorPrototypeThrow,GeneratorBuiltinsAssembler)190 TF_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.
TF_BUILTIN(SuspendGeneratorBaseline,GeneratorBuiltinsAssembler)206 TF_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.
TF_BUILTIN(ResumeGeneratorBaseline,GeneratorBuiltinsAssembler)272 TF_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