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