michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* JS script descriptor. */ michael@0: michael@0: #ifndef jsscript_h michael@0: #define jsscript_h michael@0: michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "mozilla/PodOperations.h" michael@0: michael@0: #include "jsatom.h" michael@0: #ifdef JS_THREADSAFE michael@0: #include "jslock.h" michael@0: #endif michael@0: #include "jsobj.h" michael@0: #include "jsopcode.h" michael@0: #include "jstypes.h" michael@0: michael@0: #include "gc/Barrier.h" michael@0: #include "gc/Rooting.h" michael@0: #include "jit/IonCode.h" michael@0: #include "vm/Shape.h" michael@0: michael@0: namespace JS { michael@0: struct ScriptSourceInfo; michael@0: } michael@0: michael@0: namespace js { michael@0: michael@0: namespace jit { michael@0: struct BaselineScript; michael@0: struct IonScriptCounts; michael@0: } michael@0: michael@0: # define ION_DISABLED_SCRIPT ((js::jit::IonScript *)0x1) michael@0: # define ION_COMPILING_SCRIPT ((js::jit::IonScript *)0x2) michael@0: michael@0: # define BASELINE_DISABLED_SCRIPT ((js::jit::BaselineScript *)0x1) michael@0: michael@0: class BreakpointSite; michael@0: class BindingIter; michael@0: class LazyScript; michael@0: class RegExpObject; michael@0: struct SourceCompressionTask; michael@0: class Shape; michael@0: class WatchpointMap; michael@0: class NestedScopeObject; michael@0: michael@0: namespace frontend { michael@0: class BytecodeEmitter; michael@0: } michael@0: michael@0: } michael@0: michael@0: /* michael@0: * Type of try note associated with each catch or finally block, and also with michael@0: * for-in and other kinds of loops. Non-for-in loops do not need these notes michael@0: * for exception unwinding, but storing their boundaries here is helpful for michael@0: * heuristics that need to know whether a given op is inside a loop. michael@0: */ michael@0: typedef enum JSTryNoteKind { michael@0: JSTRY_CATCH, michael@0: JSTRY_FINALLY, michael@0: JSTRY_ITER, michael@0: JSTRY_LOOP michael@0: } JSTryNoteKind; michael@0: michael@0: /* michael@0: * Exception handling record. michael@0: */ michael@0: struct JSTryNote { michael@0: uint8_t kind; /* one of JSTryNoteKind */ michael@0: uint32_t stackDepth; /* stack depth upon exception handler entry */ michael@0: uint32_t start; /* start of the try statement or loop michael@0: relative to script->main */ michael@0: uint32_t length; /* length of the try statement or loop */ michael@0: }; michael@0: michael@0: namespace js { michael@0: michael@0: // A block scope has a range in bytecode: it is entered at some offset, and left michael@0: // at some later offset. Scopes can be nested. Given an offset, the michael@0: // BlockScopeNote containing that offset whose with the highest start value michael@0: // indicates the block scope. The block scope list is sorted by increasing michael@0: // start value. michael@0: // michael@0: // It is possible to leave a scope nonlocally, for example via a "break" michael@0: // statement, so there may be short bytecode ranges in a block scope in which we michael@0: // are popping the block chain in preparation for a goto. These exits are also michael@0: // nested with respect to outer scopes. The scopes in these exits are indicated michael@0: // by the "index" field, just like any other block. If a nonlocal exit pops the michael@0: // last block scope, the index will be NoBlockScopeIndex. michael@0: // michael@0: struct BlockScopeNote { michael@0: static const uint32_t NoBlockScopeIndex = UINT32_MAX; michael@0: michael@0: uint32_t index; // Index of NestedScopeObject in the object michael@0: // array, or NoBlockScopeIndex if there is no michael@0: // block scope in this range. michael@0: uint32_t start; // Bytecode offset at which this scope starts, michael@0: // from script->main(). michael@0: uint32_t length; // Bytecode length of scope. michael@0: uint32_t parent; // Index of parent block scope in notes, or UINT32_MAX. michael@0: }; michael@0: michael@0: struct ConstArray { michael@0: js::HeapValue *vector; /* array of indexed constant values */ michael@0: uint32_t length; michael@0: }; michael@0: michael@0: struct ObjectArray { michael@0: js::HeapPtrObject *vector; // Array of indexed objects. michael@0: uint32_t length; // Count of indexed objects. michael@0: }; michael@0: michael@0: struct TryNoteArray { michael@0: JSTryNote *vector; // Array of indexed try notes. michael@0: uint32_t length; // Count of indexed try notes. michael@0: }; michael@0: michael@0: struct BlockScopeArray { michael@0: BlockScopeNote *vector; // Array of indexed BlockScopeNote records. michael@0: uint32_t length; // Count of indexed try notes. michael@0: }; michael@0: michael@0: class Binding michael@0: { michael@0: // One JSScript stores one Binding per formal/variable so we use a michael@0: // packed-word representation. michael@0: uintptr_t bits_; michael@0: michael@0: static const uintptr_t KIND_MASK = 0x3; michael@0: static const uintptr_t ALIASED_BIT = 0x4; michael@0: static const uintptr_t NAME_MASK = ~(KIND_MASK | ALIASED_BIT); michael@0: michael@0: public: michael@0: // A "binding" is a formal, 'var', or 'const' declaration. A function's michael@0: // lexical scope is composed of these three kinds of bindings. michael@0: enum Kind { ARGUMENT, VARIABLE, CONSTANT }; michael@0: michael@0: explicit Binding() : bits_(0) {} michael@0: michael@0: Binding(PropertyName *name, Kind kind, bool aliased) { michael@0: JS_STATIC_ASSERT(CONSTANT <= KIND_MASK); michael@0: JS_ASSERT((uintptr_t(name) & ~NAME_MASK) == 0); michael@0: JS_ASSERT((uintptr_t(kind) & ~KIND_MASK) == 0); michael@0: bits_ = uintptr_t(name) | uintptr_t(kind) | (aliased ? ALIASED_BIT : 0); michael@0: } michael@0: michael@0: PropertyName *name() const { michael@0: return (PropertyName *)(bits_ & NAME_MASK); michael@0: } michael@0: michael@0: Kind kind() const { michael@0: return Kind(bits_ & KIND_MASK); michael@0: } michael@0: michael@0: bool aliased() const { michael@0: return bool(bits_ & ALIASED_BIT); michael@0: } michael@0: }; michael@0: michael@0: JS_STATIC_ASSERT(sizeof(Binding) == sizeof(uintptr_t)); michael@0: michael@0: class Bindings; michael@0: typedef InternalHandle InternalBindingsHandle; michael@0: michael@0: /* michael@0: * Formal parameters and local variables are stored in a shape tree michael@0: * path encapsulated within this class. This class represents bindings for michael@0: * both function and top-level scripts (the latter is needed to track names in michael@0: * strict mode eval code, to give such code its own lexical environment). michael@0: */ michael@0: class Bindings michael@0: { michael@0: friend class BindingIter; michael@0: friend class AliasedFormalIter; michael@0: michael@0: HeapPtr callObjShape_; michael@0: uintptr_t bindingArrayAndFlag_; michael@0: uint16_t numArgs_; michael@0: uint16_t numBlockScoped_; michael@0: uint32_t numVars_; michael@0: michael@0: /* michael@0: * During parsing, bindings are allocated out of a temporary LifoAlloc. michael@0: * After parsing, a JSScript object is created and the bindings are michael@0: * permanently transferred to it. On error paths, the JSScript object may michael@0: * end up with bindings that still point to the (new released) LifoAlloc michael@0: * memory. To avoid tracing these bindings during GC, we keep track of michael@0: * whether the bindings are temporary or permanent in the low bit of michael@0: * bindingArrayAndFlag_. michael@0: */ michael@0: static const uintptr_t TEMPORARY_STORAGE_BIT = 0x1; michael@0: bool bindingArrayUsingTemporaryStorage() const { michael@0: return bindingArrayAndFlag_ & TEMPORARY_STORAGE_BIT; michael@0: } michael@0: michael@0: public: michael@0: michael@0: Binding *bindingArray() const { michael@0: return reinterpret_cast(bindingArrayAndFlag_ & ~TEMPORARY_STORAGE_BIT); michael@0: } michael@0: michael@0: inline Bindings(); michael@0: michael@0: /* michael@0: * Initialize a Bindings with a pointer into temporary storage. michael@0: * bindingArray must have length numArgs+numVars. Before the temporary michael@0: * storage is release, switchToScriptStorage must be called, providing a michael@0: * pointer into the Binding array stored in script->data. michael@0: */ michael@0: static bool initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle self, michael@0: unsigned numArgs, uint32_t numVars, michael@0: Binding *bindingArray, unsigned numBlockScoped); michael@0: michael@0: // CompileScript parses and compiles one statement at a time, but the result michael@0: // is one Script object. There will be no vars or bindings, because those michael@0: // go on the global, but there may be block-scoped locals, and the number of michael@0: // block-scoped locals may increase as we parse more expressions. This michael@0: // helper updates the number of block scoped variables in a script as it is michael@0: // being parsed. michael@0: void updateNumBlockScoped(unsigned numBlockScoped) { michael@0: JS_ASSERT(!callObjShape_); michael@0: JS_ASSERT(numVars_ == 0); michael@0: JS_ASSERT(numBlockScoped < LOCALNO_LIMIT); michael@0: JS_ASSERT(numBlockScoped >= numBlockScoped_); michael@0: numBlockScoped_ = numBlockScoped; michael@0: } michael@0: michael@0: uint8_t *switchToScriptStorage(Binding *newStorage); michael@0: michael@0: /* michael@0: * Clone srcScript's bindings (as part of js::CloneScript). dstScriptData michael@0: * is the pointer to what will eventually be dstScript->data. michael@0: */ michael@0: static bool clone(JSContext *cx, InternalBindingsHandle self, uint8_t *dstScriptData, michael@0: HandleScript srcScript); michael@0: michael@0: unsigned numArgs() const { return numArgs_; } michael@0: uint32_t numVars() const { return numVars_; } michael@0: unsigned numBlockScoped() const { return numBlockScoped_; } michael@0: uint32_t numLocals() const { return numVars() + numBlockScoped(); } michael@0: michael@0: // Return the size of the bindingArray. michael@0: uint32_t count() const { return numArgs() + numVars(); } michael@0: michael@0: /* Return the initial shape of call objects created for this scope. */ michael@0: Shape *callObjShape() const { return callObjShape_; } michael@0: michael@0: /* Convenience method to get the var index of 'arguments'. */ michael@0: static uint32_t argumentsVarIndex(ExclusiveContext *cx, InternalBindingsHandle); michael@0: michael@0: /* Return whether the binding at bindingIndex is aliased. */ michael@0: bool bindingIsAliased(uint32_t bindingIndex); michael@0: michael@0: /* Return whether this scope has any aliased bindings. */ michael@0: bool hasAnyAliasedBindings() const { michael@0: if (!callObjShape_) michael@0: return false; michael@0: michael@0: return !callObjShape_->isEmptyShape(); michael@0: } michael@0: michael@0: void trace(JSTracer *trc); michael@0: }; michael@0: michael@0: template <> michael@0: struct GCMethods { michael@0: static Bindings initial(); michael@0: static ThingRootKind kind() { return THING_ROOT_BINDINGS; } michael@0: static bool poisoned(const Bindings &bindings) { michael@0: return IsPoisonedPtr(static_cast(bindings.callObjShape())); michael@0: } michael@0: }; michael@0: michael@0: class ScriptCounts michael@0: { michael@0: friend class ::JSScript; michael@0: friend struct ScriptAndCounts; michael@0: michael@0: /* michael@0: * This points to a single block that holds an array of PCCounts followed michael@0: * by an array of doubles. Each element in the PCCounts array has a michael@0: * pointer into the array of doubles. michael@0: */ michael@0: PCCounts *pcCountsVector; michael@0: michael@0: /* Information about any Ion compilations for the script. */ michael@0: jit::IonScriptCounts *ionCounts; michael@0: michael@0: public: michael@0: ScriptCounts() : pcCountsVector(nullptr), ionCounts(nullptr) { } michael@0: michael@0: inline void destroy(FreeOp *fop); michael@0: michael@0: void set(js::ScriptCounts counts) { michael@0: pcCountsVector = counts.pcCountsVector; michael@0: ionCounts = counts.ionCounts; michael@0: } michael@0: }; michael@0: michael@0: typedef HashMap, michael@0: SystemAllocPolicy> ScriptCountsMap; michael@0: michael@0: class DebugScript michael@0: { michael@0: friend class ::JSScript; michael@0: michael@0: /* michael@0: * When non-zero, compile script in single-step mode. The top bit is set and michael@0: * cleared by setStepMode, as used by JSD. The lower bits are a count, michael@0: * adjusted by changeStepModeCount, used by the Debugger object. Only michael@0: * when the bit is clear and the count is zero may we compile the script michael@0: * without single-step support. michael@0: */ michael@0: uint32_t stepMode; michael@0: michael@0: /* Number of breakpoint sites at opcodes in the script. */ michael@0: uint32_t numSites; michael@0: michael@0: /* michael@0: * Array with all breakpoints installed at opcodes in the script, indexed michael@0: * by the offset of the opcode into the script. michael@0: */ michael@0: BreakpointSite *breakpoints[1]; michael@0: }; michael@0: michael@0: typedef HashMap, michael@0: SystemAllocPolicy> DebugScriptMap; michael@0: michael@0: class ScriptSource; michael@0: michael@0: class SourceDataCache michael@0: { michael@0: typedef HashMap, michael@0: SystemAllocPolicy> Map; michael@0: michael@0: public: michael@0: // Hold an entry in the source data cache and prevent it from being purged on GC. michael@0: class AutoHoldEntry michael@0: { michael@0: SourceDataCache *cache_; michael@0: ScriptSource *source_; michael@0: const jschar *charsToFree_; michael@0: public: michael@0: explicit AutoHoldEntry(); michael@0: ~AutoHoldEntry(); michael@0: private: michael@0: void holdEntry(SourceDataCache *cache, ScriptSource *source); michael@0: void deferDelete(const jschar *chars); michael@0: ScriptSource *source() const { return source_; } michael@0: friend class SourceDataCache; michael@0: }; michael@0: michael@0: private: michael@0: Map *map_; michael@0: AutoHoldEntry *holder_; michael@0: michael@0: public: michael@0: SourceDataCache() : map_(nullptr), holder_(nullptr) {} michael@0: michael@0: const jschar *lookup(ScriptSource *ss, AutoHoldEntry &asp); michael@0: bool put(ScriptSource *ss, const jschar *chars, AutoHoldEntry &asp); michael@0: michael@0: void purge(); michael@0: michael@0: size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf); michael@0: michael@0: private: michael@0: void holdEntry(AutoHoldEntry &holder, ScriptSource *ss); michael@0: void releaseEntry(AutoHoldEntry &holder); michael@0: }; michael@0: michael@0: class ScriptSource michael@0: { michael@0: friend class SourceCompressionTask; michael@0: michael@0: // A note on concurrency: michael@0: // michael@0: // The source may be compressed by a worker thread during parsing. (See michael@0: // SourceCompressionTask.) When compression is running in the background, michael@0: // ready() returns false. The compression thread touches the |data| union michael@0: // and |compressedLength_|. Therefore, it is not safe to read these members michael@0: // unless ready() is true. With that said, users of the public ScriptSource michael@0: // API should be fine. michael@0: michael@0: union { michael@0: // Before setSourceCopy or setSource are successfully called, this union michael@0: // has a nullptr pointer. When the script source is ready, michael@0: // compressedLength_ != 0 implies compressed holds the compressed data; michael@0: // otherwise, source holds the uncompressed source. There is a special michael@0: // pointer |emptySource| for source code for length 0. michael@0: // michael@0: // The only function allowed to malloc, realloc, or free the pointers in michael@0: // this union is adjustDataSize(). Don't do it elsewhere. michael@0: jschar *source; michael@0: unsigned char *compressed; michael@0: } data; michael@0: uint32_t refs; michael@0: uint32_t length_; michael@0: uint32_t compressedLength_; michael@0: char *filename_; michael@0: jschar *displayURL_; michael@0: jschar *sourceMapURL_; michael@0: JSPrincipals *originPrincipals_; michael@0: michael@0: // bytecode offset in caller script that generated this code. michael@0: // This is present for eval-ed code, as well as "new Function(...)"-introduced michael@0: // scripts. michael@0: uint32_t introductionOffset_; michael@0: michael@0: // If this ScriptSource was generated by a code-introduction mechanism such as |eval| michael@0: // or |new Function|, the debugger needs access to the "raw" filename of the top-level michael@0: // script that contains the eval-ing code. To keep track of this, we must preserve michael@0: // the original outermost filename (of the original introducer script), so that instead michael@0: // of a filename of "foo.js line 30 > eval line 10 > Function", we can obtain the michael@0: // original raw filename of "foo.js". michael@0: char *introducerFilename_; michael@0: michael@0: // A string indicating how this source code was introduced into the system. michael@0: // This accessor returns one of the following values: michael@0: // "eval" for code passed to |eval|. michael@0: // "Function" for code passed to the |Function| constructor. michael@0: // "Worker" for code loaded by calling the Web worker constructor—the worker's main script. michael@0: // "importScripts" for code by calling |importScripts| in a web worker. michael@0: // "handler" for code assigned to DOM elements' event handler IDL attributes. michael@0: // "scriptElement" for code belonging to