summaryrefslogtreecommitdiffstats
path: root/js/src/vm/GeneratorObject.h
blob: 1be5ff8ba25a03bba692002611c3850d1042ae4a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef vm_GeneratorObject_h
#define vm_GeneratorObject_h

#include "js/Class.h"
#include "vm/ArgumentsObject.h"
#include "vm/ArrayObject.h"
#include "vm/BytecodeUtil.h"
#include "vm/GeneratorResumeKind.h"  // GeneratorResumeKind
#include "vm/JSObject.h"
#include "vm/Stack.h"

namespace js {

class InterpreterActivation;

namespace frontend {
class TaggedParserAtomIndex;
}

extern const JSClass GeneratorFunctionClass;

class AbstractGeneratorObject : public NativeObject {
 public:
  // Magic value stored in the resumeIndex slot when the generator is
  // running or closing. See the resumeIndex comment below.
  static const int32_t RESUME_INDEX_RUNNING = INT32_MAX;

  enum {
    CALLEE_SLOT = 0,
    ENV_CHAIN_SLOT,
    ARGS_OBJ_SLOT,
    STACK_STORAGE_SLOT,
    RESUME_INDEX_SLOT,
    RESERVED_SLOTS
  };

  // Maximum number of fixed stack slots in a generator or async function
  // script. If a script would have more, we instead store some variables in
  // heap EnvironmentObjects.
  //
  // This limit is a performance heuristic. Stack slots reduce allocations,
  // and `Local` opcodes are a bit faster than `AliasedVar` ones; but at each
  // `yield` or `await` the stack slots must be memcpy'd into a
  // GeneratorObject. At some point the memcpy is too much. The limit is
  // plenty for typical human-authored code.
  static constexpr uint32_t FixedSlotLimit = 256;

 private:
  static JSObject* createModuleGenerator(JSContext* cx, AbstractFramePtr frame);

 public:
  static JSObject* createFromFrame(JSContext* cx, AbstractFramePtr frame);
  static AbstractGeneratorObject* create(JSContext* cx, HandleFunction callee,
                                         HandleScript script,
                                         HandleObject environmentChain,
                                         Handle<ArgumentsObject*> argsObject);

  static bool resume(JSContext* cx, InterpreterActivation& activation,
                     Handle<AbstractGeneratorObject*> genObj, HandleValue arg,
                     HandleValue resumeKind);

  static bool suspend(JSContext* cx, HandleObject obj, AbstractFramePtr frame,
                      const jsbytecode* pc, unsigned nvalues);

  static void finalSuspend(HandleObject obj);

  JSFunction& callee() const {
    return getFixedSlot(CALLEE_SLOT).toObject().as<JSFunction>();
  }
  void setCallee(JSFunction& callee) {
    setFixedSlot(CALLEE_SLOT, ObjectValue(callee));
  }

  JSObject& environmentChain() const {
    return getFixedSlot(ENV_CHAIN_SLOT).toObject();
  }
  void setEnvironmentChain(JSObject& envChain) {
    setFixedSlot(ENV_CHAIN_SLOT, ObjectValue(envChain));
  }

  bool hasArgsObj() const { return getFixedSlot(ARGS_OBJ_SLOT).isObject(); }
  ArgumentsObject& argsObj() const {
    return getFixedSlot(ARGS_OBJ_SLOT).toObject().as<ArgumentsObject>();
  }
  void setArgsObj(ArgumentsObject& argsObj) {
    setFixedSlot(ARGS_OBJ_SLOT, ObjectValue(argsObj));
  }

  bool hasStackStorage() const {
    return getFixedSlot(STACK_STORAGE_SLOT).isObject();
  }
  bool isStackStorageEmpty() const {
    return stackStorage().getDenseInitializedLength() == 0;
  }
  ArrayObject& stackStorage() const {
    return getFixedSlot(STACK_STORAGE_SLOT).toObject().as<ArrayObject>();
  }
  void setStackStorage(ArrayObject& stackStorage) {
    setFixedSlot(STACK_STORAGE_SLOT, ObjectValue(stackStorage));
  }

  // Access stack storage. Requires `hasStackStorage() && isSuspended()`.
  // `slot` is the index of the desired local in the stack frame when this
  // generator is *not* suspended.
  const Value& getUnaliasedLocal(uint32_t slot) const;
  void setUnaliasedLocal(uint32_t slot, const Value& value);

  // The resumeIndex slot is abused for a few purposes.  It's undefined if
  // it hasn't been set yet (before the initial yield), and null if the
  // generator is closed. If the generator is running, the resumeIndex is
  // RESUME_INDEX_RUNNING.
  //
  // If the generator is suspended, it's the resumeIndex (stored as
  // JSOp::InitialYield/JSOp::Yield/JSOp::Await operand) of the yield
  // instruction that suspended the generator. The resumeIndex can be mapped to
  // the bytecode offset (interpreter) or to the native code offset (JIT).

