1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * vim: set ts=8 sts=2 et sw=2 tw=80:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #ifndef vm_GeneratorObject_h
8 #define vm_GeneratorObject_h
9 
10 #include "frontend/ParserAtom.h"  // frontend::TaggedParserAtomIndex
11 #include "js/Class.h"
12 #include "vm/ArgumentsObject.h"
13 #include "vm/ArrayObject.h"
14 #include "vm/BytecodeUtil.h"
15 #include "vm/GeneratorResumeKind.h"  // GeneratorResumeKind
16 #include "vm/JSContext.h"
17 #include "vm/JSObject.h"
18 #include "vm/Stack.h"
19 
20 namespace js {
21 
22 extern const JSClass GeneratorFunctionClass;
23 
24 class AbstractGeneratorObject : public NativeObject {
25  public:
26   // Magic value stored in the resumeIndex slot when the generator is
27   // running or closing. See the resumeIndex comment below.
28   static const int32_t RESUME_INDEX_RUNNING = INT32_MAX;
29 
30   enum {
31     CALLEE_SLOT = 0,
32     ENV_CHAIN_SLOT,
33     ARGS_OBJ_SLOT,
34     STACK_STORAGE_SLOT,
35     RESUME_INDEX_SLOT,
36     RESERVED_SLOTS
37   };
38 
39   // Maximum number of fixed stack slots in a generator or async function
40   // script. If a script would have more, we instead store some variables in
41   // heap EnvironmentObjects.
42   //
43   // This limit is a performance heuristic. Stack slots reduce allocations,
44   // and `Local` opcodes are a bit faster than `AliasedVar` ones; but at each
45   // `yield` or `await` the stack slots must be memcpy'd into a
46   // GeneratorObject. At some point the memcpy is too much. The limit is
47   // plenty for typical human-authored code.
48   static constexpr uint32_t FixedSlotLimit = 256;
49 
50  private:
51   static JSObject* createModuleGenerator(JSContext* cx, AbstractFramePtr frame);
52 
53  public:
54   static JSObject* createFromFrame(JSContext* cx, AbstractFramePtr frame);
55   static AbstractGeneratorObject* create(JSContext* cx, HandleFunction callee,
56                                          HandleScript script,
57                                          HandleObject environmentChain,
58                                          Handle<ArgumentsObject*> argsObject);
59 
60   static bool resume(JSContext* cx, InterpreterActivation& activation,
61                      Handle<AbstractGeneratorObject*> genObj, HandleValue arg,
62                      HandleValue resumeKind);
63 
64   static bool suspend(JSContext* cx, HandleObject obj, AbstractFramePtr frame,
65                       jsbytecode* pc, unsigned nvalues);
66 
67   static void finalSuspend(HandleObject obj);
68 
callee()69   JSFunction& callee() const {
70     return getFixedSlot(CALLEE_SLOT).toObject().as<JSFunction>();
71   }
setCallee(JSFunction & callee)72   void setCallee(JSFunction& callee) {
73     setFixedSlot(CALLEE_SLOT, ObjectValue(callee));
74   }
75 
environmentChain()76   JSObject& environmentChain() const {
77     return getFixedSlot(ENV_CHAIN_SLOT).toObject();
78   }
setEnvironmentChain(JSObject & envChain)79   void setEnvironmentChain(JSObject& envChain) {
80     setFixedSlot(ENV_CHAIN_SLOT, ObjectValue(envChain));
81   }
82 
hasArgsObj()83   bool hasArgsObj() const { return getFixedSlot(ARGS_OBJ_SLOT).isObject(); }
argsObj()84   ArgumentsObject& argsObj() const {
85     return getFixedSlot(ARGS_OBJ_SLOT).toObject().as<ArgumentsObject>();
86   }
setArgsObj(ArgumentsObject & argsObj)87   void setArgsObj(ArgumentsObject& argsObj) {
88     setFixedSlot(ARGS_OBJ_SLOT, ObjectValue(argsObj));
89   }
90 
hasStackStorage()91   bool hasStackStorage() const {
92     return getFixedSlot(STACK_STORAGE_SLOT).isObject();
93   }
isStackStorageEmpty()94   bool isStackStorageEmpty() const {
95     return stackStorage().getDenseInitializedLength() == 0;
96   }
stackStorage()97   ArrayObject& stackStorage() const {
98     return getFixedSlot(STACK_STORAGE_SLOT).toObject().as<ArrayObject>();
99   }
setStackStorage(ArrayObject & stackStorage)100   void setStackStorage(ArrayObject& stackStorage) {
101     setFixedSlot(STACK_STORAGE_SLOT, ObjectValue(stackStorage));
102   }
103 
104   // Access stack storage. Requires `hasStackStorage() && isSuspended()`.
105   // `slot` is the index of the desired local in the stack frame when this
106   // generator is *not* suspended.
107   const Value& getUnaliasedLocal(uint32_t slot) const;
108   void setUnaliasedLocal(uint32_t slot, const Value& value);
109 
110   // The resumeIndex slot is abused for a few purposes.  It's undefined if
111   // it hasn't been set yet (before the initial yield), and null if the
112   // generator is closed. If the generator is running, the resumeIndex is
113   // RESUME_INDEX_RUNNING.
114   //
115   // If the generator is suspended, it's the resumeIndex (stored as
116   // JSOp::InitialYield/JSOp::Yield/JSOp::Await operand) of the yield
117   // instruction that suspended the generator. The resumeIndex can be mapped to
118   // the bytecode offset (interpreter) or to the native code offset (JIT).
119 
isBeforeInitialYield()120   bool isBeforeInitialYield() const {
121     return getFixedSlot(RESUME_INDEX_SLOT).isUndefined();
122   }
isRunning()123   bool isRunning() const {
124     return getFixedSlot(RESUME_INDEX_SLOT) == Int32Value(RESUME_INDEX_RUNNING);
125   }
isSuspended()126   bool isSuspended() const {
127     // Note: also update Baseline's IsSuspendedGenerator code if this
128     // changes.
129     Value resumeIndex = getFixedSlot(RESUME_INDEX_SLOT);
130     return resumeIndex.isInt32() &&
131            resumeIndex.toInt32() < RESUME_INDEX_RUNNING;
132   }
setRunning()133   void setRunning() {
134     MOZ_ASSERT(isSuspended());
135     setFixedSlot(RESUME_INDEX_SLOT, Int32Value(RESUME_INDEX_RUNNING));
136   }
setResumeIndex(jsbytecode * pc)137   void setResumeIndex(jsbytecode* pc) {
138     MOZ_ASSERT(JSOp(*pc) == JSOp::InitialYield || JSOp(*pc) == JSOp::Yield ||
139                JSOp(*pc) == JSOp::Await);
140 
141     MOZ_ASSERT_IF(JSOp(*pc) == JSOp::InitialYield,
142                   getFixedSlot(RESUME_INDEX_SLOT).isUndefined());
143     MOZ_ASSERT_IF(JSOp(*pc) != JSOp::InitialYield, isRunning());
144 
145     uint32_t resumeIndex = GET_UINT24(pc);
146     MOZ_ASSERT(resumeIndex < uint32_t(RESUME_INDEX_RUNNING));
147 
148     setFixedSlot(RESUME_INDEX_SLOT, Int32Value(resumeIndex));
149     MOZ_ASSERT(isSuspended());
150   }
setResumeIndex(int32_t resumeIndex)151   void setResumeIndex(int32_t resumeIndex) {
152     setFixedSlot(RESUME_INDEX_SLOT, Int32Value(resumeIndex));
153   }
resumeIndex()154   uint32_t resumeIndex() const {
155     MOZ_ASSERT(isSuspended());
156     return getFixedSlot(RESUME_INDEX_SLOT).toInt32();
157   }
isClosed()158   bool isClosed() const { return getFixedSlot(CALLEE_SLOT).isNull(); }
setClosed()159   void setClosed() {
160     setFixedSlot(CALLEE_SLOT, NullValue());
161     setFixedSlot(ENV_CHAIN_SLOT, NullValue());
162     setFixedSlot(ARGS_OBJ_SLOT, NullValue());
163     setFixedSlot(STACK_STORAGE_SLOT, NullValue());
164     setFixedSlot(RESUME_INDEX_SLOT, NullValue());
165   }
166 
167   bool isAfterYield();
168   bool isAfterAwait();
169 
170  private:
171   bool isAfterYieldOrAwait(JSOp op);
172 
173  public:
174   void trace(JSTracer* trc);
175 
offsetOfCalleeSlot()176   static size_t offsetOfCalleeSlot() { return getFixedSlotOffset(CALLEE_SLOT); }
offsetOfEnvironmentChainSlot()177   static size_t offsetOfEnvironmentChainSlot() {
178     return getFixedSlotOffset(ENV_CHAIN_SLOT);
179   }
offsetOfArgsObjSlot()180   static size_t offsetOfArgsObjSlot() {
181     return getFixedSlotOffset(ARGS_OBJ_SLOT);
182   }
offsetOfResumeIndexSlot()183   static size_t offsetOfResumeIndexSlot() {
184     return getFixedSlotOffset(RESUME_INDEX_SLOT);
185   }
offsetOfStackStorageSlot()186   static size_t offsetOfStackStorageSlot() {
187     return getFixedSlotOffset(STACK_STORAGE_SLOT);
188   }
189 
calleeSlot()190   static size_t calleeSlot() { return CALLEE_SLOT; }
envChainSlot()191   static size_t envChainSlot() { return ENV_CHAIN_SLOT; }
argsObjectSlot()192   static size_t argsObjectSlot() { return ARGS_OBJ_SLOT; }
stackStorageSlot()193   static size_t stackStorageSlot() { return STACK_STORAGE_SLOT; }
resumeIndexSlot()194   static size_t resumeIndexSlot() { return RESUME_INDEX_SLOT; }
195 
196 #ifdef DEBUG
197   void dump() const;
198 #endif
199 };
200 
201 class GeneratorObject : public AbstractGeneratorObject {
202  public:
203   enum { RESERVED_SLOTS = AbstractGeneratorObject::RESERVED_SLOTS };
204 
205   static const JSClass class_;
206   static const JSClassOps classOps_;
207 
208   static GeneratorObject* create(JSContext* cx, HandleFunction fun);
209 };
210 
211 bool GeneratorThrowOrReturn(JSContext* cx, AbstractFramePtr frame,
212                             Handle<AbstractGeneratorObject*> obj,
213                             HandleValue val, GeneratorResumeKind resumeKind);
214 
215 /**
216  * Return the generator object associated with the given frame. The frame must
217  * be a call frame for a generator.
218  *
219  * This may return nullptr at certain points in the generator lifecycle:
220  *
221  * - While a generator call evaluates default argument values and performs
222  *   destructuring, which occurs before the generator object is created.
223  *
224  * - Between the `Generator` instruction and the `SetAliasedVar .generator`
225  *   instruction, at which point the generator object does exist, but is held
226  *   only on the stack, and not the `.generator` pseudo-variable this function
227  *   consults.
228  */
229 AbstractGeneratorObject* GetGeneratorObjectForFrame(JSContext* cx,
230                                                     AbstractFramePtr frame);
231 
232 /**
233  * If `env` or any enclosing environment is a `CallObject` associated with a
234  * generator object, return the generator.
235  *
236  * Otherwise `env` is not in a generator or async function, or the generator
237  * object hasn't been created yet; return nullptr with no pending exception.
238  */
239 AbstractGeneratorObject* GetGeneratorObjectForEnvironment(JSContext* cx,
240                                                           HandleObject env);
241 
242 GeneratorResumeKind ParserAtomToResumeKind(
243     JSContext* cx, frontend::TaggedParserAtomIndex atom);
244 JSAtom* ResumeKindToAtom(JSContext* cx, GeneratorResumeKind kind);
245 
246 }  // namespace js
247 
248 template <>
249 bool JSObject::is<js::AbstractGeneratorObject>() const;
250 
251 #endif /* vm_GeneratorObject_h */
252