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: #ifndef frontend_SharedContext_h michael@0: #define frontend_SharedContext_h michael@0: michael@0: #include "jsatom.h" michael@0: #include "jsopcode.h" michael@0: #include "jspubtd.h" michael@0: #include "jsscript.h" michael@0: #include "jstypes.h" michael@0: michael@0: #include "frontend/ParseMaps.h" michael@0: #include "frontend/ParseNode.h" michael@0: #include "frontend/TokenStream.h" michael@0: #include "vm/ScopeObject.h" michael@0: michael@0: namespace js { michael@0: namespace frontend { michael@0: michael@0: // These flags apply to both global and function contexts. michael@0: class AnyContextFlags michael@0: { michael@0: // This class's data is all private and so only visible to these friends. michael@0: friend class SharedContext; michael@0: michael@0: // True if "use strict"; appears in the body instead of being inherited. michael@0: bool hasExplicitUseStrict:1; michael@0: michael@0: // The (static) bindings of this script need to support dynamic name michael@0: // read/write access. Here, 'dynamic' means dynamic dictionary lookup on michael@0: // the scope chain for a dynamic set of keys. The primary examples are: michael@0: // - direct eval michael@0: // - function:: michael@0: // - with michael@0: // since both effectively allow any name to be accessed. Non-examples are: michael@0: // - upvars of nested functions michael@0: // - function statement michael@0: // since the set of assigned name is known dynamically. 'with' could be in michael@0: // the non-example category, provided the set of all free variables within michael@0: // the with block was noted. However, we do not optimize 'with' so, for michael@0: // simplicity, 'with' is treated like eval. michael@0: // michael@0: // Note: access through the arguments object is not considered dynamic michael@0: // binding access since it does not go through the normal name lookup michael@0: // mechanism. This is debatable and could be changed (although care must be michael@0: // taken not to turn off the whole 'arguments' optimization). To answer the michael@0: // more general "is this argument aliased" question, script->needsArgsObj michael@0: // should be tested (see JSScript::argIsAlised). michael@0: // michael@0: bool bindingsAccessedDynamically:1; michael@0: michael@0: // Whether this script, or any of its inner scripts contains a debugger michael@0: // statement which could potentially read or write anywhere along the michael@0: // scope chain. michael@0: bool hasDebuggerStatement:1; michael@0: michael@0: public: michael@0: AnyContextFlags() michael@0: : hasExplicitUseStrict(false), michael@0: bindingsAccessedDynamically(false), michael@0: hasDebuggerStatement(false) michael@0: { } michael@0: }; michael@0: michael@0: class FunctionContextFlags michael@0: { michael@0: // This class's data is all private and so only visible to these friends. michael@0: friend class FunctionBox; michael@0: michael@0: // The function or a function that encloses it may define new local names michael@0: // at runtime through means other than calling eval. michael@0: bool mightAliasLocals:1; michael@0: michael@0: // This function does something that can extend the set of bindings in its michael@0: // call objects --- it does a direct eval in non-strict code, or includes a michael@0: // function statement (as opposed to a function definition). michael@0: // michael@0: // This flag is *not* inherited by enclosed or enclosing functions; it michael@0: // applies only to the function in whose flags it appears. michael@0: // michael@0: bool hasExtensibleScope:1; michael@0: michael@0: // This function refers directly to its name in a way which requires the michael@0: // name to be a separate object on the scope chain. michael@0: bool needsDeclEnvObject:1; michael@0: michael@0: // Technically, every function has a binding named 'arguments'. Internally, michael@0: // this binding is only added when 'arguments' is mentioned by the function michael@0: // body. This flag indicates whether 'arguments' has been bound either michael@0: // through implicit use: michael@0: // function f() { return arguments } michael@0: // or explicit redeclaration: michael@0: // function f() { var arguments; return arguments } michael@0: // michael@0: // Note 1: overwritten arguments (function() { arguments = 3 }) will cause michael@0: // this flag to be set but otherwise require no special handling: michael@0: // 'arguments' is just a local variable and uses of 'arguments' will just michael@0: // read the local's current slot which may have been assigned. The only michael@0: // special semantics is that the initial value of 'arguments' is the michael@0: // arguments object (not undefined, like normal locals). michael@0: // michael@0: // Note 2: if 'arguments' is bound as a formal parameter, there will be an michael@0: // 'arguments' in Bindings, but, as the "LOCAL" in the name indicates, this michael@0: // flag will not be set. This is because, as a formal, 'arguments' will michael@0: // have no special semantics: the initial value is unconditionally the michael@0: // actual argument (or undefined if nactual < nformal). michael@0: // michael@0: bool argumentsHasLocalBinding:1; michael@0: michael@0: // In many cases where 'arguments' has a local binding (as described above) michael@0: // we do not need to actually create an arguments object in the function michael@0: // prologue: instead we can analyze how 'arguments' is used (using the michael@0: // simple dataflow analysis in analyzeSSA) to determine that uses of michael@0: // 'arguments' can just read from the stack frame directly. However, the michael@0: // dataflow analysis only looks at how JSOP_ARGUMENTS is used, so it will michael@0: // be unsound in several cases. The frontend filters out such cases by michael@0: // setting this flag which eagerly sets script->needsArgsObj to true. michael@0: // michael@0: bool definitelyNeedsArgsObj:1; michael@0: michael@0: public: michael@0: FunctionContextFlags() michael@0: : mightAliasLocals(false), michael@0: hasExtensibleScope(false), michael@0: needsDeclEnvObject(false), michael@0: argumentsHasLocalBinding(false), michael@0: definitelyNeedsArgsObj(false) michael@0: { } michael@0: }; michael@0: michael@0: class GlobalSharedContext; michael@0: michael@0: // List of directives that may be encountered in a Directive Prologue (ES5 15.1). michael@0: class Directives michael@0: { michael@0: bool strict_; michael@0: bool asmJS_; michael@0: michael@0: public: michael@0: explicit Directives(bool strict) : strict_(strict), asmJS_(false) {} michael@0: template explicit Directives(ParseContext *parent); michael@0: michael@0: void setStrict() { strict_ = true; } michael@0: bool strict() const { return strict_; } michael@0: michael@0: void setAsmJS() { asmJS_ = true; } michael@0: bool asmJS() const { return asmJS_; } michael@0: michael@0: Directives &operator=(Directives rhs) { michael@0: strict_ = rhs.strict_; michael@0: asmJS_ = rhs.asmJS_; michael@0: return *this; michael@0: } michael@0: bool operator==(const Directives &rhs) const { michael@0: return strict_ == rhs.strict_ && asmJS_ == rhs.asmJS_; michael@0: } michael@0: bool operator!=(const Directives &rhs) const { michael@0: return !(*this == rhs); michael@0: } michael@0: }; michael@0: michael@0: /* michael@0: * The struct SharedContext is part of the current parser context (see michael@0: * ParseContext). It stores information that is reused between the parser and michael@0: * the bytecode emitter. Note however, that this information is not shared michael@0: * between the two; they simply reuse the same data structure. michael@0: */ michael@0: class SharedContext michael@0: { michael@0: public: michael@0: ExclusiveContext *const context; michael@0: AnyContextFlags anyCxFlags; michael@0: bool strict; michael@0: bool extraWarnings; michael@0: michael@0: // If it's function code, funbox must be non-nullptr and scopeChain must be michael@0: // nullptr. If it's global code, funbox must be nullptr. michael@0: SharedContext(ExclusiveContext *cx, Directives directives, bool extraWarnings) michael@0: : context(cx), michael@0: anyCxFlags(), michael@0: strict(directives.strict()), michael@0: extraWarnings(extraWarnings) michael@0: {} michael@0: michael@0: virtual ObjectBox *toObjectBox() = 0; michael@0: inline bool isGlobalSharedContext() { return toObjectBox() == nullptr; } michael@0: inline bool isFunctionBox() { return toObjectBox() && toObjectBox()->isFunctionBox(); } michael@0: inline GlobalSharedContext *asGlobalSharedContext(); michael@0: inline FunctionBox *asFunctionBox(); michael@0: michael@0: bool hasExplicitUseStrict() const { return anyCxFlags.hasExplicitUseStrict; } michael@0: bool bindingsAccessedDynamically() const { return anyCxFlags.bindingsAccessedDynamically; } michael@0: bool hasDebuggerStatement() const { return anyCxFlags.hasDebuggerStatement; } michael@0: michael@0: void setExplicitUseStrict() { anyCxFlags.hasExplicitUseStrict = true; } michael@0: void setBindingsAccessedDynamically() { anyCxFlags.bindingsAccessedDynamically = true; } michael@0: void setHasDebuggerStatement() { anyCxFlags.hasDebuggerStatement = true; } michael@0: michael@0: inline bool allLocalsAliased(); michael@0: michael@0: // JSOPTION_EXTRA_WARNINGS warnings or strict mode errors. michael@0: bool needStrictChecks() { michael@0: return strict || extraWarnings; michael@0: } michael@0: }; michael@0: michael@0: class GlobalSharedContext : public SharedContext michael@0: { michael@0: private: michael@0: const RootedObject scopeChain_; /* scope chain object for the script */ michael@0: michael@0: public: michael@0: GlobalSharedContext(ExclusiveContext *cx, JSObject *scopeChain, michael@0: Directives directives, bool extraWarnings) michael@0: : SharedContext(cx, directives, extraWarnings), michael@0: scopeChain_(cx, scopeChain) michael@0: {} michael@0: michael@0: ObjectBox *toObjectBox() { return nullptr; } michael@0: JSObject *scopeChain() const { return scopeChain_; } michael@0: }; michael@0: michael@0: inline GlobalSharedContext * michael@0: SharedContext::asGlobalSharedContext() michael@0: { michael@0: JS_ASSERT(isGlobalSharedContext()); michael@0: return static_cast(this); michael@0: } michael@0: michael@0: class FunctionBox : public ObjectBox, public SharedContext michael@0: { michael@0: public: michael@0: Bindings bindings; /* bindings for this function */ michael@0: uint32_t bufStart; michael@0: uint32_t bufEnd; michael@0: uint32_t startLine; michael@0: uint32_t startColumn; michael@0: uint16_t length; michael@0: michael@0: uint8_t generatorKindBits_; /* The GeneratorKind of this function. */ michael@0: bool inWith:1; /* some enclosing scope is a with-statement */ michael@0: bool inGenexpLambda:1; /* lambda from generator expression */ michael@0: bool hasDestructuringArgs:1; /* arguments list contains destructuring expression */ michael@0: bool useAsm:1; /* function contains "use asm" directive */ michael@0: bool insideUseAsm:1; /* nested function of function of "use asm" directive */ michael@0: michael@0: // Fields for use in heuristics. michael@0: bool usesArguments:1; /* contains a free use of 'arguments' */ michael@0: bool usesApply:1; /* contains an f.apply() call */ michael@0: michael@0: FunctionContextFlags funCxFlags; michael@0: michael@0: template michael@0: FunctionBox(ExclusiveContext *cx, ObjectBox* traceListHead, JSFunction *fun, michael@0: ParseContext *pc, Directives directives, michael@0: bool extraWarnings, GeneratorKind generatorKind); michael@0: michael@0: ObjectBox *toObjectBox() { return this; } michael@0: JSFunction *function() const { return &object->as(); } michael@0: michael@0: GeneratorKind generatorKind() const { return GeneratorKindFromBits(generatorKindBits_); } michael@0: bool isGenerator() const { return generatorKind() != NotGenerator; } michael@0: bool isLegacyGenerator() const { return generatorKind() == LegacyGenerator; } michael@0: bool isStarGenerator() const { return generatorKind() == StarGenerator; } michael@0: michael@0: void setGeneratorKind(GeneratorKind kind) { michael@0: // A generator kind can be set at initialization, or when "yield" is michael@0: // first seen. In both cases the transition can only happen from michael@0: // NotGenerator. michael@0: JS_ASSERT(!isGenerator()); michael@0: generatorKindBits_ = GeneratorKindAsBits(kind); michael@0: } michael@0: michael@0: bool mightAliasLocals() const { return funCxFlags.mightAliasLocals; } michael@0: bool hasExtensibleScope() const { return funCxFlags.hasExtensibleScope; } michael@0: bool needsDeclEnvObject() const { return funCxFlags.needsDeclEnvObject; } michael@0: bool argumentsHasLocalBinding() const { return funCxFlags.argumentsHasLocalBinding; } michael@0: bool definitelyNeedsArgsObj() const { return funCxFlags.definitelyNeedsArgsObj; } michael@0: michael@0: void setMightAliasLocals() { funCxFlags.mightAliasLocals = true; } michael@0: void setHasExtensibleScope() { funCxFlags.hasExtensibleScope = true; } michael@0: void setNeedsDeclEnvObject() { funCxFlags.needsDeclEnvObject = true; } michael@0: void setArgumentsHasLocalBinding() { funCxFlags.argumentsHasLocalBinding = true; } michael@0: void setDefinitelyNeedsArgsObj() { JS_ASSERT(funCxFlags.argumentsHasLocalBinding); michael@0: funCxFlags.definitelyNeedsArgsObj = true; } michael@0: michael@0: bool hasDefaults() const { michael@0: return length != function()->nargs() - function()->hasRest(); michael@0: } michael@0: michael@0: // Return whether this function has either specified "use asm" or is michael@0: // (transitively) nested inside a function that has. michael@0: bool useAsmOrInsideUseAsm() const { michael@0: return useAsm || insideUseAsm; michael@0: } michael@0: michael@0: void setStart(const TokenStream &tokenStream) { michael@0: bufStart = tokenStream.currentToken().pos.begin; michael@0: startLine = tokenStream.getLineno(); michael@0: startColumn = tokenStream.getColumn(); michael@0: } michael@0: michael@0: bool isHeavyweight() michael@0: { michael@0: // Note: this should be kept in sync with JSFunction::isHeavyweight(). michael@0: return bindings.hasAnyAliasedBindings() || michael@0: hasExtensibleScope() || michael@0: needsDeclEnvObject() || michael@0: isGenerator(); michael@0: } michael@0: }; michael@0: michael@0: inline FunctionBox * michael@0: SharedContext::asFunctionBox() michael@0: { michael@0: JS_ASSERT(isFunctionBox()); michael@0: return static_cast(this); michael@0: } michael@0: michael@0: // In generators, we treat all locals as aliased so that they get stored on the michael@0: // heap. This way there is less information to copy off the stack when michael@0: // suspending, and back on when resuming. It also avoids the need to create and michael@0: // invalidate DebugScope proxies for unaliased locals in a generator frame, as michael@0: // the generator frame will be copied out to the heap and released only by GC. michael@0: inline bool michael@0: SharedContext::allLocalsAliased() michael@0: { michael@0: return bindingsAccessedDynamically() || (isFunctionBox() && asFunctionBox()->isGenerator()); michael@0: } michael@0: michael@0: michael@0: /* michael@0: * NB: If you add a new type of statement that is a scope, add it between michael@0: * STMT_WITH and STMT_CATCH, or you will break StmtInfoBase::linksScope. If you michael@0: * add a non-looping statement type, add it before STMT_DO_LOOP or you will michael@0: * break StmtInfoBase::isLoop(). michael@0: * michael@0: * Also remember to keep the statementName array in BytecodeEmitter.cpp in michael@0: * sync. michael@0: */ michael@0: enum StmtType { michael@0: STMT_LABEL, /* labeled statement: L: s */ michael@0: STMT_IF, /* if (then) statement */ michael@0: STMT_ELSE, /* else clause of if statement */ michael@0: STMT_SEQ, /* synthetic sequence of statements */ michael@0: STMT_BLOCK, /* compound statement: { s1[;... sN] } */ michael@0: STMT_SWITCH, /* switch statement */ michael@0: STMT_WITH, /* with statement */ michael@0: STMT_CATCH, /* catch block */ michael@0: STMT_TRY, /* try block */ michael@0: STMT_FINALLY, /* finally block */ michael@0: STMT_SUBROUTINE, /* gosub-target subroutine body */ michael@0: STMT_DO_LOOP, /* do/while loop statement */ michael@0: STMT_FOR_LOOP, /* for loop statement */ michael@0: STMT_FOR_IN_LOOP, /* for/in loop statement */ michael@0: STMT_FOR_OF_LOOP, /* for/of loop statement */ michael@0: STMT_WHILE_LOOP, /* while loop statement */ michael@0: STMT_LIMIT michael@0: }; michael@0: michael@0: /* michael@0: * A comment on the encoding of the js::StmtType enum and StmtInfoBase michael@0: * type-testing methods: michael@0: * michael@0: * StmtInfoBase::maybeScope() tells whether a statement type is always, or may michael@0: * become, a lexical scope. It therefore includes block and switch (the two michael@0: * low-numbered "maybe" scope types) and excludes with (with has dynamic scope michael@0: * pending the "reformed with" in ES4/JS2). It includes all try-catch-finally michael@0: * types, which are high-numbered maybe-scope types. michael@0: * michael@0: * StmtInfoBase::linksScope() tells whether a js::StmtInfo{PC,BCE} of the given michael@0: * type eagerly links to other scoping statement info records. It excludes the michael@0: * two early "maybe" types, block and switch, as well as the try and both michael@0: * finally types, since try and the other trailing maybe-scope types don't need michael@0: * block scope unless they contain let declarations. michael@0: * michael@0: * We treat WITH as a static scope because it prevents lexical binding from michael@0: * continuing further up the static scope chain. With the lost "reformed with" michael@0: * proposal for ES4, we would be able to model it statically, too. michael@0: */ michael@0: michael@0: // StmtInfoPC is used by the Parser. StmtInfoBCE is used by the michael@0: // BytecodeEmitter. The two types have some overlap, encapsulated by michael@0: // StmtInfoBase. Several functions below (e.g. PushStatement) are templated to michael@0: // work with both types. michael@0: michael@0: struct StmtInfoBase { michael@0: // Statement type (StmtType). michael@0: uint16_t type; michael@0: michael@0: // True if type is STMT_BLOCK, STMT_TRY, STMT_SWITCH, or STMT_FINALLY and michael@0: // the block contains at least one let-declaration, or if type is michael@0: // STMT_CATCH. michael@0: bool isBlockScope:1; michael@0: michael@0: // True if isBlockScope or type == STMT_WITH. michael@0: bool isNestedScope:1; michael@0: michael@0: // for (let ...) induced block scope michael@0: bool isForLetBlock:1; michael@0: michael@0: // Block label. michael@0: RootedAtom label; michael@0: michael@0: // Compile-time scope chain node for this scope. Only set if michael@0: // isNestedScope. michael@0: Rooted staticScope; michael@0: michael@0: StmtInfoBase(ExclusiveContext *cx) michael@0: : isBlockScope(false), isNestedScope(false), isForLetBlock(false), michael@0: label(cx), staticScope(cx) michael@0: {} michael@0: michael@0: bool maybeScope() const { michael@0: return STMT_BLOCK <= type && type <= STMT_SUBROUTINE && type != STMT_WITH; michael@0: } michael@0: michael@0: bool linksScope() const { michael@0: return isNestedScope; michael@0: } michael@0: michael@0: StaticBlockObject& staticBlock() const { michael@0: JS_ASSERT(isNestedScope); michael@0: JS_ASSERT(isBlockScope); michael@0: return staticScope->as(); michael@0: } michael@0: michael@0: bool isLoop() const { michael@0: return type >= STMT_DO_LOOP; michael@0: } michael@0: michael@0: bool isTrying() const { michael@0: return STMT_TRY <= type && type <= STMT_SUBROUTINE; michael@0: } michael@0: }; michael@0: michael@0: // Push the C-stack-allocated struct at stmt onto the StmtInfoPC stack. michael@0: template michael@0: void michael@0: PushStatement(ContextT *ct, typename ContextT::StmtInfo *stmt, StmtType type) michael@0: { michael@0: stmt->type = type; michael@0: stmt->isBlockScope = false; michael@0: stmt->isNestedScope = false; michael@0: stmt->isForLetBlock = false; michael@0: stmt->label = nullptr; michael@0: stmt->staticScope = nullptr; michael@0: stmt->down = ct->topStmt; michael@0: ct->topStmt = stmt; michael@0: if (stmt->linksScope()) { michael@0: stmt->downScope = ct->topScopeStmt; michael@0: ct->topScopeStmt = stmt; michael@0: } else { michael@0: stmt->downScope = nullptr; michael@0: } michael@0: } michael@0: michael@0: template michael@0: void michael@0: FinishPushNestedScope(ContextT *ct, typename ContextT::StmtInfo *stmt, NestedScopeObject &staticScope) michael@0: { michael@0: stmt->isNestedScope = true; michael@0: stmt->downScope = ct->topScopeStmt; michael@0: ct->topScopeStmt = stmt; michael@0: ct->staticScope = &staticScope; michael@0: stmt->staticScope = &staticScope; michael@0: } michael@0: michael@0: // Pop pc->topStmt. If the top StmtInfoPC struct is not stack-allocated, it michael@0: // is up to the caller to free it. The dummy argument is just to make the michael@0: // template matching work. michael@0: template michael@0: void michael@0: FinishPopStatement(ContextT *ct) michael@0: { michael@0: typename ContextT::StmtInfo *stmt = ct->topStmt; michael@0: ct->topStmt = stmt->down; michael@0: if (stmt->linksScope()) { michael@0: ct->topScopeStmt = stmt->downScope; michael@0: if (stmt->isNestedScope) { michael@0: JS_ASSERT(stmt->staticScope); michael@0: ct->staticScope = stmt->staticScope->enclosingNestedScope(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Find a lexically scoped variable (one declared by let, catch, or an array michael@0: * comprehension) named by atom, looking in sc's compile-time scopes. michael@0: * michael@0: * If a WITH statement is reached along the scope stack, return its statement michael@0: * info record, so callers can tell that atom is ambiguous. If slotp is not michael@0: * null, then if atom is found, set *slotp to its stack slot, otherwise to -1. michael@0: * This means that if slotp is not null, all the block objects on the lexical michael@0: * scope chain must have had their depth slots computed by the code generator, michael@0: * so the caller must be under EmitTree. michael@0: * michael@0: * In any event, directly return the statement info record in which atom was michael@0: * found. Otherwise return null. michael@0: */ michael@0: template michael@0: typename ContextT::StmtInfo * michael@0: LexicalLookup(ContextT *ct, HandleAtom atom, int *slotp, typename ContextT::StmtInfo *stmt); michael@0: michael@0: } // namespace frontend michael@0: michael@0: } // namespace js michael@0: michael@0: #endif /* frontend_SharedContext_h */