[INFER] Optimize arguments accesses, bug 658638.

This commit is contained in:
Brian Hackett 2011-05-26 12:28:19 -07:00
parent 02a6764444
commit 621ab68f21
27 changed files with 784 additions and 96 deletions

View File

@ -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);

View File

@ -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);
}

View File

@ -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, "'<27>O<EFBFBD> <20>:i<10><>'.match(new RegExp('.+'))", [], '<27>O<EFBFBD> <20>:i<10><>');
jsTestDriverEnd();

View File

@ -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:

View File

@ -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<SSAValue> *seen);
bool followEscapingArguments(JSContext *cx, SSAUseChain *use, Vector<SSAValue> *seen);
inline void setForTypes(JSContext *cx, jsbytecode *pc, types::TypeSet *types);
};

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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<TypeConstraintSubsetBarrier>(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<TypeConstraintLazyArguments>(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<SSAValue> 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<SSAValue> *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<SSAValue> *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));
}

View File

@ -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);

View File

@ -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);

View File

@ -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)

View File

@ -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 */

View File

@ -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;

View File

@ -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 ***/

View File

@ -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);
}

View File

@ -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

View File

@ -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);

View File

@ -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.

View File

@ -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));

View File

@ -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);

View File

@ -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;
{

View File

@ -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);

View File

@ -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);
}

View File

@ -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 &regs = 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));
}

View File

@ -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<StackFrame *>(firstUnused + ncopy);
}

View File

@ -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 <class Op> 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_);
}