/src/serenity/Userland/Libraries/LibJS/Runtime/GeneratorObject.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <AK/TemporaryChange.h> |
8 | | #include <LibJS/Bytecode/Generator.h> |
9 | | #include <LibJS/Bytecode/Interpreter.h> |
10 | | #include <LibJS/Runtime/GeneratorObject.h> |
11 | | #include <LibJS/Runtime/GeneratorPrototype.h> |
12 | | #include <LibJS/Runtime/GlobalObject.h> |
13 | | #include <LibJS/Runtime/Iterator.h> |
14 | | |
15 | | namespace JS { |
16 | | |
17 | | JS_DEFINE_ALLOCATOR(GeneratorObject); |
18 | | |
19 | | ThrowCompletionOr<NonnullGCPtr<GeneratorObject>> GeneratorObject::create(Realm& realm, Value initial_value, ECMAScriptFunctionObject* generating_function, NonnullOwnPtr<ExecutionContext> execution_context) |
20 | 0 | { |
21 | 0 | auto& vm = realm.vm(); |
22 | | // This is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png) |
23 | 0 | Value generating_function_prototype; |
24 | 0 | if (generating_function->kind() == FunctionKind::Async) { |
25 | | // We implement async functions by transforming them to generator function in the bytecode |
26 | | // interpreter. However an async function does not have a prototype and should not be |
27 | | // changed thus we hardcode the prototype. |
28 | 0 | generating_function_prototype = realm.intrinsics().generator_prototype(); |
29 | 0 | } else { |
30 | 0 | generating_function_prototype = TRY(generating_function->get(vm.names.prototype)); |
31 | 0 | } |
32 | 0 | auto generating_function_prototype_object = TRY(generating_function_prototype.to_object(vm)); |
33 | 0 | auto object = realm.heap().allocate<GeneratorObject>(realm, realm, generating_function_prototype_object, move(execution_context)); |
34 | 0 | object->m_generating_function = generating_function; |
35 | 0 | object->m_previous_value = initial_value; |
36 | 0 | return object; |
37 | 0 | } |
38 | | |
39 | | GeneratorObject::GeneratorObject(Realm&, Object& prototype, NonnullOwnPtr<ExecutionContext> context, Optional<StringView> generator_brand) |
40 | 0 | : Object(ConstructWithPrototypeTag::Tag, prototype) |
41 | 0 | , m_execution_context(move(context)) |
42 | 0 | , m_generator_brand(move(generator_brand)) |
43 | 0 | { |
44 | 0 | } |
45 | | |
46 | | void GeneratorObject::visit_edges(Cell::Visitor& visitor) |
47 | 0 | { |
48 | 0 | Base::visit_edges(visitor); |
49 | 0 | visitor.visit(m_generating_function); |
50 | 0 | visitor.visit(m_previous_value); |
51 | 0 | m_execution_context->visit_edges(visitor); |
52 | 0 | } |
53 | | |
54 | | // 27.5.3.2 GeneratorValidate ( generator, generatorBrand ), https://tc39.es/ecma262/#sec-generatorvalidate |
55 | | ThrowCompletionOr<GeneratorObject::GeneratorState> GeneratorObject::validate(VM& vm, Optional<StringView> const& generator_brand) |
56 | 0 | { |
57 | | // 1. Perform ? RequireInternalSlot(generator, [[GeneratorState]]). |
58 | | // 2. Perform ? RequireInternalSlot(generator, [[GeneratorBrand]]). |
59 | | // NOTE: Already done by the caller of resume or resume_abrupt, as they wouldn't have a GeneratorObject otherwise. |
60 | | |
61 | | // 3. If generator.[[GeneratorBrand]] is not the same value as generatorBrand, throw a TypeError exception. |
62 | 0 | if (m_generator_brand != generator_brand) |
63 | 0 | return vm.throw_completion<TypeError>(ErrorType::GeneratorBrandMismatch, m_generator_brand.value_or("<empty>"sv), generator_brand.value_or("<empty>"sv)); |
64 | | |
65 | | // 4. Assert: generator also has a [[GeneratorContext]] internal slot. |
66 | | // NOTE: Done by already being a GeneratorObject. |
67 | | |
68 | | // 5. Let state be generator.[[GeneratorState]]. |
69 | 0 | auto state = m_generator_state; |
70 | | |
71 | | // 6. If state is executing, throw a TypeError exception. |
72 | 0 | if (state == GeneratorState::Executing) |
73 | 0 | return vm.throw_completion<TypeError>(ErrorType::GeneratorAlreadyExecuting); |
74 | | |
75 | | // 7. Return state. |
76 | 0 | return state; |
77 | 0 | } |
78 | | |
79 | | ThrowCompletionOr<Value> GeneratorObject::execute(VM& vm, Completion const& completion) |
80 | 0 | { |
81 | | // Loosely based on step 4 of https://tc39.es/ecma262/#sec-generatorstart mixed with https://tc39.es/ecma262/#sec-generatoryield at the end. |
82 | |
|
83 | 0 | VERIFY(completion.value().has_value()); |
84 | | |
85 | 0 | auto generated_value = [](Value value) -> Value { |
86 | 0 | if (value.is_object()) |
87 | 0 | return value.as_object().get_without_side_effects("result"); |
88 | 0 | return value.is_empty() ? js_undefined() : value; |
89 | 0 | }; |
90 | |
|
91 | 0 | auto generated_continuation = [&](Value value) -> Optional<size_t> { |
92 | 0 | if (value.is_object()) { |
93 | 0 | auto number_value = value.as_object().get_without_side_effects("continuation"); |
94 | 0 | if (number_value.is_null()) |
95 | 0 | return {}; |
96 | 0 | return static_cast<u64>(number_value.as_double()); |
97 | 0 | } |
98 | 0 | return {}; |
99 | 0 | }; |
100 | |
|
101 | 0 | auto& realm = *vm.current_realm(); |
102 | 0 | auto completion_object = Object::create(realm, nullptr); |
103 | 0 | completion_object->define_direct_property(vm.names.type, Value(to_underlying(completion.type())), default_attributes); |
104 | 0 | completion_object->define_direct_property(vm.names.value, completion.value().value(), default_attributes); |
105 | |
|
106 | 0 | auto& bytecode_interpreter = vm.bytecode_interpreter(); |
107 | |
|
108 | 0 | auto const next_block = generated_continuation(m_previous_value); |
109 | | |
110 | | // We should never enter `execute` again after the generator is complete. |
111 | 0 | VERIFY(next_block.has_value()); |
112 | | |
113 | 0 | auto next_result = bytecode_interpreter.run_executable(*m_generating_function->bytecode_executable(), next_block, completion_object); |
114 | |
|
115 | 0 | vm.pop_execution_context(); |
116 | |
|
117 | 0 | auto result_value = move(next_result.value); |
118 | 0 | if (result_value.is_throw_completion()) { |
119 | | // Uncaught exceptions disable the generator. |
120 | 0 | m_generator_state = GeneratorState::Completed; |
121 | 0 | return result_value; |
122 | 0 | } |
123 | 0 | m_previous_value = result_value.release_value(); |
124 | 0 | bool done = !generated_continuation(m_previous_value).has_value(); |
125 | |
|
126 | 0 | m_generator_state = done ? GeneratorState::Completed : GeneratorState::SuspendedYield; |
127 | 0 | return create_iterator_result_object(vm, generated_value(m_previous_value), done); |
128 | 0 | } |
129 | | |
130 | | // 27.5.3.3 GeneratorResume ( generator, value, generatorBrand ), https://tc39.es/ecma262/#sec-generatorresume |
131 | | ThrowCompletionOr<Value> GeneratorObject::resume(VM& vm, Value value, Optional<StringView> const& generator_brand) |
132 | 0 | { |
133 | | // 1. Let state be ? GeneratorValidate(generator, generatorBrand). |
134 | 0 | auto state = TRY(validate(vm, generator_brand)); |
135 | | |
136 | | // 2. If state is completed, return CreateIterResultObject(undefined, true). |
137 | 0 | if (state == GeneratorState::Completed) |
138 | 0 | return create_iterator_result_object(vm, js_undefined(), true); |
139 | | |
140 | | // 3. Assert: state is either suspendedStart or suspendedYield. |
141 | 0 | VERIFY(state == GeneratorState::SuspendedStart || state == GeneratorState::SuspendedYield); |
142 | | |
143 | | // 4. Let genContext be generator.[[GeneratorContext]]. |
144 | 0 | auto& generator_context = m_execution_context; |
145 | | |
146 | | // 5. Let methodContext be the running execution context. |
147 | 0 | auto const& method_context = vm.running_execution_context(); |
148 | | |
149 | | // FIXME: 6. Suspend methodContext. |
150 | | |
151 | | // 8. Push genContext onto the execution context stack; genContext is now the running execution context. |
152 | | // NOTE: This is done out of order as to not permanently disable the generator if push_execution_context throws, |
153 | | // as `resume` will immediately throw when [[GeneratorState]] is "executing", never allowing the state to change. |
154 | 0 | TRY(vm.push_execution_context(*generator_context, {})); |
155 | | |
156 | | // 7. Set generator.[[GeneratorState]] to executing. |
157 | 0 | m_generator_state = GeneratorState::Executing; |
158 | | |
159 | | // 9. Resume the suspended evaluation of genContext using NormalCompletion(value) as the result of the operation that suspended it. Let result be the value returned by the resumed computation. |
160 | 0 | auto result = execute(vm, normal_completion(value)); |
161 | | |
162 | | // 10. Assert: When we return here, genContext has already been removed from the execution context stack and methodContext is the currently running execution context. |
163 | 0 | VERIFY(&vm.running_execution_context() == &method_context); |
164 | | |
165 | | // 11. Return ? result. |
166 | 0 | return result; |
167 | 0 | } |
168 | | |
169 | | // 27.5.3.4 GeneratorResumeAbrupt ( generator, abruptCompletion, generatorBrand ), https://tc39.es/ecma262/#sec-generatorresumeabrupt |
170 | | ThrowCompletionOr<Value> GeneratorObject::resume_abrupt(JS::VM& vm, JS::Completion abrupt_completion, Optional<StringView> const& generator_brand) |
171 | 0 | { |
172 | | // Not part of the spec, but the spec assumes abruptCompletion.[[Value]] is not empty. |
173 | 0 | VERIFY(abrupt_completion.value().has_value()); |
174 | | |
175 | | // 1. Let state be ? GeneratorValidate(generator, generatorBrand). |
176 | 0 | auto state = TRY(validate(vm, generator_brand)); |
177 | | |
178 | | // 2. If state is suspendedStart, then |
179 | 0 | if (state == GeneratorState::SuspendedStart) { |
180 | | // a. Set generator.[[GeneratorState]] to completed. |
181 | 0 | m_generator_state = GeneratorState::Completed; |
182 | | |
183 | | // b. Once a generator enters the completed state it never leaves it and its associated execution context is never resumed. Any execution state associated with generator can be discarded at this point. |
184 | | // We don't currently discard anything. |
185 | | |
186 | | // c. Set state to completed. |
187 | 0 | state = GeneratorState::Completed; |
188 | 0 | } |
189 | | |
190 | | // 3. If state is completed, then |
191 | 0 | if (state == GeneratorState::Completed) { |
192 | | // a. If abruptCompletion.[[Type]] is return, then |
193 | 0 | if (abrupt_completion.type() == Completion::Type::Return) { |
194 | | // i. Return CreateIterResultObject(abruptCompletion.[[Value]], true). |
195 | 0 | return create_iterator_result_object(vm, abrupt_completion.value().value(), true); |
196 | 0 | } |
197 | | |
198 | | // b. Return ? abruptCompletion. |
199 | 0 | return abrupt_completion; |
200 | 0 | } |
201 | | |
202 | | // 4. Assert: state is suspendedYield. |
203 | 0 | VERIFY(state == GeneratorState::SuspendedYield); |
204 | | |
205 | | // 5. Let genContext be generator.[[GeneratorContext]]. |
206 | 0 | auto& generator_context = m_execution_context; |
207 | | |
208 | | // 6. Let methodContext be the running execution context. |
209 | 0 | auto const& method_context = vm.running_execution_context(); |
210 | | |
211 | | // FIXME: 7. Suspend methodContext. |
212 | | |
213 | | // 9. Push genContext onto the execution context stack; genContext is now the running execution context. |
214 | | // NOTE: This is done out of order as to not permanently disable the generator if push_execution_context throws, |
215 | | // as `resume_abrupt` will immediately throw when [[GeneratorState]] is "executing", never allowing the state to change. |
216 | 0 | TRY(vm.push_execution_context(*generator_context, {})); |
217 | | |
218 | | // 8. Set generator.[[GeneratorState]] to executing. |
219 | 0 | m_generator_state = GeneratorState::Executing; |
220 | | |
221 | | // 10. Resume the suspended evaluation of genContext using abruptCompletion as the result of the operation that suspended it. Let result be the Completion Record returned by the resumed computation. |
222 | 0 | auto result = execute(vm, abrupt_completion); |
223 | | |
224 | | // 11. Assert: When we return here, genContext has already been removed from the execution context stack and methodContext is the currently running execution context. |
225 | 0 | VERIFY(&vm.running_execution_context() == &method_context); |
226 | | |
227 | | // 12. Return ? result. |
228 | 0 | return result; |
229 | 0 | } |
230 | | |
231 | | } |