Bug 730497 - rm flat closures (r=bhackett,waldo)

--HG--
extra : rebase_source : cf704765ad227abced9e8804aaeb1dc8d12fc5a8
This commit is contained in:
Luke Wagner 2012-02-27 23:49:02 -08:00
parent d5c7764bc8
commit 1843d7c72e
32 changed files with 292 additions and 1094 deletions

View File

@ -110,8 +110,6 @@ BytecodeEmitter::BytecodeEmitter(Parser *parser, unsigned lineno)
emitLevel(0),
constMap(parser->context),
constList(parser->context),
upvarIndices(parser->context),
upvarMap(parser->context),
globalScope(NULL),
globalUses(parser->context),
globalMap(parser->context),
@ -1367,79 +1365,8 @@ BindNameToSlot(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
JS_ASSERT(bce->staticLevel >= level);
const unsigned skip = bce->staticLevel - level;
if (skip != 0) {
JS_ASSERT(bce->inFunction());
JS_ASSERT_IF(cookie.slot() != UpvarCookie::CALLEE_SLOT, bce->roLexdeps->lookup(atom));
JS_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
/*
* If op is a mutating opcode, this upvar's lookup skips too many levels,
* or the function is heavyweight, we fall back on JSOP_*NAME*.
*/
if (op != JSOP_NAME)
return JS_TRUE;
if (skip >= UpvarCookie::UPVAR_LEVEL_LIMIT)
return JS_TRUE;
if (bce->flags & TCF_FUN_HEAVYWEIGHT)
return JS_TRUE;
if (!bce->fun()->isFlatClosure())
return JS_TRUE;
if (!bce->upvarIndices.ensureMap(cx))
return JS_FALSE;
AtomIndexAddPtr p = bce->upvarIndices->lookupForAdd(atom);
jsatomid index;
if (p) {
index = p.value();
} else {
if (!bce->bindings.addUpvar(cx, atom))
return JS_FALSE;
index = bce->upvarIndices->count();
if (!bce->upvarIndices->add(p, atom, index))
return JS_FALSE;
UpvarCookies &upvarMap = bce->upvarMap;
/* upvarMap should have the same number of UpvarCookies as there are lexdeps. */
size_t lexdepCount = bce->roLexdeps->count();
JS_ASSERT_IF(!upvarMap.empty(), lexdepCount == upvarMap.length());
if (upvarMap.empty()) {
/* Lazily initialize the upvar map with exactly the necessary capacity. */
if (lexdepCount <= upvarMap.sMaxInlineStorage) {
JS_ALWAYS_TRUE(upvarMap.growByUninitialized(lexdepCount));
} else {
void *buf = upvarMap.allocPolicy().malloc_(lexdepCount * sizeof(UpvarCookie));
if (!buf)
return JS_FALSE;
upvarMap.replaceRawBuffer(static_cast<UpvarCookie *>(buf), lexdepCount);
}
for (size_t i = 0; i < lexdepCount; ++i)
upvarMap[i] = UpvarCookie();
}
unsigned slot = cookie.slot();
if (slot != UpvarCookie::CALLEE_SLOT && dn_kind != Definition::ARG) {
TreeContext *tc = bce;
do {
tc = tc->parent;
} while (tc->staticLevel != level);
if (tc->inFunction())
slot += tc->fun()->nargs;
}
JS_ASSERT(index < upvarMap.length());
upvarMap[index].set(skip, slot);
}
pn->setOp(JSOP_GETFCSLOT);
JS_ASSERT((index & JS_BITMASK(16)) == index);
pn->pn_cookie.set(0, index);
pn->pn_dflags |= PND_BOUND;
if (skip != 0)
return JS_TRUE;
}
/*
* We are compiling a function body and may be able to optimize name
@ -1834,9 +1761,6 @@ EmitNameOp(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, JSBool callContex
case JSOP_GETLOCAL:
op = JSOP_CALLLOCAL;
break;
case JSOP_GETFCSLOT:
op = JSOP_CALLFCSLOT;
break;
default:
JS_ASSERT(op == JSOP_ARGUMENTS || op == JSOP_CALLEE);
break;
@ -4048,7 +3972,7 @@ EmitCatch(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
case PNK_NAME:
/* Inline and specialize BindNameToSlot for pn2. */
JS_ASSERT(!pn2->pn_cookie.isFree());
EMIT_UINT16_IMM_OP(JSOP_SETLOCALPOP, pn2->pn_cookie.asInteger());
EMIT_UINT16_IMM_OP(JSOP_SETLOCALPOP, pn2->pn_cookie.slot());
break;
default:
@ -5122,8 +5046,6 @@ EmitFunc(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
return false;
if (pn->pn_cookie.isFree()) {
bce->switchToProlog();
MOZ_ASSERT(!fun->isFlatClosure(),
"global functions can't have upvars, so they are never flat");
if (!EmitFunctionOp(cx, JSOP_DEFFUN, index, bce))
return false;
if (!UpdateLineNumberNotes(cx, bce, pn->pn_pos.begin.lineno))
@ -5140,14 +5062,13 @@ EmitFunc(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
JS_ASSERT(kind == VARIABLE || kind == CONSTANT);
JS_ASSERT(index < JS_BIT(20));
pn->pn_index = index;
JSOp op = fun->isFlatClosure() ? JSOP_DEFLOCALFUN_FC : JSOP_DEFLOCALFUN;
if (pn->isClosed() &&
!bce->callsEval() &&
!bce->closedVars.append(pn->pn_cookie.slot()))
{
return false;
}
return EmitSlotObjectOp(cx, op, slot, index, bce);
return EmitSlotObjectOp(cx, JSOP_DEFLOCALFUN, slot, index, bce);
}
return true;
@ -5794,8 +5715,7 @@ EmitIncOrDec(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
if (Emit1(cx, bce, op) < 0)
return false;
} else if (!pn2->pn_cookie.isFree()) {
jsatomid atomIndex = pn2->pn_cookie.asInteger();
EMIT_UINT16_IMM_OP(op, atomIndex);
EMIT_UINT16_IMM_OP(op, pn2->pn_cookie.slot());
} else {
JS_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
if (js_CodeSpec[op].format & (JOF_INC | JOF_DEC)) {

View File

@ -589,10 +589,6 @@ struct BytecodeEmitter : public TreeContext
CGObjectList regexpList; /* list of emitted regexp that will be
cloned during execution */
OwnedAtomIndexMapPtr upvarIndices; /* map of atoms to upvar indexes */
UpvarCookies upvarMap; /* indexed upvar slot locations */
GlobalScope *globalScope; /* frontend::CompileScript global scope, or null */
typedef Vector<GlobalSlotArray::Entry, 16> GlobalUseVector;
@ -638,10 +634,6 @@ struct BytecodeEmitter : public TreeContext
*/
bool addGlobalUse(JSAtom *atom, uint32_t slot, UpvarCookie *cookie);
bool hasUpvarIndices() const {
return upvarIndices.hasMap() && !upvarIndices->empty();
}
bool compilingForEval() const { return !!(flags & TCF_COMPILE_FOR_EVAL); }
JSVersion version() const { return parser->versionWithFlags(); }

View File

@ -50,6 +50,50 @@
namespace js {
/*
* Indicates a location in the stack that an upvar value can be retrieved from
* as a two tuple of (level, slot).
*
* Some existing client code uses the level value as a delta, or level "skip"
* quantity. We could probably document that through use of more types at some
* point in the future.
*/
class UpvarCookie
{
uint32_t value;
static const uint32_t FREE_VALUE = 0xfffffffful;
void checkInvariants() {
JS_STATIC_ASSERT(sizeof(UpvarCookie) == sizeof(uint32_t));
JS_STATIC_ASSERT(UPVAR_LEVEL_LIMIT < FREE_LEVEL);
}
public:
/*
* All levels above-and-including FREE_LEVEL are reserved so that
* FREE_VALUE can be used as a special value.
*/
static const uint16_t FREE_LEVEL = 0x3fff;
/*
* If a function has a higher static level than this limit, we will not
* optimize it using UPVAR opcodes.
*/
static const uint16_t UPVAR_LEVEL_LIMIT = 16;
static const uint16_t CALLEE_SLOT = 0xffff;
static bool isLevelReserved(uint16_t level) { return level >= FREE_LEVEL; }
bool isFree() const { return value == FREE_VALUE; }
/* isFree check should be performed before using these accessors. */
uint16_t level() const { JS_ASSERT(!isFree()); return uint16_t(value >> 16); }
uint16_t slot() const { JS_ASSERT(!isFree()); return uint16_t(value); }
void set(const UpvarCookie &other) { set(other.level(), other.slot()); }
void set(uint16_t newLevel, uint16_t newSlot) { value = (uint32_t(newLevel) << 16) | newSlot; }
void makeFree() { set(0xffff, 0xffff); JS_ASSERT(isFree()); }
};
/*
* Parsing builds a tree of nodes that directs code generation. This tree is
* not a concrete syntax tree in all respects (for example, || and && are left

View File

@ -331,161 +331,6 @@ MarkFunArgs(JSContext *cx, FunctionBox *funbox, uint32_t functionCount)
return true;
}
static uint32_t
MinBlockId(ParseNode *fn, uint32_t id)
{
if (fn->pn_blockid < id)
return false;
if (fn->isDefn()) {
for (ParseNode *pn = fn->dn_uses; pn; pn = pn->pn_link) {
if (pn->pn_blockid < id)
return false;
}
}
return true;
}
static inline bool
CanFlattenUpvar(Definition *dn, FunctionBox *funbox, uint32_t tcflags)
{
/*
* Consider the current function (the lambda, innermost below) using a var
* x defined two static levels up:
*
* function f() {
* // z = g();
* var x = 42;
* function g() {
* return function () { return x; };
* }
* return g();
* }
*
* So long as (1) the initialization in 'var x = 42' dominates all uses of
* g and (2) x is not reassigned, it is safe to optimize the lambda to a
* flat closure. Uncommenting the early call to g makes this optimization
* unsafe (z could name a global setter that calls its argument).
*/
FunctionBox *afunbox = funbox;
unsigned dnLevel = dn->frameLevel();
JS_ASSERT(dnLevel <= funbox->level);
while (afunbox->level != dnLevel) {
afunbox = afunbox->parent;
/*
* NB: afunbox can't be null because we are sure to find a function box
* whose level == dnLevel before we would try to walk above the root of
* the funbox tree. See bug 493260 comments 16-18.
*
* Assert but check anyway, to protect future changes that bind eval
* upvars in the parser.
*/
JS_ASSERT(afunbox);
/*
* If this function is reaching up across an enclosing funarg, then we
* cannot copy dn's value into a flat closure slot. The flat closure
* code assumes the upvars to be copied are in frames still on the
* stack.
*/
if (!afunbox || afunbox->node->isFunArg())
return false;
/*
* Reaching up for dn across a generator also means we can't flatten,
* since the generator iterator does not run until later, in general.
* See bug 563034.
*/
if (afunbox->tcflags & TCF_FUN_IS_GENERATOR)
return false;
}
/*
* If afunbox's function (which is at the same level as dn) is in a loop,
* pessimistically assume the variable initializer may be in the same loop.
* A flat closure would then be unsafe, as the captured variable could be
* assigned after the closure is created. See bug 493232.
*/
if (afunbox->inLoop)
return false;
/*
* |with| and eval used as an operator defeat lexical scoping: they can be
* used to assign to any in-scope variable. Therefore they must disable
* flat closures that use such upvars. The parser detects these as special
* forms and marks the function heavyweight.
*/
if ((afunbox->parent ? afunbox->parent->tcflags : tcflags) & TCF_FUN_HEAVYWEIGHT)
return false;
/*
* If afunbox's function is not a lambda, it will be hoisted, so it could
* capture the undefined value that by default initializes var, let, and
* const bindings. And if dn is a function that comes at (meaning a
* function refers to its own name) or strictly after afunbox, we also
* defeat the flat closure optimization for this dn.
*/
JSFunction *afun = afunbox->function();
if (!(afun->flags & JSFUN_LAMBDA)) {
if (dn->isBindingForm() || dn->pn_pos >= afunbox->node->pn_pos)
return false;
}
if (!dn->isInitialized())
return false;
Definition::Kind dnKind = dn->kind();
if (dnKind != Definition::CONST) {
if (dn->isAssigned())
return false;
/*
* Any formal could be mutated behind our back via the arguments
* object, so deoptimize if the outer function uses arguments.
*
* In a Function constructor call where the final argument -- the body
* source for the function to create -- contains a nested function
* definition or expression, afunbox->parent will be null. The body
* source might use |arguments| outside of any nested functions it may
* contain, so we have to check the tcflags parameter that was passed
* in from js::frontend::CompileFunctionBody.
*/
if (dnKind == Definition::ARG &&
((afunbox->parent ? afunbox->parent->tcflags : tcflags) & TCF_FUN_USES_ARGUMENTS)) {
return false;
}
}
/*
* Check quick-and-dirty dominance relation. Function definitions dominate
* their uses thanks to hoisting. Other binding forms hoist as undefined,
* of course, so check forward-reference and blockid relations.
*/
if (dnKind != Definition::FUNCTION) {
/*
* Watch out for code such as
*
* (function () {
* ...
* var jQuery = ... = function (...) {
* return new jQuery.foo.bar(baz);
* }
* ...
* })();
*
* where the jQuery variable is not reassigned, but of course is not
* initialized at the time that the would-be-flat closure containing
* the jQuery upvar is formed.
*/
if (dn->pn_pos.end >= afunbox->node->pn_pos.end)
return false;
if (!MinBlockId(afunbox->node, dn->pn_blockid))
return false;
}
return true;
}
static void
FlagHeavyweights(Definition *dn, FunctionBox *funbox, uint32_t *tcflags)
{
@ -539,70 +384,31 @@ SetFunctionKinds(FunctionBox *funbox, uint32_t *tcflags, bool isDirectEval)
JS_ASSERT(!fun->isNullClosure());
} else {
bool hasUpvars = false;
bool canFlatten = true;
if (pn->isKind(PNK_UPVARS)) {
AtomDefnMapPtr upvars = pn->pn_names;
JS_ASSERT(!upvars->empty());
/*
* For each lexical dependency from this closure to an outer
* binding, analyze whether it is safe to copy the binding's
* value into a flat closure slot when the closure is formed.
*/
/* Determine whether the this function contains upvars. */
for (AtomDefnRange r = upvars->all(); !r.empty(); r.popFront()) {
Definition *defn = r.front().value();
Definition *lexdep = defn->resolve();
if (!lexdep->isFreeVar()) {
if (!r.front().value()->resolve()->isFreeVar()) {
hasUpvars = true;
if (!CanFlattenUpvar(lexdep, funbox, *tcflags)) {
/*
* Can't flatten. Enclosing functions holding
* variables used by this function will be flagged
* heavyweight below. FIXME bug 545759: re-enable
* partial flat closures.
*/
canFlatten = false;
break;
}
break;
}
}
}
/*
* Top-level functions, and (extension) functions not at top level
* which are also not directly within other functions, aren't
* flattened.
*/
if (fn->isOp(JSOP_DEFFUN))
canFlatten = false;
if (!hasUpvars) {
/* No lexical dependencies => null closure, for best performance. */
fun->setKind(JSFUN_NULL_CLOSURE);
} else if (canFlatten) {
fun->setKind(JSFUN_FLAT_CLOSURE);
switch (fn->getOp()) {
case JSOP_DEFLOCALFUN:
fn->setOp(JSOP_DEFLOCALFUN_FC);
break;
case JSOP_LAMBDA:
fn->setOp(JSOP_LAMBDA_FC);
break;
default:
/* js::frontend::EmitTree's PNK_FUNCTION case sets op. */
JS_ASSERT(fn->isOp(JSOP_NOP));
}
}
}
if (fun->kind() == JSFUN_INTERPRETED && pn->isKind(PNK_UPVARS)) {
/*
* One or more upvars cannot be safely snapshot into a flat
* closure's non-reserved slot (see JSOP_GETFCSLOT), so we loop
* again over all upvars, and for each non-free upvar, ensure that
* its containing function has been flagged as heavyweight.
* We loop again over all upvars, and for each non-free upvar,
* ensure that its containing function has been flagged as
* heavyweight.
*
* The emitter must see TCF_FUN_HEAVYWEIGHT accurately before
* generating any code for a tree of nested functions.

View File

@ -0,0 +1,50 @@
// optimized
(function(b) {
assertEq("abc".replace(/a|b/g, function(a) { return b[a] }), 'ABc');
})({a:'A', b:'B' });
(function() {
var b = {a:'A', b:'B' };
assertEq("abc".replace(/a|b/g, function(a) { return b[a] }), 'ABc');
})();
(function() {
let (b = {a:'A', b:'B' }) {
assertEq("abc".replace(/a|b/g, function(a) { return b[a] }), 'ABc');
}
})();
(function() {
var b = {a:'A', b:'B' };
(function () {
assertEq("abc".replace(/a|b/g, function(a) { return b[a] }), 'ABc');
})();
})();
(function() {
let (b = {a:'A', b:'B' }) {
(function () {
assertEq("abc".replace(/a|b/g, function(a) { return b[a] }), 'ABc');
})();
}
})();
(function() {
var b = {a:'A', b:'B' };
(function () {
(function () {
assertEq("abc".replace(/a|b/g, function(a) { return b[a] }), 'ABc');
})();
})();
})();
// not optimized:
(function() {
var b = {a:'A', b:'B' };
with ({}) {
(function () {
assertEq("abc".replace(/a|b/g, function(a) { return b[a] }), 'ABc');
})();
}
})();
(function() {
var b = {a:'A', b:'B' };
var bad = function() { b = {a:1, b:2}; return 'X' }
Object.defineProperty(b, 'x', {get:bad});
assertEq("xabc".replace(/x|a|b/g, function(a) { return b[a] }), 'X12c');
})();

View File

@ -510,11 +510,7 @@ ScriptAnalysis::analyzeBytecode(JSContext *cx)
case JSOP_THROW:
case JSOP_EXCEPTION:
case JSOP_DEFLOCALFUN:
case JSOP_DEFLOCALFUN_FC:
case JSOP_LAMBDA:
case JSOP_LAMBDA_FC:
case JSOP_GETFCSLOT:
case JSOP_CALLFCSLOT:
case JSOP_DEBUGGER:
case JSOP_FUNCALL:
case JSOP_FUNAPPLY:
@ -790,8 +786,7 @@ ScriptAnalysis::analyzeLifetimes(JSContext *cx)
case JSOP_SETARG:
case JSOP_SETLOCAL:
case JSOP_SETLOCALPOP:
case JSOP_DEFLOCALFUN:
case JSOP_DEFLOCALFUN_FC: {
case JSOP_DEFLOCALFUN: {
uint32_t slot = GetBytecodeSlot(script, pc);
if (!slotEscapes(slot))
killVariable(cx, lifetimes[slot], offset, saved, savedCount);

View File

@ -255,7 +255,6 @@ ExtendedDef(jsbytecode *pc)
case JSOP_SETLOCAL:
case JSOP_SETLOCALPOP:
case JSOP_DEFLOCALFUN:
case JSOP_DEFLOCALFUN_FC:
case JSOP_INCLOCAL:
case JSOP_DECLOCAL:
case JSOP_LOCALINC:
@ -385,7 +384,6 @@ static inline uint32_t GetBytecodeSlot(JSScript *script, jsbytecode *pc)
case JSOP_SETLOCAL:
case JSOP_SETLOCALPOP:
case JSOP_DEFLOCALFUN:
case JSOP_DEFLOCALFUN_FC:
case JSOP_INCLOCAL:
case JSOP_DECLOCAL:
case JSOP_LOCALINC:
@ -410,7 +408,6 @@ BytecodeUpdatesSlot(JSOp op)
case JSOP_SETLOCAL:
case JSOP_SETLOCALPOP:
case JSOP_DEFLOCALFUN:
case JSOP_DEFLOCALFUN_FC:
case JSOP_INCARG:
case JSOP_DECARG:
case JSOP_ARGINC:

View File

@ -4557,57 +4557,14 @@ JS_CloneFunctionObject(JSContext *cx, JSObject *funobj, JSObject *parent)
}
JSFunction *fun = funobj->toFunction();
if (!fun->isInterpreted())
return CloneFunctionObject(cx, fun, parent, fun->getAllocKind());
if (fun->script()->compileAndGo) {
if (fun->isInterpreted() && fun->script()->compileAndGo) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
JSMSG_BAD_CLONE_FUNOBJ_SCOPE);
return NULL;
}
if (!fun->isFlatClosure())
return CloneFunctionObject(cx, fun, parent, fun->getAllocKind());
/*
* A flat closure carries its own environment, so why clone it? In case
* someone wants to mutate its fixed slots or add ad-hoc properties. API
* compatibility suggests we not return funobj and let callers mutate the
* returned object at will.
*
* But it's worse than that: API compatibility according to the test for
* bug 300079 requires we get "upvars" from parent and its ancestors! So
* we do that (grudgingly!). The scope chain ancestors are searched as if
* they were activations, respecting the skip field in each upvar's cookie
* but looking up the property by name instead of frame slot.
*/
JSObject *clone = js_AllocFlatClosure(cx, fun, parent);
if (!clone)
return NULL;
JSUpvarArray *uva = fun->script()->upvars();
uint32_t i = uva->length;
JS_ASSERT(i != 0);
for (Shape::Range r(fun->script()->bindings.lastUpvar()); i-- != 0; r.popFront()) {
JSObject *obj = parent;
int skip = uva->vector[i].level();
while (--skip > 0) {
if (!obj) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
JSMSG_BAD_CLONE_FUNOBJ_SCOPE);
return NULL;
}
obj = obj->enclosingScope();
}
Value v;
if (!obj->getGeneric(cx, r.front().propid(), &v))
return NULL;
clone->toFunction()->setFlatClosureUpvar(i, v);
}
return clone;
return CloneFunctionObject(cx, fun, parent, fun->getAllocKind());
}
JS_PUBLIC_API(JSObject *)

View File

@ -1086,11 +1086,6 @@ fun_hasInstance(JSContext *cx, JSObject *obj, const Value *v, JSBool *bp)
inline void
JSFunction::trace(JSTracer *trc)
{
if (isFlatClosure() && hasFlatClosureUpvars()) {
if (HeapValue *upvars = getFlatClosureUpvars())
MarkValueRange(trc, script()->bindings.countUpvars(), upvars, "upvars");
}
if (isExtended()) {
MarkValueRange(trc, ArrayLength(toExtended()->extendedSlots),
toExtended()->extendedSlots, "nativeReserved");
@ -1113,21 +1108,6 @@ fun_trace(JSTracer *trc, JSObject *obj)
obj->toFunction()->trace(trc);
}
static void
fun_finalize(JSContext *cx, JSObject *obj)
{
if (obj->toFunction()->isFlatClosure())
obj->toFunction()->finalizeUpvars();
}
size_t
JSFunction::sizeOfMisc(JSMallocSizeOfFun mallocSizeOf) const
{
return (isFlatClosure() && hasFlatClosureUpvars()) ?
mallocSizeOf(getFlatClosureUpvars()) :
0;
}
/*
* Reserve two slots in all function objects for XPConnect. Note that this
* does not bloat every instance, only those on which reserved slots are set,
@ -1144,7 +1124,7 @@ JS_FRIEND_DATA(Class) js::FunctionClass = {
fun_enumerate,
(JSResolveOp)fun_resolve,
JS_ConvertStub,
fun_finalize,
NULL, /* finalize */
NULL, /* checkAccess */
NULL, /* call */
NULL, /* construct */
@ -1881,59 +1861,6 @@ js_CloneFunctionObject(JSContext *cx, JSFunction *fun, JSObject *parent,
return clone;
}
/*
* Create a new flat closure, but don't initialize the imported upvar
* values. The tracer calls this function and then initializes the upvar
* slots on trace.
*/
JSFunction * JS_FASTCALL
js_AllocFlatClosure(JSContext *cx, JSFunction *fun, JSObject *scopeChain)
{
JS_ASSERT(fun->isFlatClosure());
JS_ASSERT(JSScript::isValidOffset(fun->script()->upvarsOffset) ==
fun->script()->bindings.hasUpvars());
JS_ASSERT_IF(JSScript::isValidOffset(fun->script()->upvarsOffset),
fun->script()->upvars()->length == fun->script()->bindings.countUpvars());
JSFunction *closure = CloneFunctionObject(cx, fun, scopeChain, JSFunction::ExtendedFinalizeKind);
if (!closure)
return closure;
uint32_t nslots = fun->script()->bindings.countUpvars();
if (nslots == 0)
return closure;
HeapValue *data = (HeapValue *) cx->malloc_(nslots * sizeof(HeapValue));
if (!data)
return NULL;
closure->setExtendedSlot(JSFunction::FLAT_CLOSURE_UPVARS_SLOT, PrivateValue(data));
return closure;
}
JSFunction *
js_NewFlatClosure(JSContext *cx, JSFunction *fun)
{
/*
* Flat closures cannot yet be partial, that is, all upvars must be copied,
* or the closure won't be flattened. Therefore they do not need to search
* enclosing scope objects via JSOP_NAME, etc.
*/
JSObject *scopeChain = &cx->fp()->scopeChain();
JSFunction *closure = js_AllocFlatClosure(cx, fun, scopeChain);
if (!closure || !fun->script()->bindings.hasUpvars())
return closure;
unsigned level = fun->script()->staticLevel;
JSUpvarArray *uva = fun->script()->upvars();
for (uint32_t i = 0, n = uva->length; i < n; i++)
closure->initFlatClosureUpvar(i, GetUpvar(cx, level, uva->vector[i]));
return closure;
}
JSFunction *
js_DefineFunction(JSContext *cx, HandleObject obj, jsid id, Native native,
unsigned nargs, unsigned attrs, AllocKind kind)

View File

@ -57,27 +57,9 @@
* any) it might be.
*
* 00 not interpreted
* 01 interpreted, neither flat nor null closure
* 10 interpreted, flat closure
* 01 interpreted, not null closure
* 11 interpreted, null closure
*
* isFlatClosure() implies isInterpreted() and u.i.script->upvarsOffset != 0.
* isNullClosure() implies isInterpreted() and u.i.script->upvarsOffset == 0.
*
* isInterpreted() but not isFlatClosure() and u.i.script->upvarsOffset != 0
* is an Algol-like function expression or nested function, i.e., a function
* that never escapes upward or downward (heapward), and is only ever called.
*
* Finally, isInterpreted() and u.i.script->upvarsOffset == 0 could be either
* a non-closure (a global function definition, or any function that uses no
* outer names), or a closure of an escaping function that uses outer names
* whose values can't be snapshot (because the outer names could be reassigned
* after the closure is formed, or because assignments could not be analyzed
* due to with or eval).
*
* Such a hard-case function must use JSOP_NAME, etc., and reify outer function
* activations' call objects, etc. if it's not a global function.
*
* NB: JSFUN_EXPR_CLOSURE reuses JSFUN_STUB_GSOPS, which is an API request flag
* bit only, never stored in fun->flags.
*
@ -95,8 +77,7 @@
#define JSFUN_EXPR_CLOSURE 0x1000 /* expression closure: function(x) x*x */
#define JSFUN_EXTENDED 0x2000 /* structure is FunctionExtended */
#define JSFUN_INTERPRETED 0x4000 /* use u.i if kind >= this value else u.n */
#define JSFUN_FLAT_CLOSURE 0x8000 /* flat (aka "display") closure */
#define JSFUN_NULL_CLOSURE 0xc000 /* null closure entrains no scope chain */
#define JSFUN_NULL_CLOSURE 0x8000 /* null closure entrains no scope chain */
#define JSFUN_KINDMASK 0xc000 /* encode interp vs. native and closure
optimization level -- see above */
@ -129,7 +110,6 @@ struct JSFunction : public JSObject
bool isNativeConstructor() const { return flags & JSFUN_CONSTRUCTOR; }
bool isHeavyweight() const { return JSFUN_HEAVYWEIGHT_TEST(flags); }
bool isNullClosure() const { return kind() == JSFUN_NULL_CLOSURE; }
bool isFlatClosure() const { return kind() == JSFUN_FLAT_CLOSURE; }
bool isFunctionPrototype() const { return flags & JSFUN_PROTOTYPE; }
bool isInterpretedConstructor() const { return isInterpreted() && !isFunctionPrototype(); }
@ -154,7 +134,7 @@ struct JSFunction : public JSObject
#define JS_LOCAL_NAME_IS_CONST(nameWord) ((((nameWord) & uintptr_t(1))) != 0)
bool mightEscape() const {
return isInterpreted() && (isFlatClosure() || !script()->bindings.hasUpvars());
return isInterpreted() && isNullClosure();
}
bool joinable() const {
@ -211,11 +191,21 @@ struct JSFunction : public JSObject
}
#if JS_BITS_PER_WORD == 32
# ifdef JS_THREADSAFE
static const js::gc::AllocKind FinalizeKind = js::gc::FINALIZE_OBJECT2_BACKGROUND;
static const js::gc::AllocKind ExtendedFinalizeKind = js::gc::FINALIZE_OBJECT4_BACKGROUND;
# else
static const js::gc::AllocKind FinalizeKind = js::gc::FINALIZE_OBJECT2;
static const js::gc::AllocKind ExtendedFinalizeKind = js::gc::FINALIZE_OBJECT4;
# endif
#else
# ifdef JS_THREADSAFE
static const js::gc::AllocKind FinalizeKind = js::gc::FINALIZE_OBJECT4_BACKGROUND;
static const js::gc::AllocKind ExtendedFinalizeKind = js::gc::FINALIZE_OBJECT8_BACKGROUND;
# else
static const js::gc::AllocKind FinalizeKind = js::gc::FINALIZE_OBJECT4;
static const js::gc::AllocKind ExtendedFinalizeKind = js::gc::FINALIZE_OBJECT8;
# endif
#endif
inline void trace(JSTracer *trc);
@ -248,27 +238,6 @@ struct JSFunction : public JSObject
inline void setExtendedSlot(size_t which, const js::Value &val);
inline const js::Value &getExtendedSlot(size_t which) const;
/*
* Flat closures with one or more upvars snapshot the upvars' values
* into a vector of js::Values referenced from here. This is a private
* pointer but is set only at creation and does not need to be barriered.
*/
static const uint32_t FLAT_CLOSURE_UPVARS_SLOT = 0;
static inline size_t getFlatClosureUpvarsOffset();
inline js::Value getFlatClosureUpvar(uint32_t i) const;
inline void setFlatClosureUpvar(uint32_t i, const js::Value &v);
inline void initFlatClosureUpvar(uint32_t i, const js::Value &v);
private:
inline bool hasFlatClosureUpvars() const;
inline js::HeapValue *getFlatClosureUpvars() const;
public:
/* See comments in fun_finalize. */
inline void finalizeUpvars();
/* Slot holding associated method property, needed for foo.caller handling. */
static const uint32_t METHOD_PROPERTY_SLOT = 0;
@ -284,18 +253,12 @@ struct JSFunction : public JSObject
/*
* Method name imputed from property uniquely assigned to or initialized,
* where the function does not need to be cloned to carry a scope chain or
* flattened upvars. This is set on both the original and cloned function.
* where the function does not need to be cloned to carry a scope chain.
* This is set on both the original and cloned function.
*/
inline JSAtom *methodAtom() const;
inline void setMethodAtom(JSAtom *atom);
/*
* Measures things hanging off this JSFunction that are counted by the
* |miscSize| argument in JSObject::sizeOfExcludingThis().
*/
size_t sizeOfMisc(JSMallocSizeOfFun mallocSizeOf) const;
private:
/*
* These member functions are inherited from JSObject, but should never be applied to
@ -331,12 +294,6 @@ extern JSFunction * JS_FASTCALL
js_CloneFunctionObject(JSContext *cx, JSFunction *fun, JSObject *parent, JSObject *proto,
js::gc::AllocKind kind = JSFunction::FinalizeKind);
extern JSFunction * JS_FASTCALL
js_AllocFlatClosure(JSContext *cx, JSFunction *fun, JSObject *scopeChain);
extern JSFunction *
js_NewFlatClosure(JSContext *cx, JSFunction *fun);
extern JSFunction *
js_DefineFunction(JSContext *cx, js::HandleObject obj, jsid id, JSNative native,
unsigned nargs, unsigned flags,

View File

@ -140,74 +140,6 @@ JSFunction::getExtendedSlot(size_t which) const
return toExtended()->extendedSlots[which];
}
inline bool
JSFunction::hasFlatClosureUpvars() const
{
JS_ASSERT(isFlatClosure());
return isExtended() && !getExtendedSlot(FLAT_CLOSURE_UPVARS_SLOT).isUndefined();
}
inline js::HeapValue *
JSFunction::getFlatClosureUpvars() const
{
JS_ASSERT(hasFlatClosureUpvars());
return (js::HeapValue *) getExtendedSlot(FLAT_CLOSURE_UPVARS_SLOT).toPrivate();
}
inline void
JSFunction::finalizeUpvars()
{
/*
* Cloned function objects may be flat closures with upvars to free.
*
* We must not access JSScript here that is stored in JSFunction. The
* script can be finalized before the function or closure instances. So we
* just check if JSSLOT_FLAT_CLOSURE_UPVARS holds a private value encoded
* as a double. We must also ignore newborn closures that do not have the
* private pointer set.
*
* FIXME bug 648320 - allocate upvars on the GC heap to avoid doing it
* here explicitly.
*/
if (hasFlatClosureUpvars()) {
js::HeapValue *upvars = getFlatClosureUpvars();
js::Foreground::free_(upvars);
}
}
inline js::Value
JSFunction::getFlatClosureUpvar(uint32_t i) const
{
JS_ASSERT(hasFlatClosureUpvars());
JS_ASSERT(script()->bindings.countUpvars() == script()->upvars()->length);
JS_ASSERT(i < script()->bindings.countUpvars());
return getFlatClosureUpvars()[i];
}
inline void
JSFunction::setFlatClosureUpvar(uint32_t i, const js::Value &v)
{
JS_ASSERT(isFlatClosure());
JS_ASSERT(script()->bindings.countUpvars() == script()->upvars()->length);
JS_ASSERT(i < script()->bindings.countUpvars());
getFlatClosureUpvars()[i] = v;
}
inline void
JSFunction::initFlatClosureUpvar(uint32_t i, const js::Value &v)
{
JS_ASSERT(isFlatClosure());
JS_ASSERT(script()->bindings.countUpvars() == script()->upvars()->length);
JS_ASSERT(i < script()->bindings.countUpvars());
getFlatClosureUpvars()[i].init(v);
}
/* static */ inline size_t
JSFunction::getFlatClosureUpvarsOffset()
{
return offsetof(js::FunctionExtended, extendedSlots[FLAT_CLOSURE_UPVARS_SLOT]);
}
namespace js {
static JS_ALWAYS_INLINE bool

View File

@ -3629,14 +3629,10 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
poppedTypes(pc, 0)->addSubset(cx, &pushed[0]);
break;
case JSOP_GETXPROP:
case JSOP_GETFCSLOT:
case JSOP_CALLFCSLOT: {
case JSOP_GETXPROP: {
TypeSet *seen = bytecodeTypes(pc);
addTypeBarrier(cx, pc, seen, Type::UnknownType());
seen->addSubset(cx, &pushed[0]);
if (op == JSOP_CALLFCSLOT)
pushed[0].addPropagateThis(cx, script, pc, Type::UndefinedType());
break;
}
@ -3803,17 +3799,15 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
break;
case JSOP_LAMBDA:
case JSOP_LAMBDA_FC:
case JSOP_DEFFUN:
case JSOP_DEFLOCALFUN:
case JSOP_DEFLOCALFUN_FC: {
unsigned off = (op == JSOP_DEFLOCALFUN || op == JSOP_DEFLOCALFUN_FC) ? SLOTNO_LEN : 0;
case JSOP_DEFLOCALFUN: {
unsigned off = op == JSOP_DEFLOCALFUN ? SLOTNO_LEN : 0;
JSObject *obj = script->getObject(GET_UINT32_INDEX(pc + off));
TypeSet *res = NULL;
if (op == JSOP_LAMBDA || op == JSOP_LAMBDA_FC) {
if (op == JSOP_LAMBDA) {
res = &pushed[0];
} else if (op == JSOP_DEFLOCALFUN || op == JSOP_DEFLOCALFUN_FC) {
} else if (op == JSOP_DEFLOCALFUN) {
uint32_t slot = GetBytecodeSlot(script, pc);
if (trackSlot(slot)) {
res = &pushed[0];

View File

@ -1093,45 +1093,6 @@ DoIncDec(JSContext *cx, JSScript *script, jsbytecode *pc, const Value &v, Value
return true;
}
const Value &
js::GetUpvar(JSContext *cx, unsigned closureLevel, UpvarCookie cookie)
{
JS_ASSERT(closureLevel >= cookie.level() && cookie.level() > 0);
const unsigned targetLevel = closureLevel - cookie.level();
StackFrame *fp = FindUpvarFrame(cx, targetLevel);
unsigned slot = cookie.slot();
const Value *vp;
if (!fp->isFunctionFrame() || fp->isEvalFrame()) {
vp = fp->slots() + fp->numFixed();
} else if (slot < fp->numFormalArgs()) {
vp = fp->formalArgs();
} else if (slot == UpvarCookie::CALLEE_SLOT) {
vp = &fp->calleev();
slot = 0;
} else {
slot -= fp->numFormalArgs();
JS_ASSERT(slot < fp->numSlots());
vp = fp->slots();
}
return vp[slot];
}
extern StackFrame *
js::FindUpvarFrame(JSContext *cx, unsigned targetLevel)
{
StackFrame *fp = cx->fp();
while (true) {
JS_ASSERT(fp && fp->isScriptFrame());
if (fp->script()->staticLevel == targetLevel)
break;
fp = fp->prev();
}
return fp;
}
#define PUSH_COPY(v) do { *regs.sp++ = v; assertSameCompartment(cx, regs.sp[-1]); } while (0)
#define PUSH_COPY_SKIP_CHECK(v) *regs.sp++ = v
#define PUSH_NULL() regs.sp++->setNull()
@ -1249,7 +1210,6 @@ js::AssertValidPropertyCacheHit(JSContext *cx,
* same way as non-call bytecodes.
*/
JS_STATIC_ASSERT(JSOP_NAME_LENGTH == JSOP_CALLNAME_LENGTH);
JS_STATIC_ASSERT(JSOP_GETFCSLOT_LENGTH == JSOP_CALLFCSLOT_LENGTH);
JS_STATIC_ASSERT(JSOP_GETARG_LENGTH == JSOP_CALLARG_LENGTH);
JS_STATIC_ASSERT(JSOP_GETLOCAL_LENGTH == JSOP_CALLLOCAL_LENGTH);
JS_STATIC_ASSERT(JSOP_XMLNAME_LENGTH == JSOP_CALLXMLNAME_LENGTH);
@ -1750,6 +1710,10 @@ ADD_EMPTY_CASE(JSOP_UNUSED20)
ADD_EMPTY_CASE(JSOP_UNUSED21)
ADD_EMPTY_CASE(JSOP_UNUSED22)
ADD_EMPTY_CASE(JSOP_UNUSED23)
ADD_EMPTY_CASE(JSOP_UNUSED24)
ADD_EMPTY_CASE(JSOP_UNUSED25)
ADD_EMPTY_CASE(JSOP_UNUSED26)
ADD_EMPTY_CASE(JSOP_UNUSED27)
ADD_EMPTY_CASE(JSOP_CONDSWITCH)
ADD_EMPTY_CASE(JSOP_TRY)
#if JS_HAS_XML_SUPPORT
@ -3056,18 +3020,6 @@ BEGIN_CASE(JSOP_SETLOCAL)
regs.fp()->localSlot(GET_SLOTNO(regs.pc)) = regs.sp[-1];
END_CASE(JSOP_SETLOCAL)
BEGIN_CASE(JSOP_GETFCSLOT)
BEGIN_CASE(JSOP_CALLFCSLOT)
{
JS_ASSERT(regs.fp()->isNonEvalFunctionFrame());
unsigned index = GET_UINT16(regs.pc);
JSObject *obj = &argv[-2].toObject();
PUSH_COPY(obj->toFunction()->getFlatClosureUpvar(index));
TypeScript::Monitor(cx, script, regs.pc, regs.sp[-1]);
}
END_CASE(JSOP_GETFCSLOT)
BEGIN_CASE(JSOP_DEFCONST)
BEGIN_CASE(JSOP_DEFVAR)
{
@ -3108,8 +3060,6 @@ BEGIN_CASE(JSOP_DEFFUN)
*/
obj2 = &regs.fp()->scopeChain();
} else {
JS_ASSERT(!fun->isFlatClosure());
obj2 = GetScopeChain(cx, regs.fp());
if (!obj2)
goto error;
@ -3214,7 +3164,6 @@ BEGIN_CASE(JSOP_DEFLOCALFUN)
*/
JSFunction *fun = script->getFunction(GET_UINT32_INDEX(regs.pc + SLOTNO_LEN));
JS_ASSERT(fun->isInterpreted());
JS_ASSERT(!fun->isFlatClosure());
JSObject *parent;
if (fun->isNullClosure()) {
@ -3234,18 +3183,6 @@ BEGIN_CASE(JSOP_DEFLOCALFUN)
}
END_CASE(JSOP_DEFLOCALFUN)
BEGIN_CASE(JSOP_DEFLOCALFUN_FC)
{
JSFunction *fun = script->getFunction(GET_UINT32_INDEX(regs.pc + SLOTNO_LEN));
JSObject *obj = js_NewFlatClosure(cx, fun);
if (!obj)
goto error;
regs.fp()->varSlot(GET_SLOTNO(regs.pc)) = ObjectValue(*obj);
}
END_CASE(JSOP_DEFLOCALFUN_FC)
BEGIN_CASE(JSOP_LAMBDA)
{
/* Load the specified function object literal. */
@ -3342,19 +3279,6 @@ BEGIN_CASE(JSOP_LAMBDA)
}
END_CASE(JSOP_LAMBDA)
BEGIN_CASE(JSOP_LAMBDA_FC)
{
JSFunction *fun = script->getFunction(GET_UINT32_INDEX(regs.pc));
JSObject *obj = js_NewFlatClosure(cx, fun);
if (!obj)
goto error;
JS_ASSERT_IF(script->hasGlobal(), obj->getProto() == fun->getProto());
PUSH_OBJECT(*obj);
}
END_CASE(JSOP_LAMBDA_FC)
BEGIN_CASE(JSOP_CALLEE)
JS_ASSERT(regs.fp()->isNonEvalFunctionFrame());
PUSH_COPY(argv[-2]);

View File

@ -263,20 +263,6 @@ HasInstance(JSContext *cx, JSObject *obj, const js::Value *v, JSBool *bp);
extern bool
ValueToId(JSContext *cx, const Value &v, jsid *idp);
/*
* @param closureLevel The static level of the closure that the cookie
* pertains to.
* @param cookie Level amount is a "skip" (delta) value from the
* closure level.
* @return The value of the upvar.
*/
extern const Value &
GetUpvar(JSContext *cx, unsigned level, UpvarCookie cookie);
/* Search the call stack for the nearest frame with static level targetLevel. */
extern StackFrame *
FindUpvarFrame(JSContext *cx, unsigned targetLevel);
/*
* A linked list of the |FrameRegs regs;| variables belonging to all
* js::Interpret C++ frames on this thread's stack.

View File

@ -5751,9 +5751,10 @@ js_DeleteSpecial(JSContext *cx, JSObject *obj, SpecialId sid, Value *rval, JSBoo
namespace js {
bool
HasDataProperty(JSContext *cx, JSObject *obj, jsid methodid, Value *vp)
HasDataProperty(JSContext *cx, JSObject *obj, jsid id, Value *vp)
{
if (const Shape *shape = obj->nativeLookup(cx, methodid)) {
JS_ASSERT(id == js_CheckForStringIndex(id));
if (const Shape *shape = obj->nativeLookup(cx, id)) {
if (shape->hasDefaultGetterOrIsMethod() && shape->hasSlot()) {
*vp = obj->nativeGetSlot(shape->slot());
return true;

View File

@ -1665,11 +1665,18 @@ js_SetNativeAttributes(JSContext *cx, JSObject *obj, js::Shape *shape,
namespace js {
/*
* If obj has an already-resolved data property for methodid, return true and
* store the property value in *vp.
* If obj has an already-resolved data property for id, return true and
* store the property value in *vp. This helper assumes the caller has already
* called js_CheckForStringIndex.
*/
extern bool
HasDataProperty(JSContext *cx, JSObject *obj, jsid methodid, js::Value *vp);
HasDataProperty(JSContext *cx, JSObject *obj, jsid id, Value *vp);
inline bool
HasDataProperty(JSContext *cx, JSObject *obj, JSAtom *atom, Value *vp)
{
return HasDataProperty(cx, obj, js_CheckForStringIndex(ATOM_TO_JSID(atom)), vp);
}
extern JSBool
CheckAccess(JSContext *cx, JSObject *obj, jsid id, JSAccessMode mode,

View File

@ -1242,9 +1242,7 @@ JSObject::sizeOfExcludingThis(JSMallocSizeOfFun mallocSizeOf,
/* Other things may be measured in the future if DMD indicates it is worthwhile. */
*miscSize = 0;
if (isFunction()) {
*miscSize += toFunction()->sizeOfMisc(mallocSizeOf);
} else if (isArguments()) {
if (isArguments()) {
*miscSize += asArguments().sizeOfMisc(mallocSizeOf);
} else if (isRegExpStatics()) {
*miscSize += js::SizeOfRegExpStaticsData(this, mallocSizeOf);

View File

@ -3513,55 +3513,6 @@ Decompile(SprintStack *ss, jsbytecode *pc, int nb)
break;
}
case JSOP_GETFCSLOT:
case JSOP_CALLFCSLOT:
{
if (!jp->fun)
jp->fun = jp->script->getCallerFunction();
if (!jp->localNames) {
JS_ASSERT(fun == jp->fun);
jp->localNames = cx->new_<Vector<JSAtom *> >(cx);
if (!jp->localNames ||
!jp->fun->script()->bindings.getLocalNameArray(cx, jp->localNames))
{
return NULL;
}
}
unsigned index = GET_UINT16(pc);
if (index < jp->fun->script()->bindings.countUpvars()) {
index += jp->fun->script()->bindings.countArgsAndVars();
} else {
JSUpvarArray *uva;
#ifdef DEBUG
/*
* We must be in an eval called from jp->fun, where
* jp->script is the eval-compiled script.
*
* However, it's possible that a js_Invoke already
* pushed a frame trying to call Construct on an
* object that's not a constructor, causing us to be
* called with an intervening frame on the stack.
*/
StackFrame *fp = js_GetTopStackFrame(cx, FRAME_EXPAND_NONE);
if (fp) {
while (!fp->isEvalFrame())
fp = fp->prev();
JS_ASSERT(fp->script() == jp->script);
JS_ASSERT(fp->prev()->fun() == jp->fun);
JS_ASSERT(jp->fun->isInterpreted());
JS_ASSERT(jp->script != jp->fun->script());
JS_ASSERT(JSScript::isValidOffset(jp->script->upvarsOffset));
}
#endif
uva = jp->script->upvars();
index = uva->vector[index].slot();
}
atom = GetArgOrVarAtom(jp, index);
goto do_name;
}
case JSOP_CALLLOCAL:
case JSOP_GETLOCAL:
if (IsVarSlot(jp, pc, &i)) {
@ -4701,7 +4652,6 @@ Decompile(SprintStack *ss, jsbytecode *pc, int nb)
break;
case JSOP_LAMBDA:
case JSOP_LAMBDA_FC:
#if JS_HAS_GENERATOR_EXPRS
sn = js_GetSrcNote(jp->script, pc);
if (sn && SN_TYPE(sn) == SRC_GENEXP) {

View File

@ -364,13 +364,8 @@ OPDEF(JSOP_PICK, 133, "pick", NULL, 2, 0, 0, 0, JOF_UINT8|
OPDEF(JSOP_TRY, 134,"try", NULL, 1, 0, 0, 0, JOF_BYTE)
OPDEF(JSOP_FINALLY, 135,"finally", NULL, 1, 0, 2, 0, JOF_BYTE)
/*
* Get a slot from a flat closure function object that contains a snapshot of
* the closure-invariant upvar values. The immediate operand indexes the upvar
* in the function's u.i.script->upvars() array.
*/
OPDEF(JSOP_GETFCSLOT, 136,"getfcslot", NULL, 3, 0, 1, 19, JOF_UINT16|JOF_NAME|JOF_TYPESET)
OPDEF(JSOP_CALLFCSLOT, 137,"callfcslot", NULL, 3, 0, 1, 19, JOF_UINT16|JOF_NAME|JOF_TYPESET)
OPDEF(JSOP_UNUSED26, 136,"unused20", NULL, 1, 0, 0, 0, JOF_BYTE)
OPDEF(JSOP_UNUSED27, 137,"unused21", NULL, 1, 0, 0, 0, JOF_BYTE)
/*
* Define a local function object as a local variable.
@ -543,12 +538,8 @@ OPDEF(JSOP_LENGTH, 217, "length", NULL, 5, 1, 1, 18, JOF_ATOM|J
OPDEF(JSOP_HOLE, 218, "hole", NULL, 1, 0, 1, 0, JOF_BYTE)
OPDEF(JSOP_UNUSED17, 219,"unused17", NULL, 1, 0, 0, 0, JOF_BYTE)
/*
* Variants of JSOP_{DEFLOCALFUN,LAMBDA} optimized for the flat closure case.
*/
OPDEF(JSOP_DEFLOCALFUN_FC,220,"deflocalfun_fc",NULL, 7, 0, 0, 0, JOF_SLOTOBJECT|JOF_DECLARING|JOF_TMPSLOT)
OPDEF(JSOP_LAMBDA_FC, 221,"lambda_fc", NULL, 5, 0, 1, 19, JOF_OBJECT)
OPDEF(JSOP_UNUSED24, 220,"unused18", NULL, 1, 0, 0, 0, JOF_BYTE)
OPDEF(JSOP_UNUSED25, 221,"unused19", NULL, 1, 0, 0, 0, JOF_BYTE)
/*
* Joined function object as method optimization support.

View File

@ -100,8 +100,6 @@ Bindings::lookup(JSContext *cx, JSAtom *name, unsigned *indexp) const
if (shape->getter() == CallObject::getArgOp)
return ARGUMENT;
if (shape->getter() == CallObject::getUpvarOp)
return UPVAR;
return shape->writable() ? VARIABLE : CONSTANT;
}
@ -126,20 +124,12 @@ Bindings::add(JSContext *cx, JSAtom *name, BindingKind kind)
if (kind == ARGUMENT) {
JS_ASSERT(nvars == 0);
JS_ASSERT(nupvars == 0);
indexp = &nargs;
getter = CallObject::getArgOp;
setter = CallObject::setArgOp;
slot += nargs;
} else if (kind == UPVAR) {
indexp = &nupvars;
getter = CallObject::getUpvarOp;
setter = CallObject::setUpvarOp;
slot = lastBinding->maybeSlot();
attrs |= JSPROP_SHARED;
} else {
JS_ASSERT(kind == VARIABLE || kind == CONSTANT);
JS_ASSERT(nupvars == 0);
indexp = &nvars;
getter = CallObject::getVarOp;
@ -250,9 +240,6 @@ Bindings::getLocalNameArray(JSContext *cx, Vector<JSAtom *> *namesp)
if (shape.getter() == CallObject::getArgOp) {
JS_ASSERT(index < nargs);
} else if (shape.getter() == CallObject::getUpvarOp) {
JS_ASSERT(index < nupvars);
index += nargs + nvars;
} else {
JS_ASSERT(index < nvars);
index += nargs;
@ -290,19 +277,6 @@ Bindings::lastArgument() const
const Shape *
Bindings::lastVariable() const
{
JS_ASSERT(lastBinding);
const js::Shape *shape = lastUpvar();
if (nupvars > 0) {
while (shape->getter() == CallObject::getUpvarOp)
shape = shape->previous();
}
return shape;
}
const Shape *
Bindings::lastUpvar() const
{
JS_ASSERT(lastBinding);
return lastBinding;
@ -445,12 +419,12 @@ XDRScript(JSXDRState *xdr, JSScript **scriptp)
jssrcnote *notes = NULL;
/* XDR arguments, local vars, and upvars. */
uint16_t nargs, nvars, nupvars;
uint16_t nargs, nvars;
#if defined(DEBUG) || defined(__GNUC__) /* quell GCC overwarning */
script = NULL;
nargs = nvars = nupvars = Bindings::BINDING_COUNT_LIMIT;
nargs = nvars = Bindings::BINDING_COUNT_LIMIT;
#endif
uint32_t argsVars, paddingUpvars;
uint32_t argsVars;
if (xdr->mode == JSXDR_ENCODE) {
script = *scriptp;
@ -459,24 +433,19 @@ XDRScript(JSXDRState *xdr, JSScript **scriptp)
nargs = script->bindings.countArgs();
nvars = script->bindings.countVars();
nupvars = script->bindings.countUpvars();
argsVars = (nargs << 16) | nvars;
paddingUpvars = nupvars;
}
if (!JS_XDRUint32(xdr, &argsVars) || !JS_XDRUint32(xdr, &paddingUpvars))
if (!JS_XDRUint32(xdr, &argsVars))
return false;
if (xdr->mode == JSXDR_DECODE) {
nargs = argsVars >> 16;
nvars = argsVars & 0xFFFF;
JS_ASSERT((paddingUpvars >> 16) == 0);
nupvars = paddingUpvars & 0xFFFF;
}
JS_ASSERT(nargs != Bindings::BINDING_COUNT_LIMIT);
JS_ASSERT(nvars != Bindings::BINDING_COUNT_LIMIT);
JS_ASSERT(nupvars != Bindings::BINDING_COUNT_LIMIT);
Bindings bindings(cx);
uint32_t nameCount = nargs + nvars + nupvars;
uint32_t nameCount = nargs + nvars;
if (nameCount > 0) {
LifoAllocScope las(&cx->tempLifoAlloc());
@ -532,12 +501,10 @@ XDRScript(JSXDRState *xdr, JSScript **scriptp)
if (xdr->mode == JSXDR_DECODE) {
BindingKind kind = (i < nargs)
? ARGUMENT
: (i < unsigned(nargs + nvars))
? (bitmap[i >> JS_BITS_PER_UINT32_LOG2] &
: (bitmap[i >> JS_BITS_PER_UINT32_LOG2] &
JS_BIT(i & (JS_BITS_PER_UINT32 - 1))
? CONSTANT
: VARIABLE)
: UPVAR;
? CONSTANT
: VARIABLE);
if (!bindings.add(cx, name, kind))
return false;
}
@ -569,8 +536,6 @@ XDRScript(JSXDRState *xdr, JSScript **scriptp)
if (JSScript::isValidOffset(script->objectsOffset))
nobjects = script->objects()->length;
if (JSScript::isValidOffset(script->upvarsOffset))
JS_ASSERT(script->bindings.countUpvars() == script->upvars()->length);
if (JSScript::isValidOffset(script->regexpsOffset))
nregexps = script->regexps()->length;
if (JSScript::isValidOffset(script->trynotesOffset))
@ -639,7 +604,7 @@ XDRScript(JSXDRState *xdr, JSScript **scriptp)
/* Note: version is packed into the 32b space with another 16b value. */
JSVersion version_ = JSVersion(version & JS_BITMASK(16));
JS_ASSERT((version_ & VersionFlags::FULL_MASK) == unsigned(version_));
script = JSScript::NewScript(cx, length, nsrcnotes, natoms, nobjects, nupvars,
script = JSScript::NewScript(cx, length, nsrcnotes, natoms, nobjects,
nregexps, ntrynotes, nconsts, 0, nClosedArgs,
nClosedVars, nTypeSets, version_);
if (!script)
@ -751,10 +716,6 @@ XDRScript(JSXDRState *xdr, JSScript **scriptp)
*objp = tmp;
}
}
for (i = 0; i != nupvars; ++i) {
if (!JS_XDRUint32(xdr, reinterpret_cast<uint32_t *>(&script->upvars()->vector[i])))
return false;
}
for (i = 0; i != nregexps; ++i) {
if (!XDRScriptRegExpObject(xdr, &script->regexps()->vector[i]))
return false;
@ -989,7 +950,6 @@ JS_STATIC_ASSERT(sizeof(jsbytecode) % sizeof(jssrcnote) == 0);
* coincide with INVALID_OFFSET.
*/
JS_STATIC_ASSERT(sizeof(JSObjectArray) +
sizeof(JSUpvarArray) +
sizeof(JSObjectArray) +
sizeof(JSTryNoteArray) +
sizeof(js::GlobalSlotArray)
@ -998,15 +958,13 @@ JS_STATIC_ASSERT(JSScript::INVALID_OFFSET <= 255);
JSScript *
JSScript::NewScript(JSContext *cx, uint32_t length, uint32_t nsrcnotes, uint32_t natoms,
uint32_t nobjects, uint32_t nupvars, uint32_t nregexps,
uint32_t nobjects, uint32_t nregexps,
uint32_t ntrynotes, uint32_t nconsts, uint32_t nglobals,
uint16_t nClosedArgs, uint16_t nClosedVars, uint32_t nTypeSets, JSVersion version)
{
size_t size = sizeof(JSAtom *) * natoms;
if (nobjects != 0)
size += sizeof(JSObjectArray) + nobjects * sizeof(JSObject *);
if (nupvars != 0)
size += sizeof(JSUpvarArray) + nupvars * sizeof(uint32_t);
if (nregexps != 0)
size += sizeof(JSObjectArray) + nregexps * sizeof(JSObject *);
if (ntrynotes != 0)
@ -1024,7 +982,6 @@ JSScript::NewScript(JSContext *cx, uint32_t length, uint32_t nsrcnotes, uint32_t
* alignment which we ensure below.
*/
JS_STATIC_ASSERT(sizeof(JSObjectArray) % sizeof(jsval) == 0);
JS_STATIC_ASSERT(sizeof(JSUpvarArray) % sizeof(jsval) == 0);
JS_STATIC_ASSERT(sizeof(JSTryNoteArray) % sizeof(jsval) == 0);
JS_STATIC_ASSERT(sizeof(GlobalSlotArray) % sizeof(jsval) == 0);
JS_STATIC_ASSERT(sizeof(JSConstArray) % sizeof(jsval) == 0);
@ -1083,12 +1040,6 @@ JSScript::NewScript(JSContext *cx, uint32_t length, uint32_t nsrcnotes, uint32_t
} else {
script->objectsOffset = JSScript::INVALID_OFFSET;
}
if (nupvars != 0) {
script->upvarsOffset = uint8_t(cursor - data);
cursor += sizeof(JSUpvarArray);
} else {
script->upvarsOffset = JSScript::INVALID_OFFSET;
}
if (nregexps != 0) {
script->regexpsOffset = uint8_t(cursor - data);
cursor += sizeof(JSObjectArray);
@ -1116,7 +1067,6 @@ JSScript::NewScript(JSContext *cx, uint32_t length, uint32_t nsrcnotes, uint32_t
}
JS_STATIC_ASSERT(sizeof(JSObjectArray) +
sizeof(JSUpvarArray) +
sizeof(JSObjectArray) +
sizeof(JSTryNoteArray) +
sizeof(GlobalSlotArray) < 0xFF);
@ -1173,16 +1123,6 @@ JSScript::NewScript(JSContext *cx, uint32_t length, uint32_t nsrcnotes, uint32_t
JS_ASSERT(nTypeSets <= UINT16_MAX);
script->nTypeSets = uint16_t(nTypeSets);
/*
* NB: We allocate the vector of uint32_t upvar cookies after all vectors of
* pointers, to avoid misalignment on 64-bit platforms. See bug 514645.
*/
if (nupvars != 0) {
script->upvars()->length = nupvars;
script->upvars()->vector = reinterpret_cast<UpvarCookie *>(cursor);
cursor += nupvars * sizeof(script->upvars()->vector[0]);
}
script->code = (jsbytecode *)cursor;
JS_ASSERT(cursor + length * sizeof(jsbytecode) + nsrcnotes * sizeof(jssrcnote) == data + size);
@ -1218,11 +1158,9 @@ JSScript::NewScriptFromEmitter(JSContext *cx, BytecodeEmitter *bce)
JS_ASSERT(nClosedArgs == bce->closedArgs.length());
uint16_t nClosedVars = uint16_t(bce->closedVars.length());
JS_ASSERT(nClosedVars == bce->closedVars.length());
size_t upvarIndexCount = bce->upvarIndices.hasMap() ? bce->upvarIndices->count() : 0;
script = NewScript(cx, prologLength + mainLength, nsrcnotes,
bce->atomIndices->count(), bce->objectList.length,
upvarIndexCount, bce->regexpList.length,
bce->ntrynotes, bce->constList.length(),
bce->regexpList.length, bce->ntrynotes, bce->constList.length(),
bce->globalUses.length(), nClosedArgs, nClosedVars,
bce->typesetCount, bce->version());
if (!script)
@ -1294,14 +1232,6 @@ JSScript::NewScriptFromEmitter(JSContext *cx, BytecodeEmitter *bce)
if (bce->flags & TCF_HAS_SINGLETONS)
script->hasSingletons = true;
if (bce->hasUpvarIndices()) {
JS_ASSERT(bce->upvarIndices->count() <= bce->upvarMap.length());
PodCopy<UpvarCookie>(script->upvars()->vector, bce->upvarMap.begin(),
bce->upvarIndices->count());
bce->upvarIndices->clear();
bce->upvarMap.clear();
}
if (bce->globalUses.length()) {
PodCopy<GlobalSlotArray::Entry>(script->globals()->vector, &bce->globalUses[0],
bce->globalUses.length());
@ -1325,25 +1255,17 @@ JSScript::NewScriptFromEmitter(JSContext *cx, BytecodeEmitter *bce)
fun = bce->fun();
JS_ASSERT(fun->isInterpreted());
JS_ASSERT(!fun->script());
#ifdef DEBUG
if (JSScript::isValidOffset(script->upvarsOffset))
JS_ASSERT(script->upvars()->length == script->bindings.countUpvars());
else
JS_ASSERT(script->bindings.countUpvars() == 0);
#endif
if (bce->flags & TCF_FUN_HEAVYWEIGHT)
fun->flags |= JSFUN_HEAVYWEIGHT;
/*
* Mark functions which will only be executed once as singletons.
* Skip this for flat closures, which must be copied on executing.
*/
bool singleton =
cx->typeInferenceEnabled() &&
bce->parent &&
bce->parent->compiling() &&
bce->parent->asBytecodeEmitter()->checkSingletonContext() &&
!fun->isFlatClosure();
bce->parent->asBytecodeEmitter()->checkSingletonContext();
if (!script->typeSetFunction(cx, fun, singleton))
return NULL;

View File

@ -63,61 +63,6 @@ typedef enum JSTryNoteKind {
JSTRY_ITER
} JSTryNoteKind;
namespace js {
/*
* Indicates a location in the stack that an upvar value can be retrieved from
* as a two tuple of (level, slot).
*
* Some existing client code uses the level value as a delta, or level "skip"
* quantity. We could probably document that through use of more types at some
* point in the future.
*
* Existing XDR code wants this to be backed by a 32b integer for serialization,
* so we oblige.
*
* TODO: consider giving more bits to the slot value and takings ome from the level.
*/
class UpvarCookie
{
uint32_t value;
static const uint32_t FREE_VALUE = 0xfffffffful;
void checkInvariants() {
JS_STATIC_ASSERT(sizeof(UpvarCookie) == sizeof(uint32_t));
JS_STATIC_ASSERT(UPVAR_LEVEL_LIMIT < FREE_LEVEL);
}
public:
/*
* All levels above-and-including FREE_LEVEL are reserved so that
* FREE_VALUE can be used as a special value.
*/
static const uint16_t FREE_LEVEL = 0x3fff;
/*
* If a function has a higher static level than this limit, we will not
* optimize it using UPVAR opcodes.
*/
static const uint16_t UPVAR_LEVEL_LIMIT = 16;
static const uint16_t CALLEE_SLOT = 0xffff;
static bool isLevelReserved(uint16_t level) { return level >= FREE_LEVEL; }
bool isFree() const { return value == FREE_VALUE; }
uint32_t asInteger() const { return value; }
/* isFree check should be performed before using these accessors. */
uint16_t level() const { JS_ASSERT(!isFree()); return uint16_t(value >> 16); }
uint16_t slot() const { JS_ASSERT(!isFree()); return uint16_t(value); }
void set(const UpvarCookie &other) { set(other.level(), other.slot()); }
void set(uint16_t newLevel, uint16_t newSlot) { value = (uint32_t(newLevel) << 16) | newSlot; }
void makeFree() { set(0xffff, 0xffff); JS_ASSERT(isFree()); }
void fromInteger(uint32_t u32) { value = u32; }
};
}
/*
* Exception handling record.
*/
@ -140,11 +85,6 @@ typedef struct JSObjectArray {
uint32_t length; /* count of indexed objects */
} JSObjectArray;
typedef struct JSUpvarArray {
js::UpvarCookie *vector; /* array of indexed upvar cookies */
uint32_t length; /* count of indexed upvar cookies */
} JSUpvarArray;
typedef struct JSConstArray {
js::HeapValue *vector; /* array of indexed constant values */
uint32_t length;
@ -163,19 +103,19 @@ struct GlobalSlotArray {
struct Shape;
enum BindingKind { NONE, ARGUMENT, VARIABLE, CONSTANT, UPVAR };
enum BindingKind { NONE, ARGUMENT, VARIABLE, CONSTANT };
/*
* Formal parameters, local variables, and upvars are stored in a shape tree
* Formal parameters and local variables are stored in a shape tree
* path encapsulated within this class. This class represents bindings for
* both function and top-level scripts (the latter is needed to track names in
* strict mode eval code, to give such code its own lexical environment).
*/
class Bindings {
class Bindings
{
HeapPtr<Shape> lastBinding;
uint16_t nargs;
uint16_t nvars;
uint16_t nupvars;
bool hasDup_:1; // true if there are duplicate argument names
inline Shape *initialShape(JSContext *cx) const;
@ -198,13 +138,9 @@ class Bindings {
uint16_t countArgs() const { return nargs; }
uint16_t countVars() const { return nvars; }
uint16_t countUpvars() const { return nupvars; }
unsigned countArgsAndVars() const { return nargs + nvars; }
unsigned countLocalNames() const { return nargs + nvars; }
unsigned countLocalNames() const { return nargs + nvars + nupvars; }
bool hasUpvars() const { return nupvars > 0; }
bool hasLocalNames() const { return countLocalNames() > 0; }
/* Ensure these bindings have a shape lineage. */
@ -226,10 +162,7 @@ class Bindings {
bool setParent(JSContext *cx, JSObject *obj);
enum {
/*
* A script may have no more than this many arguments, variables, or
* upvars.
*/
/* A script may have no more than this many arguments or variables. */
BINDING_COUNT_LIMIT = 0xFFFF
};
@ -246,8 +179,7 @@ class Bindings {
*
* The parser builds shape paths for functions, usable by Call objects at
* runtime, by calling an "add" method. All ARGUMENT bindings must be added
* before before any VARIABLE or CONSTANT bindings, which themselves must
* be added before all UPVAR bindings.
* before before any VARIABLE or CONSTANT bindings.
*/
bool add(JSContext *cx, JSAtom *name, BindingKind kind);
@ -258,9 +190,6 @@ class Bindings {
bool addConstant(JSContext *cx, JSAtom *name) {
return add(cx, name, CONSTANT);
}
bool addUpvar(JSContext *cx, JSAtom *name) {
return add(cx, name, UPVAR);
}
bool addArgument(JSContext *cx, JSAtom *name, uint16_t *slotp) {
JS_ASSERT(name != NULL); /* not destructuring */
*slotp = nargs;
@ -316,7 +245,6 @@ class Bindings {
*/
const js::Shape *lastArgument() const;
const js::Shape *lastVariable() const;
const js::Shape *lastUpvar() const;
void trace(JSTracer *trc);
@ -412,7 +340,7 @@ struct JSScript : public js::gc::Cell {
* kind (function or other) of new JSScript.
*/
static JSScript *NewScript(JSContext *cx, uint32_t length, uint32_t nsrcnotes, uint32_t natoms,
uint32_t nobjects, uint32_t nupvars, uint32_t nregexps,
uint32_t nobjects, uint32_t nregexps,
uint32_t ntrynotes, uint32_t nconsts, uint32_t nglobals,
uint16_t nClosedArgs, uint16_t nClosedVars, uint32_t nTypeSets,
JSVersion version);
@ -443,8 +371,6 @@ struct JSScript : public js::gc::Cell {
uint8_t objectsOffset; /* offset to the array of nested function,
block, scope, xml and one-time regexps
objects */
uint8_t upvarsOffset; /* offset of the array of display ("up")
closure vars */
uint8_t regexpsOffset; /* offset to the array of to-be-cloned
regexps */
uint8_t trynotesOffset; /* offset to the array of try notes */
@ -696,11 +622,6 @@ struct JSScript : public js::gc::Cell {
return reinterpret_cast<JSObjectArray *>(data + objectsOffset);
}
JSUpvarArray *upvars() {
JS_ASSERT(isValidOffset(upvarsOffset));
return reinterpret_cast<JSUpvarArray *>(data + upvarsOffset);
}
JSObjectArray *regexps() {
JS_ASSERT(isValidOffset(regexpsOffset));
return reinterpret_cast<JSObjectArray *>(data + regexpsOffset);

View File

@ -58,7 +58,7 @@ namespace js {
inline
Bindings::Bindings(JSContext *cx)
: lastBinding(NULL), nargs(0), nvars(0), nupvars(0), hasDup_(false)
: lastBinding(NULL), nargs(0), nvars(0), hasDup_(false)
{}
inline void

View File

@ -1793,8 +1793,7 @@ InterpretDollar(JSContext *cx, RegExpStatics *res, const jschar *dp, const jscha
static bool
FindReplaceLength(JSContext *cx, RegExpStatics *res, ReplaceData &rdata, size_t *sizep)
{
JSObject *base = rdata.elembase;
if (base) {
if (JSObject *base = rdata.elembase) {
/*
* The base object is used when replace was passed a lambda which looks like
* 'function(a) { return b[a]; }' for the base object b. b will not change
@ -1818,26 +1817,14 @@ FindReplaceLength(JSContext *cx, RegExpStatics *res, ReplaceData &rdata, size_t
if (!atom)
return false;
}
jsid id = ATOM_TO_JSID(atom);
JSObject *holder;
JSProperty *prop = NULL;
if (!LookupPropertyWithFlags(cx, base, id, JSRESOLVE_QUALIFIED, &holder, &prop))
return false;
/* Only handle the case where the property exists and is on this object. */
if (prop && holder == base) {
Shape *shape = (Shape *) prop;
if (shape->hasSlot() && shape->hasDefaultGetter()) {
Value value = base->getSlot(shape->slot());
if (value.isString()) {
rdata.repstr = value.toString()->ensureLinear(cx);
if (!rdata.repstr)
return false;
*sizep = rdata.repstr->length();
return true;
}
}
Value v;
if (HasDataProperty(cx, base, atom, &v) && v.isString()) {
rdata.repstr = v.toString()->ensureLinear(cx);
if (!rdata.repstr)
return false;
*sizep = rdata.repstr->length();
return true;
}
/*
@ -1847,8 +1834,7 @@ FindReplaceLength(JSContext *cx, RegExpStatics *res, ReplaceData &rdata, size_t
rdata.elembase = NULL;
}
JSObject *lambda = rdata.lambda;
if (lambda) {
if (JSObject *lambda = rdata.lambda) {
PreserveRegExpStatics staticsGuard(res);
if (!staticsGuard.init(cx))
return false;
@ -2222,6 +2208,78 @@ str_replace_flat_lambda(JSContext *cx, CallArgs outerArgs, ReplaceData &rdata, c
static const uint32_t ReplaceOptArg = 2;
/*
* Pattern match the script to check if it is is indexing into a particular
* object, e.g. 'function(a) { return b[a]; }'. Avoid calling the script in
* such cases, which are used by javascript packers (particularly the popular
* Dean Edwards packer) to efficiently encode large scripts. We only handle the
* code patterns generated by such packers here.
*/
static JSObject *
LambdaIsGetElem(JSObject &lambda, JSContext *cx)
{
if (!lambda.isFunction())
return NULL;
JSFunction *fun = lambda.toFunction();
if (!fun->isInterpreted())
return NULL;
JSScript *script = fun->script();
jsbytecode *pc = script->code;
/* Look for an access to 'b' in the enclosing scope. */
if (JSOp(*pc) != JSOP_NAME)
return NULL;
PropertyName *bname;
GET_NAME_FROM_BYTECODE(script, pc, 0, bname);
pc += JSOP_NAME_LENGTH;
/*
* Do a conservative search for 'b' in the enclosing scope. Avoid using a
* real name lookup since this can trigger observable effects.
*/
Value b;
JSObject *scope = cx->stack.currentScriptedScopeChain();
while (true) {
if (scope->isCall()) {
if (scope->asCall().containsVarOrArg(bname, &b, cx))
break;
} else if (scope->isBlock()) {
if (scope->asClonedBlock().containsVar(bname, &b, cx))
break;
} else {
return NULL;
}
scope = &scope->asScope().enclosingScope();
}
/* Look for 'a' to be the lambda's first argument. */
if (JSOp(*pc) != JSOP_GETARG || GET_SLOTNO(pc) != 0)
return NULL;
pc += JSOP_GETARG_LENGTH;
/* 'b[a]' */
if (JSOp(*pc) != JSOP_GETELEM)
return NULL;
pc += JSOP_GETELEM_LENGTH;
/* 'return b[a]' */
if (JSOp(*pc) != JSOP_RETURN)
return NULL;
/* 'b' must behave like a normal object. */
if (!b.isObject())
return NULL;
JSObject &bobj = b.toObject();
Class *clasp = bobj.getClass();
if (!clasp->isNative() || clasp->ops.lookupProperty || clasp->ops.getProperty)
return NULL;
return &bobj;
}
JSBool
js::str_replace(JSContext *cx, unsigned argc, Value *vp)
{
@ -2242,39 +2300,8 @@ js::str_replace(JSContext *cx, unsigned argc, Value *vp)
rdata.repstr = NULL;
rdata.dollar = rdata.dollarEnd = NULL;
if (rdata.lambda->isFunction()) {
JSFunction *fun = rdata.lambda->toFunction();
if (fun->isInterpreted()) {
/*
* Pattern match the script to check if it is is indexing into a
* particular object, e.g. 'function(a) { return b[a]; }'. Avoid
* calling the script in such cases, which are used by javascript
* packers (particularly the popular Dean Edwards packer) to efficiently
* encode large scripts. We only handle the code patterns generated
* by such packers here.
*/
JSScript *script = fun->script();
jsbytecode *pc = script->code;
Value table = UndefinedValue();
if (JSOp(*pc) == JSOP_GETFCSLOT) {
table = fun->getFlatClosureUpvar(GET_UINT16(pc));
pc += JSOP_GETFCSLOT_LENGTH;
}
if (table.isObject() &&
JSOp(*pc) == JSOP_GETARG && GET_SLOTNO(pc) == 0 &&
JSOp(pc[JSOP_GETARG_LENGTH]) == JSOP_GETELEM &&
JSOp(pc[JSOP_GETARG_LENGTH + JSOP_GETELEM_LENGTH]) == JSOP_RETURN) {
Class *clasp = table.toObject().getClass();
if (clasp->isNative() &&
!clasp->ops.lookupProperty &&
!clasp->ops.getProperty) {
rdata.elembase = &table.toObject();
}
}
}
}
if (JSObject *base = LambdaIsGetElem(*rdata.lambda, cx))
rdata.elembase = base;
} else {
rdata.lambda = NULL;
rdata.elembase = NULL;

View File

@ -192,7 +192,7 @@ JS_XDRScript(JSXDRState *xdr, JSScript **scriptp);
* and saved versions. If deserialization fails, the data should be
* invalidated if possible.
*/
#define JSXDR_BYTECODE_VERSION (0xb973c0de - 109)
#define JSXDR_BYTECODE_VERSION (0xb973c0de - 110)
JS_END_EXTERN_C

View File

@ -3054,25 +3054,6 @@ mjit::Compiler::generateMethod()
}
END_CASE(JSOP_SETCONST)
BEGIN_CASE(JSOP_DEFLOCALFUN_FC)
{
uint32_t slot = GET_SLOTNO(PC);
JSFunction *fun = script->getFunction(GET_UINT32_INDEX(PC + SLOTNO_LEN));
/* See JSOP_DEFLOCALFUN. */
markUndefinedLocal(PC - script->code, slot);
prepareStubCall(Uses(frame.frameSlots()));
masm.move(ImmPtr(fun), Registers::ArgReg1);
INLINE_STUBCALL(stubs::DefLocalFun_FC, REJOIN_DEFLOCALFUN);
frame.takeReg(Registers::ReturnReg);
frame.pushTypedPayload(JSVAL_TYPE_OBJECT, Registers::ReturnReg);
frame.storeLocal(slot, true);
frame.pop();
updateVarType();
}
END_CASE(JSOP_DEFLOCALFUN_FC)
BEGIN_CASE(JSOP_LAMBDA)
{
JSFunction *fun = script->getFunction(GET_UINT32_INDEX(PC));
@ -3117,27 +3098,6 @@ mjit::Compiler::generateMethod()
frame.syncAndForgetEverything();
END_CASE(JSOP_TRY)
BEGIN_CASE(JSOP_GETFCSLOT)
BEGIN_CASE(JSOP_CALLFCSLOT)
{
unsigned index = GET_UINT16(PC);
// Load the callee's payload into a register.
frame.pushCallee();
RegisterID reg = frame.copyDataIntoReg(frame.peek(-1));
frame.pop();
// obj->getFlatClosureUpvars()
Address upvarAddress(reg, JSFunction::getFlatClosureUpvarsOffset());
masm.loadPrivate(upvarAddress, reg);
// push ((Value *) reg)[index]
BarrierState barrier = pushAddressMaybeBarrier(Address(reg, index * sizeof(Value)),
knownPushedType(0), true);
finishBarrier(barrier, REJOIN_GETTER, 0);
}
END_CASE(JSOP_CALLFCSLOT)
BEGIN_CASE(JSOP_DEFLOCALFUN)
{
uint32_t slot = GET_SLOTNO(PC);
@ -3237,17 +3197,6 @@ mjit::Compiler::generateMethod()
frame.push(MagicValue(JS_ARRAY_HOLE));
END_CASE(JSOP_HOLE)
BEGIN_CASE(JSOP_LAMBDA_FC)
{
JSFunction *fun = script->getFunction(GET_UINT32_INDEX(PC));
prepareStubCall(Uses(frame.frameSlots()));
masm.move(ImmPtr(fun), Registers::ArgReg1);
INLINE_STUBCALL(stubs::FlatLambda, REJOIN_PUSH_OBJECT);
frame.takeReg(Registers::ReturnReg);
frame.pushTypedPayload(JSVAL_TYPE_OBJECT, Registers::ReturnReg);
}
END_CASE(JSOP_LAMBDA_FC)
BEGIN_CASE(JSOP_LOOPHEAD)
{
if (analysis->jumpTarget(PC)) {

View File

@ -339,8 +339,6 @@ stubs::DefFun(VMFrame &f, JSFunction *fun)
*/
obj2 = &fp->scopeChain();
} else {
JS_ASSERT(!fun->isFlatClosure());
obj2 = GetScopeChain(cx, fp);
if (!obj2)
THROW();
@ -976,16 +974,6 @@ stubs::InitElem(VMFrame &f, uint32_t last)
}
}
void JS_FASTCALL
stubs::GetUpvar(VMFrame &f, uint32_t ck)
{
/* :FIXME: We can do better, this stub isn't needed. */
uint32_t staticLevel = f.script()->staticLevel;
UpvarCookie cookie;
cookie.fromInteger(ck);
f.regs.sp[0] = GetUpvar(f.cx, staticLevel, cookie);
}
JSObject * JS_FASTCALL
stubs::DefLocalFun(VMFrame &f, JSFunction *fun)
{
@ -997,7 +985,6 @@ stubs::DefLocalFun(VMFrame &f, JSFunction *fun)
* activation.
*/
JS_ASSERT(fun->isInterpreted());
JS_ASSERT(!fun->isFlatClosure());
JSObject *parent;
if (fun->isNullClosure()) {
@ -1016,15 +1003,6 @@ stubs::DefLocalFun(VMFrame &f, JSFunction *fun)
return obj;
}
JSObject * JS_FASTCALL
stubs::DefLocalFun_FC(VMFrame &f, JSFunction *fun)
{
JSObject *obj = js_NewFlatClosure(f.cx, fun);
if (!obj)
THROWV(NULL);
return obj;
}
void JS_FASTCALL
stubs::RegExp(VMFrame &f, JSObject *regex)
{
@ -1283,15 +1261,6 @@ stubs::Throw(VMFrame &f)
THROW();
}
JSObject * JS_FASTCALL
stubs::FlatLambda(VMFrame &f, JSFunction *fun)
{
JSObject *obj = js_NewFlatClosure(f.cx, fun);
if (!obj)
THROWV(NULL);
return obj;
}
void JS_FASTCALL
stubs::Arguments(VMFrame &f)
{

View File

@ -126,7 +126,6 @@ void JS_FASTCALL GetElem(VMFrame &f);
template<JSBool strict> void JS_FASTCALL SetElem(VMFrame &f);
void JS_FASTCALL ToId(VMFrame &f);
void JS_FASTCALL ImplicitThis(VMFrame &f, PropertyName *name);
void JS_FASTCALL GetUpvar(VMFrame &f, uint32_t index);
template <JSBool strict> void JS_FASTCALL DelProp(VMFrame &f, PropertyName *name);
template <JSBool strict> void JS_FASTCALL DelElem(VMFrame &f);

View File

@ -1717,32 +1717,7 @@ DisassembleScript(JSContext *cx, JSScript *script, JSFunction *fun, bool lines,
if (fun->isNullClosure())
Sprint(sp, " NULL_CLOSURE");
else if (fun->isFlatClosure())
Sprint(sp, " FLAT_CLOSURE");
JSScript *script = fun->script();
if (script->bindings.hasUpvars()) {
Sprint(sp, "\nupvars: {\n");
Vector<JSAtom *> localNames(cx);
if (!script->bindings.getLocalNameArray(cx, &localNames))
return false;
JSUpvarArray *uva = script->upvars();
unsigned upvar_base = script->bindings.countArgsAndVars();
for (uint32_t i = 0, n = uva->length; i < n; i++) {
JSAtom *atom = localNames[upvar_base + i];
UpvarCookie cookie = uva->vector[i];
JSAutoByteString printable;
if (js_AtomToPrintableString(cx, atom, &printable)) {
Sprint(sp, " %s: {skip:%u, slot:%u},\n",
printable.ptr(), cookie.level(), cookie.slot());
}
}
Sprint(sp, "}");
}
Sprint(sp, "\n");
}

View File

@ -9,9 +9,6 @@ if (typeof findReferences == "function") {
var o = ({});
function returnFlat(x) { return function flat() { return x; }; }
assertEq(referencesVia(returnFlat(o), 'upvars[0]', o), true);
function returnHeavy(y) { eval(''); return function heavy() { return y; }; }
assertEq(referencesVia(returnHeavy(o), 'fun_callscope; y', o), true);
assertEq(referencesVia(returnHeavy(o), 'fun_callscope; shape; base; parent', this), true);

View File

@ -144,7 +144,7 @@ GlobalObject::initFunctionAndObjectClasses(JSContext *cx)
functionProto->flags |= JSFUN_PROTOTYPE;
JSScript *script =
JSScript::NewScript(cx, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, JSVERSION_DEFAULT);
JSScript::NewScript(cx, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, JSVERSION_DEFAULT);
if (!script)
return NULL;
script->noScriptRval = true;

View File

@ -86,7 +86,7 @@ js_PutCallObject(StackFrame *fp)
JS_ASSERT(script == callobj.getCalleeFunction()->script());
JS_ASSERT(script == fun->script());
unsigned n = bindings.countArgsAndVars();
unsigned n = bindings.countLocalNames();
if (n > 0) {
uint32_t nvars = bindings.countVars();
uint32_t nargs = bindings.countArgs();
@ -332,28 +332,6 @@ CallObject::setArgOp(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value
return true;
}
JSBool
CallObject::getUpvarOp(JSContext *cx, JSObject *obj, jsid id, Value *vp)
{
CallObject &callobj = obj->asCall();
JS_ASSERT((int16_t) JSID_TO_INT(id) == JSID_TO_INT(id));
unsigned i = (uint16_t) JSID_TO_INT(id);
*vp = callobj.getCallee()->toFunction()->getFlatClosureUpvar(i);
return true;
}
JSBool
CallObject::setUpvarOp(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value *vp)
{
CallObject &callobj = obj->asCall();
JS_ASSERT((int16_t) JSID_TO_INT(id) == JSID_TO_INT(id));
unsigned i = (uint16_t) JSID_TO_INT(id);
callobj.getCallee()->toFunction()->setFlatClosureUpvar(i, *vp);
return true;
}
JSBool
CallObject::getVarOp(JSContext *cx, JSObject *obj, jsid id, Value *vp)
{
@ -391,6 +369,22 @@ CallObject::setVarOp(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value
return true;
}
bool
CallObject::containsVarOrArg(PropertyName *name, Value *vp, JSContext *cx)
{
jsid id = ATOM_TO_JSID(name);
const Shape *shape = nativeLookup(cx, id);
if (!shape)
return false;
PropertyOp op = shape->getterOp();
if (op != getVarOp && op != getArgOp)
return false;
JS_ALWAYS_TRUE(op(cx, this, INT_TO_JSID(shape->shortid()), vp));
return true;
}
static JSBool
call_resolve(JSContext *cx, JSObject *obj, jsid id, unsigned flags, JSObject **objp)
{
@ -872,6 +866,19 @@ block_setProperty(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value *v
return true;
}
bool
ClonedBlockObject::containsVar(PropertyName *name, Value *vp, JSContext *cx)
{
jsid id = ATOM_TO_JSID(name);
const Shape *shape = nativeLookup(cx, id);
if (!shape)
return false;
JS_ASSERT(shape->getterOp() == block_getProperty);
JS_ALWAYS_TRUE(block_getProperty(cx, this, INT_TO_JSID(shape->shortid()), vp));
return true;
}
StaticBlockObject *
StaticBlockObject::create(JSContext *cx)
{

View File

@ -168,10 +168,11 @@ class CallObject : public ScopeObject
static JSBool setArgumentsOp(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value *vp);
static JSBool getArgOp(JSContext *cx, JSObject *obj, jsid id, Value *vp);
static JSBool getVarOp(JSContext *cx, JSObject *obj, jsid id, Value *vp);
static JSBool getUpvarOp(JSContext *cx, JSObject *obj, jsid id, Value *vp);
static JSBool setArgOp(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value *vp);
static JSBool setVarOp(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value *vp);
static JSBool setUpvarOp(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value *vp);
/* Return whether this environment contains 'name' and, if so, its value. */
bool containsVarOrArg(PropertyName *name, Value *vp, JSContext *cx);
};
class DeclEnvObject : public ScopeObject
@ -270,6 +271,9 @@ class ClonedBlockObject : public BlockObject
/* Assuming 'put' has been called, return the value of the ith let var. */
const Value &closedSlot(unsigned i);
/* Return whether this environment contains 'name' and, if so, its value. */
bool containsVar(PropertyName *name, Value *vp, JSContext *cx);
};
extern bool