  bool isBeforeInitialYield() const {
    return getFixedSlot(RESUME_INDEX_SLOT).isUndefined();
  }
  bool isRunning() const {
    return getFixedSlot(RESUME_INDEX_SLOT) == Int32Value(RESUME_INDEX_RUNNING);
  }
  bool isSuspended() const {
    // Note: also update Baseline's IsSuspendedGenerator code if this
    // changes.
    Value resumeIndex = getFixedSlot(RESUME_INDEX_SLOT);
    return resumeIndex.isInt32() &&
           resumeIndex.toInt32() < RESUME_INDEX_RUNNING;
  }
  void setRunning() {
    MOZ_ASSERT(isSuspended());
    setFixedSlot(RESUME_INDEX_SLOT, Int32Value(RESUME_INDEX_RUNNING));
  }
  void setResumeIndex(const jsbytecode* pc) {
    MOZ_ASSERT(JSOp(*pc) == JSOp::InitialYield || JSOp(*pc) == JSOp::Yield ||
               JSOp(*pc) == JSOp::Await);

    MOZ_ASSERT_IF(JSOp(*pc) == JSOp::InitialYield,
                  getFixedSlot(RESUME_INDEX_SLOT).isUndefined());
    MOZ_ASSERT_IF(JSOp(*pc) != JSOp::InitialYield, isRunning());

    uint32_t resumeIndex = GET_UINT24(pc);
    MOZ_ASSERT(resumeIndex < uint32_t(RESUME_INDEX_RUNNING));

    setFixedSlot(RESUME_INDEX_SLOT, Int32Value(resumeIndex));
    MOZ_ASSERT(isSuspended());
  }
  void setResumeIndex(int32_t resumeIndex) {
    setFixedSlot(RESUME_INDEX_SLOT, Int32Value(resumeIndex));
  }
  uint32_t resumeIndex() const {
    MOZ_ASSERT(isSuspended());
    return getFixedSlot(RESUME_INDEX_SLOT).toInt32();
  }
  bool isClosed() const { return getFixedSlot(CALLEE_SLOT).isNull(); }
  void setClosed() {
    setFixedSlot(CALLEE_SLOT, NullValue());
    setFixedSlot(ENV_CHAIN_SLOT, NullValue());
    setFixedSlot(ARGS_OBJ_SLOT, NullValue());
    setFixedSlot(STACK_STORAGE_SLOT, NullValue());
    setFixedSlot(RESUME_INDEX_SLOT, NullValue());
  }

  bool isAfterYield();
  bool isAfterAwait();

 private:
  bool isAfterYieldOrAwait(JSOp op);

 public:
  void trace(JSTracer* trc);

  static size_t offsetOfCalleeSlot() { return getFixedSlotOffset(CALLEE_SLOT); }
  static size_t offsetOfEnvironmentChainSlot() {
    return getFixedSlotOffset(ENV_CHAIN_SLOT);
  }
  static size_t offsetOfArgsObjSlot() {
    return getFixedSlotOffset(ARGS_OBJ_SLOT);
  }
  static size_t offsetOfResumeIndexSlot() {
    return getFixedSlotOffset(RESUME_INDEX_SLOT);
  }
  static size_t offsetOfStackStorageSlot() {
    return getFixedSlotOffset(STACK_STORAGE_SLOT);
  }

  static size_t calleeSlot() { return CALLEE_SLOT; }
  static size_t envChainSlot() { return ENV_CHAIN_SLOT; }
  static size_t argsObjectSlot() { return ARGS_OBJ_SLOT; }
  static size_t stackStorageSlot() { return STACK_STORAGE_SLOT; }
  static size_t resumeIndexSlot() { return RESUME_INDEX_SLOT; }

#ifdef DEBUG
  void dump() const;
#endif
};

class GeneratorObject : public AbstractGeneratorObject {
 public:
  enum { RESERVED_SLOTS = AbstractGeneratorObject::RESERVED_SLOTS };

  static const JSClass class_;
  static const JSClassOps classOps_;

  static GeneratorObject* create(JSContext* cx, HandleFunction fun);
};

bool GeneratorThrowOrReturn(JSContext* cx, AbstractFramePtr frame,
                            Handle<AbstractGeneratorObject*> obj,
                            HandleValue val, GeneratorResumeKind resumeKind);

/**
 * Return the generator object associated with the given frame. The frame must
 * be a call frame for a generator.
 *
 * This may return nullptr at certain points in the generator lifecycle:
 *
 * - While a generator call evaluates default argument values and performs
 *   destructuring, which occurs before the generator object is created.
 *
 * - Between the `Generator` instruction and the `SetAliasedVar .generator`
 *   instruction, at which point the generator object does exist, but is held
 *   only on the stack, and not the `.generator` pseudo-variable this function
 *   consults.
 */
AbstractGeneratorObject* GetGeneratorObjectForFrame(JSContext* cx,
                                                    AbstractFramePtr frame);

/**
 * If `env` or any enclosing environment is a `CallObject` associated with a
 * generator object, return the generator.
 *
 * Otherwise `env` is not in a generator or async function, or the generator
 * object hasn't been created yet; return nullptr with no pending exception.
 */
AbstractGeneratorObject* GetGeneratorObjectForEnvironment(JSContext* cx,
                                                          HandleObject env);

GeneratorResumeKind ParserAtomToResumeKind(
    frontend::TaggedParserAtomIndex atom);
JSAtom* ResumeKindToAtom(JSContext* cx, GeneratorResumeKind kind);

}  // namespace js

template <>
bool JSObject::is<js::AbstractGeneratorObject>() const;

#endif /* vm_GeneratorObject_h */