diff --git a/js/src/jit-test/tests/jaeger/argumentsOptimize-1.js b/js/src/jit-test/tests/jaeger/argumentsOptimize-1.js new file mode 100644 index 00000000000..33971cbe840 --- /dev/null +++ b/js/src/jit-test/tests/jaeger/argumentsOptimize-1.js @@ -0,0 +1,14 @@ + +function bar() { + foo.arguments.length = 10; +} + +function foo(x) { + var a = arguments; + var n = 0; + bar(); + assertEq(x, 5); + assertEq(a.length, 10); +} + +foo(5); diff --git a/js/src/jit-test/tests/jaeger/argumentsOptimize-2.js b/js/src/jit-test/tests/jaeger/argumentsOptimize-2.js new file mode 100644 index 00000000000..e7b1b9bd2ab --- /dev/null +++ b/js/src/jit-test/tests/jaeger/argumentsOptimize-2.js @@ -0,0 +1,27 @@ + +function foo() { + var x = 0; + for (var i = arguments.length - 1; i >= 0; i--) + x += arguments[i]; + return x; +} + +function bar() { + var x = 0; + for (var i = 0; i < arguments.length; i++) + x += arguments[i]; + return x; +} + +function baz(a,b,c,d,e) { + var x = 0; + for (var i = 0; i < arguments.length; i++) + x += arguments[i]; + return x; +} + +for (var i = 0; i < 10; i++) { + assertEq(foo(1,2,3,4,5), 15); + assertEq(bar(1,2.5,true,{valueOf:function() { return 10}},"five"), "14.5five"); + assertEq(baz(1,2,3,4,5), 15); +} diff --git a/js/src/jit-test/tests/jaeger/recompile/bug659766.js b/js/src/jit-test/tests/jaeger/recompile/bug659766.js new file mode 100644 index 00000000000..012447ba438 --- /dev/null +++ b/js/src/jit-test/tests/jaeger/recompile/bug659766.js @@ -0,0 +1,29 @@ +var gTestcases = new Array; +var gTc = gTestcases; +function TestCase(n, d, e, a) { + this.description=d + this.reason='' + gTestcases[gTc++]=this +} +TestCase.prototype.dump=function () + toPrinted(this.description) + toPrinted(this.reason) + '\n'; +function toPrinted(value) value=value.replace(/\\n/g, 'NL').replace(/[^\x20-\x7E]+/g, escapeString); +function escapeString (str) { + try { + err + } catch(ex) { } +} +function jsTestDriverEnd() { + for (var i = 0; i < gTestcases.length; i++) + gTestcases[i].dump() +} +var SECTION = "dowhile-007"; +DoWhile(); +function DoWhile( object ) result1=false; +new TestCase( + SECTION, + "break one: ", + result1 +); +jsTestDriverEnd(); +new TestCase( SECTION, "'�O� �:i��'.match(new RegExp('.+'))", [], '�O� �:i��'); +jsTestDriverEnd(); diff --git a/js/src/jsanalyze.cpp b/js/src/jsanalyze.cpp index 7e8626e08fe..d9662274a70 100644 --- a/js/src/jsanalyze.cpp +++ b/js/src/jsanalyze.cpp @@ -329,6 +329,10 @@ ScriptAnalysis::analyzeBytecode(JSContext *cx) isInlineable = false; } + modifiesArguments_ = false; + if (script->nClosedArgs || (script->fun && script->fun->isHeavyweight())) + modifiesArguments_ = true; + canTrackVars = true; /* @@ -641,15 +645,19 @@ ScriptAnalysis::analyzeBytecode(JSContext *cx) break; } - /* Additional opcodes which can be compiled but which can't be inlined. */ - case JSOP_ARGUMENTS: - case JSOP_EVAL: case JSOP_FORARG: case JSOP_SETARG: case JSOP_INCARG: case JSOP_DECARG: case JSOP_ARGINC: case JSOP_ARGDEC: + modifiesArguments_ = true; + isInlineable = false; + break; + + /* Additional opcodes which can be compiled but which can't be inlined. */ + case JSOP_ARGUMENTS: + case JSOP_EVAL: case JSOP_THROW: case JSOP_EXCEPTION: case JSOP_DEFLOCALFUN: diff --git a/js/src/jsanalyze.h b/js/src/jsanalyze.h index 5508fd0d5ff..4449028669f 100644 --- a/js/src/jsanalyze.h +++ b/js/src/jsanalyze.h @@ -742,6 +742,12 @@ class SSAValue u.var.offset = offset; } + static SSAValue WrittenVar(uint32 slot, uint32 offset) { + SSAValue v; + v.initWritten(slot, offset); + return v; + } + void initPhi(uint32 offset, SSAPhiNode *node) { clear(); u.phi.kind = PHI; @@ -864,6 +870,7 @@ class ScriptAnalysis bool canTrackVars; bool isInlineable; uint32 numReturnSites_; + bool modifiesArguments_; /* Offsets at which each local becomes unconditionally defined, or a value below. */ uint32 *definedLocals; @@ -906,6 +913,12 @@ class ScriptAnalysis bool hasFunctionCalls() const { return hasCalls; } uint32 numReturnSites() const { return numReturnSites_; } + /* + * True if all named formal arguments are not modified. If the arguments + * object cannot escape, the arguments are never modified within the script. + */ + bool modifiesArguments() { return modifiesArguments_; } + /* Accessors for bytecode information. */ Bytecode& getCode(uint32 offset) { @@ -1151,6 +1164,8 @@ class ScriptAnalysis /* Type inference helpers */ bool analyzeTypesBytecode(JSContext *cx, unsigned offset, TypeInferenceState &state); + bool followEscapingArguments(JSContext *cx, const SSAValue &v, Vector *seen); + bool followEscapingArguments(JSContext *cx, SSAUseChain *use, Vector *seen); inline void setForTypes(JSContext *cx, jsbytecode *pc, types::TypeSet *types); }; diff --git a/js/src/jsdbgapi.cpp b/js/src/jsdbgapi.cpp index 7fa134212be..e59aef10b2d 100644 --- a/js/src/jsdbgapi.cpp +++ b/js/src/jsdbgapi.cpp @@ -223,6 +223,10 @@ JS_SetDebugModeForCompartment(JSContext *cx, JSCompartment *comp, JSBool debug) mjit::ReleaseScriptCode(cx, script, true); mjit::ReleaseScriptCode(cx, script, false); script->debugMode = !!debug; + + /* Mark arguments objects as escaping in all scripts if debug mode is on. */ + if (script->usesArguments && debug) + cx->markTypeObjectFlags(script->fun->getType(), types::OBJECT_FLAG_CREATED_ARGUMENTS); } #endif diff --git a/js/src/jsemit.cpp b/js/src/jsemit.cpp index 2b4741d7d70..74ad7e71ea2 100644 --- a/js/src/jsemit.cpp +++ b/js/src/jsemit.cpp @@ -2824,7 +2824,8 @@ EmitPropOp(JSContext *cx, JSParseNode *pn, JSOp op, JSCodeGenerator *cg, /* Try to optimize arguments.length into JSOP_ARGCNT */ if (!BindNameToSlot(cx, cg, pn2)) return JS_FALSE; - if (pn->pn_atom == cx->runtime->atomState.lengthAtom) { + if (!cx->typeInferenceEnabled() && + pn->pn_atom == cx->runtime->atomState.lengthAtom) { if (pn2->pn_op == JSOP_ARGUMENTS) return js_Emit1(cx, cg, JSOP_ARGCNT) >= 0; } @@ -2916,6 +2917,7 @@ EmitElemOp(JSContext *cx, JSParseNode *pn, JSOp op, JSCodeGenerator *cg) if (left->pn_op == JSOP_ARGUMENTS && JSDOUBLE_IS_INT32(next->pn_dval, &slot) && jsuint(slot) < JS_BIT(16) && + !cx->typeInferenceEnabled() && (!cg->inStrictMode() || (!cg->mutatesParameter() && !cg->callsEval()))) { /* @@ -2992,6 +2994,7 @@ EmitElemOp(JSContext *cx, JSParseNode *pn, JSOp op, JSCodeGenerator *cg) if (left->pn_op == JSOP_ARGUMENTS && JSDOUBLE_IS_INT32(right->pn_dval, &slot) && jsuint(slot) < JS_BIT(16) && + !cx->typeInferenceEnabled() && (!cg->inStrictMode() || (!cg->mutatesParameter() && !cg->callsEval()))) { left->pn_offset = right->pn_offset = top; diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index aadef6382b1..93d39174925 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -109,6 +109,9 @@ js_GetArgsValue(JSContext *cx, StackFrame *fp, Value *vp) { JSObject *argsobj; + cx->markTypeObjectFlags(fp->fun()->getType(), + OBJECT_FLAG_CREATED_ARGUMENTS | OBJECT_FLAG_UNINLINEABLE); + if (fp->hasOverriddenArgs()) { JS_ASSERT(fp->hasCallObj()); jsid id = ATOM_TO_JSID(cx->runtime->atomState.argumentsAtom); diff --git a/js/src/jsinfer.cpp b/js/src/jsinfer.cpp index 95783e4a737..59fcad693a3 100644 --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -175,6 +175,8 @@ types::TypeString(jstype type) return "float"; case TYPE_STRING: return "string"; + case TYPE_LAZYARGS: + return "lazyargs"; case TYPE_UNKNOWN: return "unknown"; default: { @@ -200,8 +202,8 @@ types::InferSpew(SpewChannel channel, const char *fmt, ...) } /* Whether types can be considered to contain type or an equivalent, for checking results. */ -static inline bool -TypeSetMatches(JSContext *cx, TypeSet *types, jstype type) +bool +types::TypeMatches(JSContext *cx, TypeSet *types, jstype type) { if (types->hasType(type)) return true; @@ -253,7 +255,7 @@ types::TypeHasProperty(JSContext *cx, TypeObject *obj, jsid id, const Value &val AutoEnterTypeInference enter(cx); TypeSet *types = obj->getProperty(cx, id, false); - if (types && !TypeSetMatches(cx, types, type)) { + if (types && !TypeMatches(cx, types, type)) { TypeFailure(cx, "Missing type in object %s %s: %s", obj->name(), TypeIdString(id), TypeString(type)); } @@ -291,7 +293,7 @@ TypeSet::addTypeSet(JSContext *cx, ClonedTypeSet *types) return; } - for (jstype type = TYPE_UNDEFINED; type <= TYPE_STRING; type++) { + for (jstype type = TYPE_UNDEFINED; type < TYPE_UNKNOWN; type++) { if (types->typeFlags & (1 << type)) addType(cx, type); } @@ -334,7 +336,7 @@ TypeSet::add(JSContext *cx, TypeConstraint *constraint, bool callExisting) return; } - for (jstype type = TYPE_UNDEFINED; type <= TYPE_STRING; type++) { + for (jstype type = TYPE_UNDEFINED; type < TYPE_UNKNOWN; type++) { if (typeFlags & (1 << type)) cx->compartment->types.addPending(cx, constraint, this, type); } @@ -380,6 +382,8 @@ TypeSet::print(JSContext *cx) printf(" float"); if (typeFlags & TYPE_FLAG_STRING) printf(" string"); + if (typeFlags & TYPE_FLAG_LAZYARGS) + printf(" lazyargs"); if (objectCount) { printf(" object[%u]", objectCount); @@ -783,6 +787,35 @@ TypeSet::addSubsetBarrier(JSContext *cx, JSScript *script, jsbytecode *pc, TypeS add(cx, ArenaNew(cx->compartment->pool, script, pc, target)); } +/* + * Constraint which marks a pushed ARGUMENTS value as unknown if the script has + * an arguments object created in the future. + */ +class TypeConstraintLazyArguments : public TypeConstraint +{ +public: + jsbytecode *pc; + TypeSet *target; + + TypeConstraintLazyArguments(JSScript *script, TypeSet *target) + : TypeConstraint("lazyArgs", script), target(target) + {} + + void newType(JSContext *cx, TypeSet *source, jstype type) {} + + void newObjectState(JSContext *cx, TypeObject *object, bool force) + { + if (object->hasAnyFlags(OBJECT_FLAG_CREATED_ARGUMENTS)) + target->addType(cx, TYPE_UNKNOWN); + } +}; + +void +TypeSet::addLazyArguments(JSContext *cx, JSScript *script, TypeSet *target) +{ + add(cx, ArenaNew(cx->compartment->pool, script, target)); +} + /* * Type constraint which marks the result of 'for in' loops as unknown if the * iterated value could be a generator. @@ -860,6 +893,15 @@ GetPropertyObject(JSContext *cx, JSScript *script, jstype type) return object; } +static inline void +MarkPropertyAccessUnknown(JSContext *cx, JSScript *script, jsbytecode *pc, TypeSet *target) +{ + if (CanHaveReadBarrier(pc)) + script->analysis(cx)->addTypeBarrier(cx, pc, target, TYPE_UNKNOWN); + else + target->addType(cx, TYPE_UNKNOWN); +} + /* * Handle a property access on a specific object. All property accesses go through * here, whether via x.f, x[f], or global name accesses. @@ -888,7 +930,7 @@ PropertyAccess(JSContext *cx, JSScript *script, jsbytecode *pc, TypeObject *obje /* Reads from objects with unknown properties are unknown, writes to such objects are ignored. */ if (object->unknownProperties()) { if (!assign) - target->addType(cx, TYPE_UNKNOWN); + MarkPropertyAccessUnknown(cx, script, pc, target); return; } @@ -915,6 +957,8 @@ PropertyAccess(JSContext *cx, JSScript *script, jsbytecode *pc, TypeObject *obje void TypeConstraintProp::newType(JSContext *cx, TypeSet *source, jstype type) { + UntrapOpcode untrap(cx, script, pc); + if (type == TYPE_UNKNOWN || (!TypeIsObject(type) && !script->global)) { /* * Access on an unknown object. Reads produce an unknown result, writes @@ -925,13 +969,24 @@ TypeConstraintProp::newType(JSContext *cx, TypeSet *source, jstype type) if (assign) cx->compartment->types.monitorBytecode(cx, script, pc - script->code); else - target->addType(cx, TYPE_UNKNOWN); + MarkPropertyAccessUnknown(cx, script, pc, target); + return; + } + + if (type == TYPE_LAZYARGS) { + /* Catch cases which will be accounted for by the followEscapingArguments analysis. */ + if (assign || (id != JSID_VOID && id != id_length(cx))) + return; + + if (id == JSID_VOID) + MarkPropertyAccessUnknown(cx, script, pc, target); + else + target->addType(cx, TYPE_INT32); return; } TypeObject *object = GetPropertyObject(cx, script, type); if (object) { - UntrapOpcode untrap(cx, script, pc); PropertyAccess(cx, script, pc, object, assign, target, id); if (!object->unknownProperties() && @@ -1072,7 +1127,7 @@ TypeConstraintCall::newType(JSContext *cx, TypeSet *source, jstype type) return; /* Analyze the function if we have not already done so. */ - if (!callee->analyzed) { + if (!callee->ranInference) { ScriptAnalysis *calleeAnalysis = callee->analysis(cx); if (!calleeAnalysis) { cx->compartment->types.setPendingNukeTypes(cx); @@ -1381,6 +1436,8 @@ GetValueTypeFromTypeFlags(TypeFlags flags) return JSVAL_TYPE_DOUBLE; case TYPE_FLAG_STRING: return JSVAL_TYPE_STRING; + case TYPE_FLAG_LAZYARGS: + return JSVAL_TYPE_MAGIC; default: return JSVAL_TYPE_UNKNOWN; } @@ -1541,6 +1598,43 @@ TypeSet::HasObjectFlags(JSContext *cx, TypeObject *object, TypeObjectFlags flags return false; } +void +FixLazyArguments(JSContext *cx, JSScript *script) +{ +#ifdef JS_METHODJIT + mjit::ExpandInlineFrames(cx, FRAME_EXPAND_ALL); +#endif + + /* :FIXME: handle OOM at calls here. */ + ScriptAnalysis *analysis = script->analysis(cx); + if (analysis && !analysis->ranBytecode()) + analysis->analyzeBytecode(cx); + if (!analysis || analysis->OOM()) + return; + + for (AllFramesIter iter(cx); !iter.done(); ++iter) { + StackFrame *fp = iter.fp(); + if (fp->isScriptFrame() && fp->script() == script) { + JSInlinedSite *inline_; + jsbytecode *pc = fp->pc(cx, NULL, &inline_); + JS_ASSERT(!inline_); + + /* + * Check locals and stack slots, assignment to individual arguments + * is treated as an escape on the arguments. + */ + Value *sp = fp->base() + analysis->getCode(pc).stackDepth; + for (Value *vp = fp->slots(); vp < sp; vp++) { + if (vp->isMagicCheck(JS_LAZY_ARGUMENTS)) { + if (!js_GetArgsValue(cx, fp, vp)) { + /* FIXME */ + } + } + } + } + } +} + static inline void ObjectStateChange(JSContext *cx, TypeObject *object, bool markingUnknown, bool force) { @@ -1552,8 +1646,18 @@ ObjectStateChange(JSContext *cx, TypeObject *object, bool markingUnknown, bool f if (!elementTypes) return; if (markingUnknown) { + JSScript *fixArgsScript = NULL; + if (!(object->flags & OBJECT_FLAG_CREATED_ARGUMENTS) && object->isFunction) { + TypeFunction *fun = object->asFunction(); + if (fun->script && fun->script->usedLazyArgs) + fixArgsScript = fun->script; + } + /* Mark as unknown after getting the element types, to avoid assertion. */ object->flags = OBJECT_FLAG_UNKNOWN_MASK; + + if (fixArgsScript) + FixLazyArguments(cx, fixArgsScript); } TypeConstraint *constraint = elementTypes->constraintList; @@ -2035,7 +2139,7 @@ TypeCompartment::dynamicPush(JSContext *cx, JSScript *script, uint32 offset, jst if (script->hasAnalysis() && script->analysis(cx)->ranInference()) { TypeSet *pushed = script->analysis(cx)->pushedTypes(offset, 0); pushed->addType(cx, type); - } else if (script->analyzed) { + } else if (script->ranInference) { /* Any new dynamic result triggers reanalysis and recompilation. */ ScriptAnalysis *analysis = script->analysis(cx); if (!analysis) { @@ -2754,8 +2858,18 @@ TypeObject::setFlags(JSContext *cx, TypeObjectFlags flags) JS_ASSERT(cx->compartment->activeInference); JS_ASSERT((this->flags & flags) != flags); + JSScript *fixArgsScript = NULL; + if ((flags & ~this->flags & OBJECT_FLAG_CREATED_ARGUMENTS) && isFunction) { + TypeFunction *fun = asFunction(); + if (fun->script && fun->script->usedLazyArgs) + fixArgsScript = fun->script; + } + this->flags |= flags; + if (fixArgsScript) + FixLazyArguments(cx, fixArgsScript); + InferSpew(ISpewOps, "%s: setFlags %u", name(), flags); ObjectStateChange(cx, this, false, false); @@ -3420,14 +3534,20 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset, break; } - case JSOP_ARGSUB: - pushed[0].addType(cx, TYPE_UNKNOWN); - break; - - case JSOP_ARGUMENTS: - case JSOP_ARGCNT: - pushed[0].addType(cx, TYPE_UNKNOWN); + case JSOP_ARGUMENTS: { + /* Compute a precise type only when we know the arguments won't escape. */ + TypeObject *funType = script->fun->getType(); + if (funType->unknownProperties() || funType->hasAnyFlags(OBJECT_FLAG_CREATED_ARGUMENTS)) { + pushed[0].addType(cx, TYPE_UNKNOWN); + break; + } + TypeSet *prop = funType->getProperty(cx, JSID_VOID, false); + if (!prop) + break; + prop->addLazyArguments(cx, script, &pushed[0]); + pushed[0].addType(cx, TYPE_LAZYARGS); break; + } case JSOP_SETPROP: case JSOP_SETMETHOD: { @@ -3886,7 +4006,7 @@ ScriptAnalysis::analyzeTypes(JSContext *cx) return; } - if (script->analyzed) { + if (script->ranInference) { /* * Reanalyzing this script after discarding from GC. * Discard/recompile any JIT code for this script, @@ -3896,7 +4016,7 @@ ScriptAnalysis::analyzeTypes(JSContext *cx) } /* Future OOM failures need to setPendingNukeTypes. */ - script->analyzed = true; + script->ranInference = true; /* * Set this early to avoid reentrance. Any failures are OOMs, and will nuke @@ -3946,6 +4066,126 @@ ScriptAnalysis::analyzeTypes(JSContext *cx) result->replay(cx, script); result = result->next; } + + if (!script->usesArguments) + return; + + /* + * Do additional analysis to determine whether the arguments object in the + * script can escape. + */ + + if (script->fun->getType()->hasAnyFlags(types::OBJECT_FLAG_CREATED_ARGUMENTS)) + return; + + /* + * Note: don't check for strict mode code here, even though arguments + * accesses in such scripts will always be deoptimized. These scripts can + * have a JSOP_ARGUMENTS in their prologue which the usesArguments check + * above does not account for. We filter in the interpreter and JITs + * themselves. + */ + if (script->fun->isHeavyweight() || cx->compartment->debugMode) { + cx->markTypeObjectFlags(script->fun->getType(), + types::OBJECT_FLAG_CREATED_ARGUMENTS); + return; + } + + offset = 0; + while (offset < script->length) { + Bytecode *code = maybeCode(offset); + jsbytecode *pc = script->code + offset; + + if (code && JSOp(*pc) == JSOP_ARGUMENTS) { + Vector seen(cx); + if (!followEscapingArguments(cx, SSAValue::PushedValue(offset, 0), &seen)) { + cx->markTypeObjectFlags(script->fun->getType(), + types::OBJECT_FLAG_CREATED_ARGUMENTS); + return; + } + } + + offset += GetBytecodeLength(pc); + } + + /* + * The VM is now free to use the arguments in this script lazily. If we end + * up creating an arguments object for the script in the future or regard + * the arguments as escaping, we need to walk the stack and replace lazy + * arguments objects with actual arguments objects. + */ + script->usedLazyArgs = true; +} + +bool +ScriptAnalysis::followEscapingArguments(JSContext *cx, const SSAValue &v, Vector *seen) +{ + /* + * trackUseChain is false for initial values of variables, which + * cannot hold the script's arguments object. + */ + if (!trackUseChain(v)) + return true; + + for (unsigned i = 0; i < seen->length(); i++) { + if (v.equals((*seen)[i])) + return true; + } + if (!seen->append(v)) { + cx->compartment->types.setPendingNukeTypes(cx); + return false; + } + + SSAUseChain *use = useChain(v); + while (use) { + if (!followEscapingArguments(cx, use, seen)) + return false; + use = use->next; + } + + return true; +} + +bool +ScriptAnalysis::followEscapingArguments(JSContext *cx, SSAUseChain *use, Vector *seen) +{ + if (!use->popped) { + for (unsigned i = 0; i < use->u.phi->length; i++) { + const SSAValue &v = use->u.phi->options[i]; + if (!followEscapingArguments(cx, v, seen)) + return false; + } + return true; + } + + jsbytecode *pc = script->code + use->offset; + uint32 which = use->u.which; + + /* Allow GETELEM and LENGTH on arguments objects that don't escape. */ + + /* + * Note: if the element index is not an integer we will mark the arguments + * as escaping at the access site. + */ + if (JSOp(*pc) == JSOP_GETELEM && which == 1) + return true; + + if (JSOp(*pc) == JSOP_LENGTH) + return true; + + /* Allow assignments to non-closed locals (but not arguments). */ + + if (JSOp(*pc) == JSOP_SETLOCAL) { + uint32 slot = GetBytecodeSlot(script, pc); + if (slotEscapes(slot)) + return false; + return followEscapingArguments(cx, SSAValue::WrittenVar(slot, use->offset), seen); + } + + if (JSOp(*pc) == JSOP_GETLOCAL) + return followEscapingArguments(cx, SSAValue::PushedValue(use->offset, 0), seen); + + return false; } void @@ -4329,7 +4569,7 @@ ScriptAnalysis::printTypes(JSContext *cx) } unsigned typeCount = types->getObjectCount() ? 1 : 0; - for (jstype type = TYPE_UNDEFINED; type <= TYPE_STRING; type++) { + for (jstype type = TYPE_UNDEFINED; type < TYPE_UNKNOWN; type++) { if (types->hasAnyFlag(1 << type)) typeCount++; } @@ -4590,7 +4830,7 @@ JSScript::typeCheckBytecode(JSContext *cx, const jsbytecode *pc, const js::Value jstype type = GetValueType(cx, val); - if (!TypeSetMatches(cx, types, type)) { + if (!types::TypeMatches(cx, types, type)) { TypeFailure(cx, "Missing type at #%u:%05u pushed %u: %s", id(), pc - code, i, TypeString(type)); } diff --git a/js/src/jsinfer.h b/js/src/jsinfer.h index 04032012f69..7e9af872ca8 100644 --- a/js/src/jsinfer.h +++ b/js/src/jsinfer.h @@ -76,12 +76,13 @@ const jstype TYPE_BOOLEAN = 3; const jstype TYPE_INT32 = 4; const jstype TYPE_DOUBLE = 5; const jstype TYPE_STRING = 6; +const jstype TYPE_LAZYARGS = 7; /* * Aggregate unknown type, could be anything. Typically used when a type set * becomes polymorphic, or when accessing an object with unknown properties. */ -const jstype TYPE_UNKNOWN = 7; +const jstype TYPE_UNKNOWN = 8; /* * Test whether a type is an primitive or an object. Object types can be @@ -218,37 +219,38 @@ enum { TYPE_FLAG_INT32 = 1 << TYPE_INT32, TYPE_FLAG_DOUBLE = 1 << TYPE_DOUBLE, TYPE_FLAG_STRING = 1 << TYPE_STRING, + TYPE_FLAG_LAZYARGS = 1 << TYPE_LAZYARGS, TYPE_FLAG_UNKNOWN = 1 << TYPE_UNKNOWN, /* Flag for type sets which are cleared on GC. */ - TYPE_FLAG_INTERMEDIATE_SET = 0x0100, + TYPE_FLAG_INTERMEDIATE_SET = 0x0200, /* Flags for type sets which are on object properties. */ /* Whether this property has ever been directly written. */ - TYPE_FLAG_OWN_PROPERTY = 0x0200, + TYPE_FLAG_OWN_PROPERTY = 0x0400, /* * Whether the property has ever been deleted or reconfigured to behave * differently from a normal native property (e.g. made non-writable or * given a scripted getter or setter). */ - TYPE_FLAG_CONFIGURED_PROPERTY = 0x0400, + TYPE_FLAG_CONFIGURED_PROPERTY = 0x0800, /* * Whether the property is definitely in a particular inline slot on all * objects from which it has not been deleted or reconfigured. Implies * OWN_PROPERTY and unlike OWN/CONFIGURED property, this cannot change. */ - TYPE_FLAG_DEFINITE_PROPERTY = 0x0800, + TYPE_FLAG_DEFINITE_PROPERTY = 0x08000, /* If the property is definite, mask and shift storing the slot. */ - TYPE_FLAG_DEFINITE_MASK = 0xf000, - TYPE_FLAG_DEFINITE_SHIFT = 12, + TYPE_FLAG_DEFINITE_MASK = 0xf0000, + TYPE_FLAG_DEFINITE_SHIFT = 16, /* Mask of non-type flags on a type set. */ - TYPE_FLAG_BASE_MASK = 0xffffff00 + TYPE_FLAG_BASE_MASK = 0xffffffff ^ ((TYPE_FLAG_UNKNOWN << 1) - 1) }; typedef uint32 TypeFlags; @@ -272,14 +274,17 @@ enum { /* Whether any objects this represents are not packed arrays. */ OBJECT_FLAG_NON_PACKED_ARRAY = 1 << 1, - /* Whether any objects this represents have had their .arguments accessed. */ - OBJECT_FLAG_UNINLINEABLE = 1 << 2, + /* Whether any represented script has had arguments objects created. */ + OBJECT_FLAG_CREATED_ARGUMENTS = 1 << 2, - /* Whether any objects this represents have an equality hook. */ - OBJECT_FLAG_SPECIAL_EQUALITY = 1 << 3, + /* Whether any represented script is considered uninlineable. */ + OBJECT_FLAG_UNINLINEABLE = 1 << 3, - /* Whether any objects this represents have been iterated over. */ - OBJECT_FLAG_ITERATED = 1 << 4 + /* Whether any objects have an equality hook. */ + OBJECT_FLAG_SPECIAL_EQUALITY = 1 << 4, + + /* Whether any objects have been iterated over. */ + OBJECT_FLAG_ITERATED = 1 << 5 }; typedef uint32 TypeObjectFlags; @@ -370,6 +375,7 @@ class TypeSet void addFilterPrimitives(JSContext *cx, JSScript *script, TypeSet *target, bool onlyNullVoid); void addSubsetBarrier(JSContext *cx, JSScript *script, jsbytecode *pc, TypeSet *target); + void addLazyArguments(JSContext *cx, JSScript *script, TypeSet *target); void addBaseSubset(JSContext *cx, TypeObject *object, TypeSet *target); bool addCondensed(JSContext *cx, JSScript *script); @@ -393,6 +399,8 @@ class TypeSet /* Get any type tag which all values in this set must have. */ JSValueType getKnownTypeTag(JSContext *cx); + bool isLazyArguments(JSContext *cx) { return getKnownTypeTag(cx) == JSVAL_TYPE_MAGIC; } + /* Whether the type set or a particular object has any of a set of flags. */ bool hasObjectFlags(JSContext *cx, TypeObjectFlags flags); static bool HasObjectFlags(JSContext *cx, TypeObject *object, TypeObjectFlags flags); @@ -783,6 +791,12 @@ struct TypeCompartment /* Object to use throughout the compartment as the default type of objects with no prototype. */ TypeObject typeEmpty; + /* + * Placeholder object added in type sets throughout the compartment to + * represent lazy arguments objects. + */ + TypeObject typeLazyArguments; + /* * Bit set if all current types must be marked as unknown, and all scripts * recompiled. Caused by OOM failure within inference operations. @@ -903,6 +917,13 @@ enum SpewChannel { void InferSpew(SpewChannel which, const char *fmt, ...); const char * TypeString(jstype type); +/* + * Check that a type set contains value. Unlike TypeSet::hasType, this returns + * true if type has had its prototype mutated and another object with unknown + * properties is in the type set. + */ +bool TypeMatches(JSContext *cx, TypeSet *types, jstype type); + /* Check that the type property for id in obj contains value. */ bool TypeHasProperty(JSContext *cx, TypeObject *obj, jsid id, const Value &value); diff --git a/js/src/jsinferinlines.h b/js/src/jsinferinlines.h index 1daaea3fd34..f4c8fe2f19e 100644 --- a/js/src/jsinferinlines.h +++ b/js/src/jsinferinlines.h @@ -73,6 +73,14 @@ GetValueType(JSContext *cx, const Value &val) return TYPE_STRING; case JSVAL_TYPE_NULL: return TYPE_NULL; + case JSVAL_TYPE_MAGIC: + switch (val.whyMagic()) { + case JS_LAZY_ARGUMENTS: + return TYPE_LAZYARGS; + default: + JS_NOT_REACHED("Unknown value"); + return (jstype) 0; + } case JSVAL_TYPE_OBJECT: { JSObject *obj = &val.toObject(); JS_ASSERT(obj->type); @@ -752,7 +760,7 @@ JSScript::typeSetNewCalled(JSContext *cx) * of 'this' when the script is analyzed or reanalyzed after an invoke with 'new', * and if 'new' is first invoked after the script has already been analyzed. */ - if (analyzed) { + if (ranInference) { /* Regenerate types for the function. */ js::types::AutoEnterTypeInference enter(cx); js::analyze::ScriptAnalysis *analysis = this->analysis(cx); diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index 936b9aa05a5..a7c93e04880 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -4117,6 +4117,10 @@ BEGIN_CASE(JSOP_LENGTH) rval = Int32Value(vp->toString()->length()); break; } + if (vp->isMagic(JS_LAZY_ARGUMENTS)) { + rval = Int32Value(regs.fp()->numActualArgs()); + break; + } if (vp->isObject()) { JSObject *obj = &vp->toObject(); if (obj->isArray()) { @@ -4459,6 +4463,19 @@ BEGIN_CASE(JSOP_GETELEM) } } + if (lref.isMagic(JS_LAZY_ARGUMENTS)) { + if (rref.isInt32() && size_t(rref.toInt32()) < regs.fp()->numActualArgs()) { + regs.sp--; + regs.sp[-1] = regs.fp()->canonicalActualArg(rref.toInt32()); + script->typeMonitor(cx, regs.pc, regs.sp[-1]); + len = JSOP_GETELEM_LENGTH; + DO_NEXT_OP(len); + } + cx->markTypeObjectFlags(script->fun->getType(), + types::OBJECT_FLAG_CREATED_ARGUMENTS); + JS_ASSERT(!lref.isMagic(JS_LAZY_ARGUMENTS)); + } + JSObject *obj; VALUE_TO_OBJECT(cx, &lref, obj); @@ -5157,8 +5174,24 @@ BEGIN_CASE(JSOP_TRAP) BEGIN_CASE(JSOP_ARGUMENTS) { Value rval; - if (!js_GetArgsValue(cx, regs.fp(), &rval)) - goto error; + if (cx->typeInferenceEnabled() && !script->strictModeCode) { + analyze::ScriptAnalysis *analysis = script->analysis(cx); + if (analysis && !analysis->ranInference()) { + AutoEnterTypeInference enter(cx); + analysis->analyzeTypes(cx); + } + if (!analysis || analysis->OOM()) + goto error; + if (script->fun->getType()->hasAnyFlags(types::OBJECT_FLAG_CREATED_ARGUMENTS)) { + if (!js_GetArgsValue(cx, regs.fp(), &rval)) + goto error; + } else { + rval = MagicValue(JS_LAZY_ARGUMENTS); + } + } else { + if (!js_GetArgsValue(cx, regs.fp(), &rval)) + goto error; + } PUSH_COPY(rval); } END_CASE(JSOP_ARGUMENTS) diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 0e3929532ed..768e69e1c1b 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -465,7 +465,8 @@ struct JSScript { bool isCachedEval:1; /* script came from eval(), and is in eval cache */ bool isUncachedEval:1; /* script came from EvaluateScript */ bool calledWithNew:1; /* script has been called using 'new' */ - bool analyzed:1; /* script has been analyzed by type inference */ + bool usedLazyArgs:1; /* script has used lazy arguments at some point */ + bool ranInference:1; /* script has been analyzed by type inference */ #ifdef JS_METHODJIT bool debugMode:1; /* script was compiled in debug mode */ bool singleStepMode:1; /* compile script in single-step mode */ diff --git a/js/src/jsval.h b/js/src/jsval.h index 44148a5a45a..64c812ddbcc 100644 --- a/js/src/jsval.h +++ b/js/src/jsval.h @@ -266,6 +266,7 @@ typedef enum JSWhyMagic JS_THIS_POISON, /* used in debug builds to catch tracing errors */ JS_ARG_POISON, /* used in debug builds to catch tracing errors */ JS_SERIALIZE_NO_NODE, /* an empty subnode in the AST serializer */ + JS_LAZY_ARGUMENTS, /* lazy arguments value on the stack */ JS_GENERIC_MAGIC /* for local use */ } JSWhyMagic; diff --git a/js/src/jsvalue.h b/js/src/jsvalue.h index 3b1969628f2..5a588efa540 100644 --- a/js/src/jsvalue.h +++ b/js/src/jsvalue.h @@ -539,6 +539,11 @@ class Value return JSVAL_IS_MAGIC_IMPL(data); } + JS_ALWAYS_INLINE + bool isMagicCheck(JSWhyMagic why) const { + return isMagic() && data.s.payload.why == why; + } + #if JS_BITS_PER_WORD == 64 JS_ALWAYS_INLINE bool hasPtrPayload() const { @@ -557,13 +562,11 @@ class Value return JSVAL_TRACE_KIND_IMPL(data); } -#ifdef DEBUG JS_ALWAYS_INLINE JSWhyMagic whyMagic() const { JS_ASSERT(isMagic()); return data.s.payload.why; } -#endif /*** Comparison ***/ diff --git a/js/src/methodjit/BaseAssembler.h b/js/src/methodjit/BaseAssembler.h index 549afe5ba7c..b4fa99993cf 100644 --- a/js/src/methodjit/BaseAssembler.h +++ b/js/src/methodjit/BaseAssembler.h @@ -785,6 +785,20 @@ static const JSC::MacroAssembler::RegisterID JSParamReg_Argc = JSC::SparcRegist add32(Imm32(delta), key.reg()); } + void loadFrameActuals(JSFunction *fun, RegisterID reg) { + /* Bias for the case where there was an arguments overflow. */ + load32(Address(JSFrameReg, StackFrame::offsetOfArgs()), reg); + add32(Imm32(fun->nargs + 2), reg); + Jump overflowArgs = branchTest32(Assembler::NonZero, + Address(JSFrameReg, StackFrame::offsetOfFlags()), + Imm32(StackFrame::OVERFLOW_ARGS)); + move(Imm32(fun->nargs), reg); + overflowArgs.linkTo(label(), this); + lshift32(Imm32(3), reg); + neg32(reg); + addPtr(JSFrameReg, reg); + } + void loadObjClass(RegisterID objReg, RegisterID destReg) { loadPtr(Address(objReg, offsetof(JSObject, clasp)), destReg); } diff --git a/js/src/methodjit/Compiler.cpp b/js/src/methodjit/Compiler.cpp index 9fc695462b7..017c45aa786 100644 --- a/js/src/methodjit/Compiler.cpp +++ b/js/src/methodjit/Compiler.cpp @@ -787,6 +787,22 @@ mjit::Compiler::generatePrologue() masm.storePtr(t0, Address(JSFrameReg, StackFrame::offsetOfScopeChain())); hasScope.linkTo(masm.label(), &masm); } + + if (outerScript->usesArguments && !script->fun->isHeavyweight()) { + /* + * Make sure that fp->args.nactual is always coherent. This may be + * inspected directly by JIT code, and is not guaranteed to be + * correct if the UNDERFLOW and OVERFLOW flags are not set. + */ + Jump hasArgs = masm.branchTest32(Assembler::NonZero, FrameFlagsAddress(), + Imm32(StackFrame::OVERRIDE_ARGS | + StackFrame::UNDERFLOW_ARGS | + StackFrame::OVERFLOW_ARGS | + StackFrame::HAS_ARGS_OBJ)); + masm.store32(Imm32(script->fun->nargs), + Address(JSFrameReg, StackFrame::offsetOfArgs())); + hasArgs.linkTo(masm.label(), &masm); + } } if (isConstructing) @@ -1619,11 +1635,16 @@ mjit::Compiler::generateMethod() * 'apply' actually refers to js_fun_apply. If this is not true, * the slow path in JSOP_FUNAPPLY will create the args object. */ - if (canUseApplyTricks()) + if (canUseApplyTricks()) { applyTricks = LazyArgsObj; - else - jsop_arguments(); - pushSyncedEntry(0); + pushSyncedEntry(0); + } else if (cx->typeInferenceEnabled() && !script->strictModeCode && + !script->fun->getType()->hasAnyFlags(types::OBJECT_FLAG_CREATED_ARGUMENTS)) { + frame.push(MagicValue(JS_LAZY_ARGUMENTS)); + } else { + jsop_arguments(REJOIN_FALLTHROUGH); + pushSyncedEntry(0); + } END_CASE(JSOP_ARGUMENTS) BEGIN_CASE(JSOP_FORARG) @@ -3401,7 +3422,7 @@ mjit::Compiler::inlineCallHelper(uint32 callImmArgc, bool callingNew, FrameSize #endif if (applyTricks == LazyArgsObj) { /* frame.pop() above reset us to pre-JSOP_ARGUMENTS state */ - jsop_arguments(); + jsop_arguments(REJOIN_RESUME); frame.pushSynced(JSVAL_TYPE_UNKNOWN); } emitUncachedCall(callImmArgc, callingNew); @@ -4170,19 +4191,8 @@ mjit::Compiler::jsop_getprop(JSAtom *atom, JSValueType knownType, return true; } - /* If the incoming type will never PIC, take slow path. */ - if (top->isNotType(JSVAL_TYPE_OBJECT)) { - jsop_getprop_slow(atom, usePropCache); - return true; - } - - frame.forgetMismatchedObject(top); - if (JSOp(*PC) == JSOP_LENGTH && cx->typeInferenceEnabled() && !hasTypeBarriers(PC)) { - /* - * Check if this is an array we can make a loop invariant entry for. - * This will fail for objects which are not definitely dense arrays. - */ + /* Check if this is an array we can make a loop invariant entry for. */ if (loop && loop->generatingInvariants()) { CrossSSAValue topv(a->inlineIndex, analysis->poppedValue(PC, 0)); FrameEntry *fe = loop->invariantLength(topv); @@ -4194,12 +4204,13 @@ mjit::Compiler::jsop_getprop(JSAtom *atom, JSValueType knownType, } } + types::TypeSet *types = analysis->poppedTypes(PC, 0); + /* * Check if we are accessing the 'length' property of a known dense array. * Note that if the types are known to indicate dense arrays, their lengths * must fit in an int32. */ - types::TypeSet *types = analysis->poppedTypes(PC, 0); if (!types->hasObjectFlags(cx, types::OBJECT_FLAG_NON_DENSE_ARRAY)) { bool isObject = top->isTypeKnown(); if (!isObject) { @@ -4215,8 +4226,27 @@ mjit::Compiler::jsop_getprop(JSAtom *atom, JSValueType knownType, stubcc.rejoin(Changes(1)); return true; } + + /* + * Check if we are accessing the 'length' of the lazy arguments for the + * current frame. No actual arguments object has ever been constructed + * for the script, so we can go straight to nactual. + */ + if (types->isLazyArguments(cx)) { + frame.pop(); + frame.push(Address(JSFrameReg, StackFrame::offsetOfArgs()), JSVAL_TYPE_INT32); + return true; + } } + /* If the incoming type will never PIC, take slow path. */ + if (top->isNotType(JSVAL_TYPE_OBJECT)) { + jsop_getprop_slow(atom, usePropCache); + return true; + } + + frame.forgetMismatchedObject(top); + /* Check if this is a property access we can make a loop invariant entry for. */ if (loop && loop->generatingInvariants() && !hasTypeBarriers(PC)) { CrossSSAValue topv(a->inlineIndex, analysis->poppedValue(PC, 0)); @@ -6307,10 +6337,10 @@ mjit::Compiler::emitEval(uint32 argc) } void -mjit::Compiler::jsop_arguments() +mjit::Compiler::jsop_arguments(RejoinState rejoin) { prepareStubCall(Uses(0)); - INLINE_STUBCALL(stubs::Arguments, REJOIN_NONE); + INLINE_STUBCALL(stubs::Arguments, rejoin); } bool diff --git a/js/src/methodjit/Compiler.h b/js/src/methodjit/Compiler.h index 86804b98a34..cde53b6462c 100644 --- a/js/src/methodjit/Compiler.h +++ b/js/src/methodjit/Compiler.h @@ -654,7 +654,7 @@ class Compiler : public BaseCompiler void enterBlock(JSObject *obj); void leaveBlock(); void emitEval(uint32 argc); - void jsop_arguments(); + void jsop_arguments(RejoinState rejoin); bool jsop_tableswitch(jsbytecode *pc); void jsop_forprop(JSAtom *atom); void jsop_forname(JSAtom *atom); @@ -707,6 +707,7 @@ class Compiler : public BaseCompiler bool jsop_setelem(bool popGuaranteed); bool jsop_getelem(bool isCall); void jsop_getelem_dense(bool isPacked); + void jsop_getelem_args(); bool isCacheableBaseAndIndex(FrameEntry *obj, FrameEntry *id); void jsop_stricteq(JSOp op); bool jsop_equality(JSOp op, BoolStub stub, jsbytecode *target, JSOp fused); diff --git a/js/src/methodjit/FastOps.cpp b/js/src/methodjit/FastOps.cpp index 087059a7b54..4a447d86d5a 100644 --- a/js/src/methodjit/FastOps.cpp +++ b/js/src/methodjit/FastOps.cpp @@ -1110,7 +1110,7 @@ mjit::Compiler::jsop_setelem_dense() loop->hoistArrayLengthCheck(objv, indexv); if (hoisted) { - FrameEntry *slotsFe = loop->invariantSlots(objv); + FrameEntry *slotsFe = loop->invariantArraySlots(objv); slotsReg = frame.tempRegForData(slotsFe); frame.unpinEntry(vr); @@ -1375,8 +1375,6 @@ mjit::Compiler::jsop_setelem(bool popGuaranteed) static inline bool IsCacheableGetElem(FrameEntry *obj, FrameEntry *id) { - if (obj->isTypeKnown() && obj->getKnownType() != JSVAL_TYPE_OBJECT) - return false; if (id->isTypeKnown() && !(id->getKnownType() == JSVAL_TYPE_INT32 #if defined JS_POLYIC @@ -1435,7 +1433,7 @@ mjit::Compiler::jsop_getelem_dense(bool isPacked) // we are hoisting the bounds check. RegisterID baseReg; if (hoisted) { - FrameEntry *slotsFe = loop->invariantSlots(objv); + FrameEntry *slotsFe = loop->invariantArraySlots(objv); baseReg = frame.tempRegForData(slotsFe); } else { baseReg = frame.tempRegForData(obj); @@ -1521,6 +1519,78 @@ mjit::Compiler::jsop_getelem_dense(bool isPacked) finishBarrier(barrier, REJOIN_FALLTHROUGH, 0); } +void +mjit::Compiler::jsop_getelem_args() +{ + FrameEntry *id = frame.peek(-1); + + // Test for integer index. + if (!id->isTypeKnown()) { + Jump guard = frame.testInt32(Assembler::NotEqual, id); + stubcc.linkExit(guard, Uses(2)); + } + + // Allocate registers. + + analyze::CrossSSAValue indexv(a->inlineIndex, analysis->poppedValue(PC, 0)); + bool hoistedLength = loop && id->isType(JSVAL_TYPE_INT32) && + loop->hoistArgsLengthCheck(indexv); + FrameEntry *actualsFe = loop ? loop->invariantArguments() : NULL; + + Int32Key key = id->isConstant() + ? Int32Key::FromConstant(id->getValue().toInt32()) + : Int32Key::FromRegister(frame.tempRegForData(id)); + if (!key.isConstant()) + frame.pinReg(key.reg()); + + RegisterID dataReg = frame.allocReg(); + RegisterID typeReg = frame.allocReg(); + + if (!key.isConstant()) + frame.unpinReg(key.reg()); + + // Guard on nactual. + if (!hoistedLength) { + Address nactualAddr(JSFrameReg, StackFrame::offsetOfArgs()); + MaybeJump rangeGuard; + if (key.isConstant()) { + JS_ASSERT(key.index() >= 0); + rangeGuard = masm.branch32(Assembler::BelowOrEqual, nactualAddr, Imm32(key.index())); + } else { + rangeGuard = masm.branch32(Assembler::BelowOrEqual, nactualAddr, key.reg()); + } + stubcc.linkExit(rangeGuard.get(), Uses(2)); + } + + RegisterID actualsReg; + if (actualsFe) { + actualsReg = frame.tempRegForData(actualsFe); + } else { + actualsReg = dataReg; + masm.loadFrameActuals(outerScript->fun, actualsReg); + } + + if (key.isConstant()) { + Address arg(actualsReg, key.index() * sizeof(Value)); + masm.loadValueAsComponents(arg, typeReg, dataReg); + } else { + JS_ASSERT(key.reg() != dataReg); + BaseIndex arg(actualsReg, key.reg(), masm.JSVAL_SCALE); + masm.loadValueAsComponents(arg, typeReg, dataReg); + } + + stubcc.leave(); + OOL_STUBCALL(stubs::GetElem, REJOIN_FALLTHROUGH); + + frame.popn(2); + frame.pushRegs(typeReg, dataReg, knownPushedType(0)); + BarrierState barrier = testBarrier(typeReg, dataReg, false); + + stubcc.rejoin(Changes(2)); + + finishBarrier(barrier, REJOIN_FALLTHROUGH, 0); +} + bool mjit::Compiler::jsop_getelem(bool isCall) { @@ -1535,11 +1605,13 @@ mjit::Compiler::jsop_getelem(bool isCall) return true; } - frame.forgetMismatchedObject(obj); - - if (cx->typeInferenceEnabled()) { + if (cx->typeInferenceEnabled() && id->mightBeType(JSVAL_TYPE_INT32) && !isCall) { types::TypeSet *types = analysis->poppedTypes(PC, 1); - if (!isCall && id->mightBeType(JSVAL_TYPE_INT32) && + if (types->isLazyArguments(cx) && !outerScript->analysis(cx)->modifiesArguments()) { + jsop_getelem_args(); + return true; + } + if (obj->mightBeType(JSVAL_TYPE_OBJECT) && !types->hasObjectFlags(cx, types::OBJECT_FLAG_NON_DENSE_ARRAY) && !arrayPrototypeHasIndexedProperty()) { // this is definitely a dense array, generate code directly without @@ -1550,6 +1622,8 @@ mjit::Compiler::jsop_getelem(bool isCall) } } + frame.forgetMismatchedObject(obj); + GetElementICInfo ic = GetElementICInfo(JSOp(*PC)); // Pin the top of the stack to avoid spills, before allocating registers. diff --git a/js/src/methodjit/LoopState.cpp b/js/src/methodjit/LoopState.cpp index f94e4ba7415..0180274687d 100644 --- a/js/src/methodjit/LoopState.cpp +++ b/js/src/methodjit/LoopState.cpp @@ -506,8 +506,8 @@ bool LoopState::hoistArrayLengthCheck(const CrossSSAValue &obj, const CrossSSAValue &index) { /* - * Note: this method requires that obj is either a dense array or not an - * object, and that the index is definitely an integer. + * Note: this method requires that the index is definitely an integer, and + * that obj is either a dense array or not an object. */ if (skipAnalysis) return false; @@ -628,6 +628,58 @@ LoopState::hoistArrayLengthCheck(const CrossSSAValue &obj, const CrossSSAValue & return false; } +bool +LoopState::hoistArgsLengthCheck(const CrossSSAValue &index) +{ + if (skipAnalysis) + return false; + + JaegerSpew(JSpew_Analysis, "Trying to hoist argument range check\n"); + + uint32 indexSlot; + int32 indexConstant; + if (!getEntryValue(index, &indexSlot, &indexConstant)) { + JaegerSpew(JSpew_Analysis, "Could not compute index in terms of loop entry state\n"); + return false; + } + + /* + * We only hoist arguments checks which can be completely eliminated, for + * now just tests with 'i < arguments.length' or similar in the condition. + */ + + if (indexSlot == UNASSIGNED || loopInvariantEntry(indexSlot)) { + JaegerSpew(JSpew_Analysis, "Index is constant or loop invariant\n"); + return false; + } + + if (!outerAnalysis->liveness(indexSlot).nonDecreasing(outerScript, lifetime)) { + JaegerSpew(JSpew_Analysis, "Index may decrease in future iterations\n"); + return false; + } + + if (indexSlot == testLHS && indexConstant == 0 && testConstant == -1 && testLessEqual) { + bool found = false; + for (unsigned i = 0; i < invariantEntries.length(); i++) { + const InvariantEntry &entry = invariantEntries[i]; + if (entry.kind == InvariantEntry::INVARIANT_ARGS_LENGTH) { + uint32 slot = frame.outerSlot(frame.getTemporary(entry.u.array.temporary)); + if (slot == testRHS) + found = true; + break; + } + } + if (found) { + addNegativeCheck(indexSlot, indexConstant); + JaegerSpew(JSpew_Analysis, "Access implied by loop test\n"); + return true; + } + } + + JaegerSpew(JSpew_Analysis, "No match found\n"); + return false; +} + bool LoopState::hasTestLinearRelationship(uint32 slot) { @@ -667,8 +719,10 @@ LoopState::hasTestLinearRelationship(uint32 slot) } FrameEntry * -LoopState::invariantSlots(const CrossSSAValue &obj) +LoopState::invariantArraySlots(const CrossSSAValue &obj) { + JS_ASSERT(!skipAnalysis); + uint32 objSlot; int32 objConstant; if (!getEntryValue(obj, &objSlot, &objConstant) || objConstant != 0) { @@ -689,6 +743,33 @@ LoopState::invariantSlots(const CrossSSAValue &obj) return NULL; } +FrameEntry * +LoopState::invariantArguments() +{ + if (skipAnalysis) + return NULL; + + for (unsigned i = 0; i < invariantEntries.length(); i++) { + InvariantEntry &entry = invariantEntries[i]; + if (entry.kind == InvariantEntry::INVARIANT_ARGS_BASE) + return frame.getTemporary(entry.u.array.temporary); + } + + uint32 which = frame.allocTemporary(); + if (which == uint32(-1)) + return NULL; + FrameEntry *fe = frame.getTemporary(which); + + InvariantEntry entry; + entry.kind = InvariantEntry::INVARIANT_ARGS_BASE; + entry.u.array.temporary = which; + invariantEntries.append(entry); + + JaegerSpew(JSpew_Analysis, "Using %s for loop invariant args base\n", + frame.entryName(fe)); + return fe; +} + FrameEntry * LoopState::invariantLength(const CrossSSAValue &obj) { @@ -699,6 +780,32 @@ LoopState::invariantLength(const CrossSSAValue &obj) int32 objConstant; if (!getEntryValue(obj, &objSlot, &objConstant) || objConstant != 0) return NULL; + TypeSet *objTypes = ssa->getValueTypes(obj); + + /* Check for 'length' on the lazy arguments for the current frame. */ + if (objTypes->isLazyArguments(cx)) { + JS_ASSERT(obj.frame == CrossScriptSSA::OUTER_FRAME); + + for (unsigned i = 0; i < invariantEntries.length(); i++) { + InvariantEntry &entry = invariantEntries[i]; + if (entry.kind == InvariantEntry::INVARIANT_ARGS_LENGTH) + return frame.getTemporary(entry.u.array.temporary); + } + + uint32 which = frame.allocTemporary(); + if (which == uint32(-1)) + return NULL; + FrameEntry *fe = frame.getTemporary(which); + + InvariantEntry entry; + entry.kind = InvariantEntry::INVARIANT_ARGS_LENGTH; + entry.u.array.temporary = which; + invariantEntries.append(entry); + + JaegerSpew(JSpew_Analysis, "Using %s for loop invariant args length\n", + frame.entryName(fe)); + return fe; + } for (unsigned i = 0; i < invariantEntries.length(); i++) { InvariantEntry &entry = invariantEntries[i]; @@ -711,7 +818,6 @@ LoopState::invariantLength(const CrossSSAValue &obj) if (!loopInvariantEntry(objSlot)) return NULL; - TypeSet *objTypes = ssa->getValueTypes(obj); if (objTypes->hasObjectFlags(cx, OBJECT_FLAG_NON_DENSE_ARRAY)) return NULL; @@ -1240,6 +1346,20 @@ LoopState::restoreInvariants(jsbytecode *pc, Assembler &masm, break; } + case InvariantEntry::INVARIANT_ARGS_BASE: { + Address address = frame.addressOf(frame.getTemporary(entry.u.array.temporary)); + masm.loadFrameActuals(outerScript->fun, T0); + masm.storePtr(T0, address); + break; + } + + case InvariantEntry::INVARIANT_ARGS_LENGTH: { + Address address = frame.addressOf(frame.getTemporary(entry.u.array.temporary)); + masm.load32(Address(JSFrameReg, StackFrame::offsetOfArgs()), T0); + masm.storeValueFromComponents(ImmType(JSVAL_TYPE_INT32), T0, address); + break; + } + case InvariantEntry::INVARIANT_PROPERTY: { uint32 object = entry.u.property.objectSlot; Jump notObject = masm.testObject(Assembler::NotEqual, frame.addressOf(object)); diff --git a/js/src/methodjit/LoopState.h b/js/src/methodjit/LoopState.h index 4b09042f333..55c3ce0e61b 100644 --- a/js/src/methodjit/LoopState.h +++ b/js/src/methodjit/LoopState.h @@ -172,9 +172,15 @@ class LoopState : public MacroAssemblerTypedefs /* constant >= value1 + value2 */ RANGE_CHECK, + /* For dense arrays */ INVARIANT_SLOTS, INVARIANT_LENGTH, + /* For lazy arguments */ + INVARIANT_ARGS_BASE, + INVARIANT_ARGS_LENGTH, + + /* For definite properties */ INVARIANT_PROPERTY } kind; union { @@ -276,7 +282,12 @@ class LoopState : public MacroAssemblerTypedefs */ bool hoistArrayLengthCheck(const analyze::CrossSSAValue &obj, const analyze::CrossSSAValue &index); - FrameEntry *invariantSlots(const analyze::CrossSSAValue &obj); + FrameEntry *invariantArraySlots(const analyze::CrossSSAValue &obj); + + /* Methods for accesses on lazy arguments. */ + bool hoistArgsLengthCheck(const analyze::CrossSSAValue &index); + FrameEntry *invariantArguments(); + FrameEntry *invariantLength(const analyze::CrossSSAValue &obj); FrameEntry *invariantProperty(const analyze::CrossSSAValue &obj, jsid id); diff --git a/js/src/methodjit/MethodJIT.cpp b/js/src/methodjit/MethodJIT.cpp index 414f485c777..950c32f2091 100644 --- a/js/src/methodjit/MethodJIT.cpp +++ b/js/src/methodjit/MethodJIT.cpp @@ -874,6 +874,8 @@ mjit::EnterMethodJIT(JSContext *cx, StackFrame *fp, void *code, Value *stackLimi FrameRegs &oldRegs = cx->regs(); fp->scopeChain(); + if (fp->isFunctionFrame() && fp->script()->usesArguments) + fp->ensureCoherentArgCount(); JSBool ok; { diff --git a/js/src/methodjit/PolyIC.cpp b/js/src/methodjit/PolyIC.cpp index bbc61eb07d6..4fbe83ff8be 100644 --- a/js/src/methodjit/PolyIC.cpp +++ b/js/src/methodjit/PolyIC.cpp @@ -1723,6 +1723,10 @@ ic::GetProp(VMFrame &f, ic::PICInfo *pic) f.regs.sp[-1].setInt32(str->length()); f.script()->typeMonitor(f.cx, f.pc(), f.regs.sp[-1]); return; + } else if (f.regs.sp[-1].isMagic(JS_LAZY_ARGUMENTS)) { + f.regs.sp[-1].setInt32(f.regs.fp()->numActualArgs()); + f.script()->typeMonitor(f.cx, f.pc(), f.regs.sp[-1]); + return; } else if (!f.regs.sp[-1].isPrimitive()) { JSObject *obj = &f.regs.sp[-1].toObject(); if (obj->isArray() || @@ -2519,7 +2523,7 @@ ic::GetElement(VMFrame &f, ic::GetElementIC *ic) { JSContext *cx = f.cx; - // Right now, we don't optimize for strings. + // Right now, we don't optimize for strings or lazy arguments. if (!f.regs.sp[-2].isObject()) { ic->disable(cx, "non-object"); stubs::GetElem(f); diff --git a/js/src/methodjit/Retcon.cpp b/js/src/methodjit/Retcon.cpp index e2af5b5bc2c..2e163ab7faf 100644 --- a/js/src/methodjit/Retcon.cpp +++ b/js/src/methodjit/Retcon.cpp @@ -232,9 +232,6 @@ Recompiler::expandInlineFrames(JSContext *cx, StackFrame *fp, mjit::CallSite *in */ cx->compartment->types.frameExpansions++; - RejoinState rejoin = (RejoinState) f->stubRejoin; - JS_ASSERT(rejoin != REJOIN_NATIVE && rejoin != REJOIN_NATIVE_LOWERED); - /* * Patch the VMFrame's return address if it is returning at the given inline site. * Note there is no worry about handling a native or CompileFunction call here, @@ -249,13 +246,12 @@ Recompiler::expandInlineFrames(JSContext *cx, StackFrame *fp, mjit::CallSite *in StackFrame *innerfp = expandInlineFrameChain(cx, fp, inner); /* Check if the VMFrame returns into the inlined frame. */ - if (f->stubRejoin) { + if (f->stubRejoin && (f->stubRejoin & 0x1) && f->regs.fp()->prev() == fp) { /* The VMFrame is calling CompileFunction. */ - if (f->regs.fp()->prev() == fp) { - fp->prev()->setRejoin(StubRejoin(rejoin)); - *frameAddr = JS_FUNC_TO_DATA_PTR(void *, JaegerInterpoline); - } - } else if (*frameAddr == codeStart + inlined->codeOffset) { + fp->prev()->setRejoin(StubRejoin((RejoinState) f->stubRejoin)); + *frameAddr = JS_FUNC_TO_DATA_PTR(void *, JaegerInterpoline); + } + if (*frameAddr == codeStart + inlined->codeOffset) { /* The VMFrame returns directly into the expanded frame. */ SetRejoinState(innerfp, *inlined, frameAddr); } diff --git a/js/src/methodjit/StubCalls.cpp b/js/src/methodjit/StubCalls.cpp index 30b7486de26..b87c8f0d7ee 100644 --- a/js/src/methodjit/StubCalls.cpp +++ b/js/src/methodjit/StubCalls.cpp @@ -443,6 +443,17 @@ stubs::GetElem(VMFrame &f) } } + if (lref.isMagic(JS_LAZY_ARGUMENTS)) { + if (rref.isInt32() && size_t(rref.toInt32()) < regs.fp()->numActualArgs()) { + regs.sp[-2] = regs.fp()->canonicalActualArg(rref.toInt32()); + f.script()->typeMonitor(cx, f.pc(), regs.sp[-2]); + return; + } + cx->markTypeObjectFlags(f.script()->fun->getType(), + types::OBJECT_FLAG_CREATED_ARGUMENTS); + JS_ASSERT(!lref.isMagic(JS_LAZY_ARGUMENTS)); + } + JSObject *obj = ValueToObject(cx, &lref); if (!obj) THROW(); @@ -1965,6 +1976,14 @@ InlineGetProp(VMFrame &f) FrameRegs ®s = f.regs; Value *vp = &f.regs.sp[-1]; + + if (vp->isMagic(JS_LAZY_ARGUMENTS)) { + JS_ASSERT(js_GetOpcode(cx, f.script(), f.pc()) == JSOP_LENGTH); + regs.sp[-1] = Int32Value(regs.fp()->numActualArgs()); + f.script()->typeMonitor(cx, f.pc(), regs.sp[-1]); + return true; + } + JSObject *obj = ValueToObject(f.cx, vp); if (!obj) return false; @@ -2826,14 +2845,14 @@ stubs::AssertArgumentTypes(VMFrame &f) JSScript *script = fun->script(); types::jstype type = types::GetValueType(f.cx, fp->thisValue()); - if (!script->thisTypes()->hasType(type)) { + if (!types::TypeMatches(f.cx, script->thisTypes(), type)) { types::TypeFailure(f.cx, "Missing type for #%u this: %s", script->id(), types::TypeString(type)); } for (unsigned i = 0; i < fun->nargs; i++) { type = types::GetValueType(f.cx, fp->formalArg(i)); - if (!script->argTypes(i)->hasType(type)) { + if (!types::TypeMatches(f.cx, script->argTypes(i), type)) { types::TypeFailure(f.cx, "Missing type for #%u arg %d: %s", script->id(), i, types::TypeString(type)); } diff --git a/js/src/vm/Stack-inl.h b/js/src/vm/Stack-inl.h index 071cba67f6c..055b062fdcc 100644 --- a/js/src/vm/Stack-inl.h +++ b/js/src/vm/Stack-inl.h @@ -496,10 +496,6 @@ StackFrame::stealFrameAndSlots(Value *vp, StackFrame *otherfp, PodCopy(vp, othervp, othersp - othervp); JS_ASSERT(vp == this->actualArgs() - 2); - /* Catch bad-touching of non-canonical args (e.g., generator_trace). */ - if (otherfp->hasOverflowArgs()) - Debug_SetValueRangeToCrashOnTouch(othervp, othervp + 2 + otherfp->numFormalArgs()); - /* * Repoint Call, Arguments, Block and With objects to the new live frame. * Call and Arguments are done directly because we have pointers to them. @@ -606,6 +602,13 @@ StackFrame::numActualArgs() const return numFormalArgs(); } +inline void +StackFrame::ensureCoherentArgCount() +{ + if (!hasArgsObj()) + args.nactual = numActualArgs(); +} + inline Value * StackFrame::actualArgs() const { @@ -903,7 +906,6 @@ ContextStack::getCallFrame(JSContext *cx, Value *firstUnused, uintN nactual, Value *dst = firstUnused; Value *src = firstUnused - (2 + nactual); PodCopy(dst, src, ncopy); - Debug_SetValueRangeToCrashOnTouch(src, ncopy); return reinterpret_cast(firstUnused + ncopy); } diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h index acb402b8834..c7b830c2ca8 100644 --- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -577,6 +577,7 @@ class StackFrame inline uintN numActualArgs() const; inline js::Value *actualArgs() const; inline js::Value *actualArgsEnd() const; + inline void ensureCoherentArgCount(); inline js::Value &canonicalActualArg(uintN i) const; template inline bool forEachCanonicalActualArg(Op op); @@ -958,6 +959,10 @@ class StackFrame return &args; } + static size_t offsetOfArgs() { + return offsetof(StackFrame, args); + } + static size_t offsetOfScopeChain() { return offsetof(StackFrame, scopeChain_); }