[INFER] Address array review comments, add back backedge table, bug 657412.

This commit is contained in:
Brian Hackett 2011-06-01 11:15:51 -07:00
parent 1bc79b5ebc
commit 8b1926a6bf
11 changed files with 144 additions and 95 deletions

View File

@ -51,21 +51,35 @@
* getArrayLength(), setArrayLength().
* - The number of element slots (capacity), gettable with
* getDenseArrayCapacity().
* - The array's initialized length, accessible with getDenseArrayInitializedLength().
* - The array's initialized length, accessible with
* getDenseArrayInitializedLength().
*
* In dense mode, holes in the array are represented by
* MagicValue(JS_ARRAY_HOLE) invalid values. Elements between the initialized
* length and the length property are left uninitialized, but are conceptually holes.
* Arrays with no holes below the initialized length are "packed" arrays.
* MagicValue(JS_ARRAY_HOLE) invalid values.
*
* NB: the capacity and length of a dense array are entirely unrelated! The
* length may be greater than, less than, or equal to the capacity. The first
* case may occur when the user writes "new Array(100), in which case the
* length is 100 while the capacity remains 0 (indices below length and above
* capacity must be treated as holes). See array_length_setter for another
* explanation of how the first case may occur. When type inference is enabled,
* the initialized length is always less than or equal to both the length and
* capacity. Otherwise, the initialized length always equals the capacity.
* explanation of how the first case may occur.
*
* The initialized length of a dense array specifies the number of elements
* that have been initialized. All elements above the initialized length are
* holes in the array, and the memory for all elements between the initialized
* length and capacity is left uninitialized. When type inference is disabled,
* the initialized length always equals the array's capacity. When inference is
* enabled, the initialized length is some value less than or equal to both the
* array's length and the array's capacity.
*
* With inference enabled, there is flexibility in exactly the value the
* initialized length must hold, e.g. if an array has length 5, capacity 10,
* completely empty, it is valid for the initialized length to be any value
* between zero and 5, as long as the in memory values below the initialized
* length have been initialized with a hole value. However, in such cases we
* want to keep the initialized length as small as possible: if the array is
* known to have no hole values below its initialized length, then it is a
* "packed" array and can be accessed much faster by JIT code.
*
* Arrays are converted to use js_SlowArrayClass when any of these conditions
* are met:
@ -520,7 +534,7 @@ DeleteArrayElement(JSContext *cx, JSObject *obj, jsdouble index, bool strict)
if (index <= jsuint(-1)) {
jsuint idx = jsuint(index);
if (idx < obj->getDenseArrayInitializedLength()) {
obj->setDenseArrayNotPacked(cx);
obj->markDenseArrayNotPacked(cx);
obj->setDenseArrayElement(idx, MagicValue(JS_ARRAY_HOLE));
if (!js_SuppressDeletedIndexProperties(cx, obj, idx, idx+1))
return -1;
@ -645,7 +659,7 @@ array_length_setter(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value
if (oldinit > newlen) {
obj->setDenseArrayInitializedLength(newlen);
if (!cx->typeInferenceEnabled())
obj->backfillDenseArrayHoles();
obj->backfillDenseArrayHoles(cx);
}
} else if (oldlen - newlen < (1 << 24)) {
do {
@ -938,7 +952,7 @@ array_deleteProperty(JSContext *cx, JSObject *obj, jsid id, Value *rval, JSBool
}
if (js_IdIsIndex(id, &i) && i < obj->getDenseArrayInitializedLength()) {
obj->setDenseArrayNotPacked(cx);
obj->markDenseArrayNotPacked(cx);
obj->setDenseArrayElement(i, MagicValue(JS_ARRAY_HOLE));
}
@ -956,8 +970,8 @@ array_trace(JSTracer *trc, JSObject *obj)
{
JS_ASSERT(obj->isDenseArray());
uint32 capacity = obj->getDenseArrayInitializedLength();
MarkValueRange(trc, capacity, obj->getDenseArrayElements(), "element");
uint32 initLength = obj->getDenseArrayInitializedLength();
MarkValueRange(trc, initLength, obj->getDenseArrayElements(), "element");
}
static JSBool
@ -1048,7 +1062,7 @@ JSObject::makeDenseArraySlow(JSContext *cx)
cx->markTypeObjectFlags(getType(),
js::types::OBJECT_FLAG_NON_PACKED_ARRAY |
js::types::OBJECT_FLAG_NON_DENSE_ARRAY);
setDenseArrayNotPacked(cx);
markDenseArrayNotPacked(cx);
/*
* Save old map now, before calling InitScopeForObject. We'll have to undo
@ -1062,7 +1076,7 @@ JSObject::makeDenseArraySlow(JSContext *cx)
if (!InitScopeForObject(cx, this, &js_SlowArrayClass, getType(), kind))
return false;
backfillDenseArrayHoles();
backfillDenseArrayHoles(cx);
uint32 arrayCapacity = getDenseArrayCapacity();
uint32 arrayInitialized = getDenseArrayInitializedLength();
@ -1569,7 +1583,7 @@ InitArrayObject(JSContext *cx, JSObject *obj, jsuint length, const Value *vector
if (cx->typeInferenceEnabled())
obj->setDenseArrayInitializedLength(length);
else
obj->backfillDenseArrayHoles();
obj->backfillDenseArrayHoles(cx);
bool hole = false;
for (jsuint i = 0; i < length; i++) {
@ -1577,7 +1591,7 @@ InitArrayObject(JSContext *cx, JSObject *obj, jsuint length, const Value *vector
hole |= vector[i].isMagic(JS_ARRAY_HOLE);
}
if (hole)
obj->setDenseArrayNotPacked(cx);
obj->markDenseArrayNotPacked(cx);
return true;
}
@ -1645,13 +1659,7 @@ array_reverse(JSContext *cx, uintN argc, Value *vp)
}
/* Fill out the array's initialized length to its proper length. */
jsuint initlen = obj->getDenseArrayInitializedLength();
if (len > initlen) {
JS_ASSERT(cx->typeInferenceEnabled());
ClearValueRange(obj->getDenseArrayElements() + initlen, len - initlen, true);
obj->setDenseArrayNotPacked(cx);
obj->setDenseArrayInitializedLength(len);
}
obj->ensureDenseArrayInitializedLength(cx, len, 0);
uint32 lo = 0, hi = len - 1;
for (; lo < hi; lo++, hi--) {
@ -2230,8 +2238,6 @@ ArrayCompPushImpl(JSContext *cx, JSObject *obj, const Value &v)
*/
if (!obj->ensureSlots(cx, length + 1))
return false;
if (!cx->typeInferenceEnabled())
obj->backfillDenseArrayHoles();
}
if (cx->typeInferenceEnabled())
@ -2680,7 +2686,7 @@ array_concat(JSContext *cx, uintN argc, Value *vp)
nobj->setType(aobj->getType());
nobj->setArrayLength(cx, length);
if (!aobj->isPackedDenseArray())
nobj->setDenseArrayNotPacked(cx);
nobj->markDenseArrayNotPacked(cx);
vp->setObject(*nobj);
if (argc == 0)
return JS_TRUE;
@ -2812,7 +2818,7 @@ array_slice(JSContext *cx, uintN argc, Value *vp)
return JS_FALSE;
nobj->setType(type);
if (!obj->isPackedDenseArray())
nobj->setDenseArrayNotPacked(cx);
nobj->markDenseArrayNotPacked(cx);
vp->setObject(*nobj);
return JS_TRUE;
}
@ -3500,13 +3506,13 @@ NewArray(JSContext *cx, jsuint length, JSObject *proto)
obj->setArrayLength(cx, length);
if (allocateCapacity) {
if (!obj->ensureSlots(cx, length))
return NULL;
if (!cx->typeInferenceEnabled()) {
obj->markDenseArrayNotPacked(cx);
obj->backfillDenseArrayHoles(cx);
}
if (!cx->typeInferenceEnabled())
obj->backfillDenseArrayHoles();
if (allocateCapacity && !obj->ensureSlots(cx, length))
return NULL;
return obj;
}
@ -3751,7 +3757,7 @@ js_CloneDensePrimitiveArray(JSContext *cx, JSObject *obj, JSObject **clone)
return JS_FALSE;
if (!obj->isPackedDenseArray())
(*clone)->setDenseArrayNotPacked(cx);
(*clone)->markDenseArrayNotPacked(cx);
/* The length will be set to the initlen, above, but length might be larger. */
(*clone)->setArrayLength(cx, length);

View File

@ -75,7 +75,7 @@ JSObject::isPackedDenseArray()
}
inline void
JSObject::setDenseArrayNotPacked(JSContext *cx)
JSObject::markDenseArrayNotPacked(JSContext *cx)
{
JS_ASSERT(isDenseArray());
if (flags & PACKED_ARRAY) {
@ -84,33 +84,48 @@ JSObject::setDenseArrayNotPacked(JSContext *cx)
}
}
inline void
JSObject::backfillDenseArrayHoles(JSContext *cx)
{
/* Ensure an array's elements are fully initialized. */
ensureDenseArrayInitializedLength(cx, getDenseArrayCapacity(), 0);
}
inline void
JSObject::ensureDenseArrayInitializedLength(JSContext *cx, uint32 index, uint32 extra)
{
/*
* Ensure that the array's contents have been initialized up to index, and
* mark the elements through 'index + extra' as initialized in preparation
* for a write.
*/
JS_ASSERT(index + extra <= capacity);
if (initializedLength < index) {
markDenseArrayNotPacked(cx);
ClearValueRange(slots + initializedLength, index - initializedLength, true);
}
if (initializedLength < index + extra)
initializedLength = index + extra;
}
inline JSObject::EnsureDenseResult
JSObject::ensureDenseArrayElements(JSContext *cx, uintN index, uintN extra)
{
JS_ASSERT(isDenseArray());
uintN currentCapacity = numSlots();
uintN initLength = getDenseArrayInitializedLength();
/*
* Don't take excessive slow paths when inference is disabled, due to
* uninitialized slots between initializedLength and capacity.
*/
JS_ASSERT_IF(!cx->typeInferenceEnabled(), currentCapacity == initLength);
JS_ASSERT_IF(!cx->typeInferenceEnabled(), currentCapacity == getDenseArrayInitializedLength());
uintN requiredCapacity;
if (extra == 1) {
/* Optimize for the common case. */
if (index < initLength)
return ED_OK;
if (index < currentCapacity) {
JS_ASSERT(cx->typeInferenceEnabled());
if (index > initLength) {
setDenseArrayNotPacked(cx);
ClearValueRange(getDenseArrayElements() + initLength,
index - initLength, true);
}
setDenseArrayInitializedLength(index + 1);
ensureDenseArrayInitializedLength(cx, index, 1);
return ED_OK;
}
requiredCapacity = index + 1;
@ -124,16 +139,8 @@ JSObject::ensureDenseArrayElements(JSContext *cx, uintN index, uintN extra)
/* Overflow. */
return ED_SPARSE;
}
if (requiredCapacity <= initLength)
return ED_OK;
if (requiredCapacity <= currentCapacity) {
JS_ASSERT(cx->typeInferenceEnabled());
if (index > initLength) {
ClearValueRange(getDenseArrayElements() + initLength,
index - initLength, true);
setDenseArrayNotPacked(cx);
}
setDenseArrayInitializedLength(requiredCapacity);
ensureDenseArrayInitializedLength(cx, index, extra);
return ED_OK;
}
}
@ -149,17 +156,7 @@ JSObject::ensureDenseArrayElements(JSContext *cx, uintN index, uintN extra)
if (!growSlots(cx, requiredCapacity))
return ED_FAILED;
if (cx->typeInferenceEnabled()) {
if (index > initLength) {
setDenseArrayNotPacked(cx);
ClearValueRange(getDenseArrayElements() + initLength,
index - initLength, true);
}
setDenseArrayInitializedLength(requiredCapacity);
} else {
backfillDenseArrayHoles();
}
ensureDenseArrayInitializedLength(cx, index, extra);
return ED_OK;
}

View File

@ -145,6 +145,9 @@ JSCompartment::init(JSContext *cx)
if (!regExpAllocator)
return false;
if (!backEdgeTable.init())
return false;
#ifdef JS_METHODJIT
jaegerCompartment = rt->new_<mjit::JaegerCompartment>();
if (!jaegerCompartment || !jaegerCompartment->Initialize())
@ -643,3 +646,20 @@ JSCompartment::allocMathCache(JSContext *cx)
js_ReportOutOfMemory(cx);
return mathCache;
}
size_t
JSCompartment::backEdgeCount(jsbytecode *pc) const
{
if (BackEdgeMap::Ptr p = backEdgeTable.lookup(pc))
return p->value;
return 0;
}
size_t
JSCompartment::incBackEdgeCount(jsbytecode *pc)
{
if (BackEdgeMap::Ptr p = backEdgeTable.lookupWithDefault(pc, 0))
return ++p->value;
return 1; /* oom not reported by backEdgeTable, so ignore. */
}

View File

@ -518,11 +518,21 @@ struct JS_FRIEND_API(JSCompartment) {
js::MathCache *allocMathCache(JSContext *cx);
typedef js::HashMap<jsbytecode*,
size_t,
js::DefaultHasher<jsbytecode*>,
js::SystemAllocPolicy> BackEdgeMap;
BackEdgeMap backEdgeTable;
JSCompartment *thisForCtor() { return this; }
public:
js::MathCache *getMathCache(JSContext *cx) {
return mathCache ? mathCache : allocMathCache(cx);
}
size_t backEdgeCount(jsbytecode *pc) const;
size_t incBackEdgeCount(jsbytecode *pc);
};
#define JS_SCRIPTS_TO_GC(cx) ((cx)->compartment->scriptsToGC)

View File

@ -122,12 +122,19 @@ const size_t SLOTS_TO_THING_KIND_LIMIT = 17;
/* Get the best kind to use when making an object with the given slot count. */
static inline FinalizeKind
GetGCObjectKind(size_t numSlots)
GetGCObjectKind(size_t numSlots, bool isArray = false)
{
extern FinalizeKind slotsToThingKind[];
if (numSlots >= SLOTS_TO_THING_KIND_LIMIT)
return FINALIZE_OBJECT16;
if (numSlots >= SLOTS_TO_THING_KIND_LIMIT) {
/*
* If the object will definitely want more than the maximum number of
* fixed slots, use zero fixed slots for arrays and the maximum for
* other objects. Arrays do not use their fixed slots anymore when
* they have a slots array, while other objects will continue to do so.
*/
return isArray ? FINALIZE_OBJECT0 : FINALIZE_OBJECT16;
}
return slotsToThingKind[numSlots];
}

View File

@ -84,7 +84,6 @@
using namespace js;
using namespace js::gc;
using namespace js::types;
static void iterator_finalize(JSContext *cx, JSObject *obj);
static void iterator_trace(JSTracer *trc, JSObject *obj);

View File

@ -4356,8 +4356,9 @@ JSObject::allocSlots(JSContext *cx, size_t newcap)
if (isDenseArray()) {
/* Copy over anything from the inline buffer. */
memcpy(slots, fixedSlots(), oldcap * sizeof(Value));
ClearValueRange(slots + oldcap, newcap - oldcap, true);
memcpy(slots, fixedSlots(), getDenseArrayInitializedLength() * sizeof(Value));
if (!cx->typeInferenceEnabled())
backfillDenseArrayHoles(cx);
} else {
/* Clear out the new slots without copying. */
ClearValueRange(slots, allocCount, false);
@ -4413,8 +4414,11 @@ JSObject::growSlots(JSContext *cx, size_t newcap)
slots = tmpslots;
capacity = actualCapacity;
if (!isDenseArray()) {
/* Initialize the additional slots we added. This is not required for dense arrays. */
if (isDenseArray()) {
if (!cx->typeInferenceEnabled())
backfillDenseArrayHoles(cx);
} else {
/* Clear the new slots we added. */
ClearValueRange(slots + oldAllocCount, allocCount - oldAllocCount, false);
}

View File

@ -851,6 +851,8 @@ struct JSObject : js::gc::Cell {
inline uint32 getDenseArrayInitializedLength();
inline void setDenseArrayLength(uint32 length);
inline void setDenseArrayInitializedLength(uint32 length);
inline void ensureDenseArrayInitializedLength(JSContext *cx, uintN index, uintN extra);
inline void backfillDenseArrayHoles(JSContext *cx);
inline js::Value* getDenseArrayElements();
inline const js::Value &getDenseArrayElement(uintN idx);
inline js::Value* addressOfDenseArrayElement(uintN idx);
@ -858,11 +860,10 @@ struct JSObject : js::gc::Cell {
inline void setDenseArrayElementWithType(JSContext *cx, uintN idx, const js::Value &val);
inline void shrinkDenseArrayElements(JSContext *cx, uintN cap);
inline bool denseArrayHasInlineSlots() const;
inline void backfillDenseArrayHoles();
/* Packed information for this array. May be incorrect if !cx->typeInferenceEnabled(). */
/* Packed information for this array. */
inline bool isPackedDenseArray();
inline void setDenseArrayNotPacked(JSContext *cx);
inline void markDenseArrayNotPacked(JSContext *cx);
/*
* ensureDenseArrayElements ensures that the dense array can hold at least

View File

@ -530,15 +530,6 @@ JSObject::denseArrayHasInlineSlots() const
return slots == fixedSlots();
}
inline void
JSObject::backfillDenseArrayHoles()
{
/* Only call this if !cx->typeInferenceEnabled(). */
JS_ASSERT(isDenseArray());
ClearValueRange(slots + initializedLength, capacity - initializedLength, true);
initializedLength = capacity;
}
inline bool
JSObject::callIsForEval() const
{
@ -895,18 +886,20 @@ JSObject::init(JSContext *cx, js::Class *aclasp, js::types::TypeObject *type,
/*
* Fill the fixed slots with undefined if needed. This object must
* already have its capacity filled in, as by js_NewGCObject.
* already have its capacity filled in, as by js_NewGCObject. If inference
* is disabled, NewArray will backfill holes up to the array's capacity
* and unset the PACKED_ARRAY flag.
*/
slots = NULL;
ClearValueRange(fixedSlots(), capacity, useHoles);
if (useHoles) {
slots = fixedSlots();
flags |= PACKED_ARRAY;
} else {
ClearValueRange(fixedSlots(), capacity, useHoles);
}
newType = NULL;
JS_ASSERT(initializedLength == 0);
initializedLength = 0;
setType(type);
setParent(parent);
@ -1564,7 +1557,7 @@ static inline gc::FinalizeKind
GuessObjectGCKind(size_t numSlots, bool isArray)
{
if (numSlots)
return gc::GetGCObjectKind(numSlots);
return gc::GetGCObjectKind(numSlots, isArray);
return isArray ? gc::FINALIZE_OBJECT8 : gc::FINALIZE_OBJECT4;
}

View File

@ -1202,8 +1202,9 @@ mjit::Compiler::finishThisUp(JITScript **jitp)
jitTraceICs[i].slowTraceHint = stubCode.locationOf(traceICs[i].slowTraceHint.get());
#ifdef JS_TRACER
uint32 hotloop = GetHotloop(cx);
uint32 prevCount = cx->compartment->backEdgeCount(traceICs[i].jumpTarget);
jitTraceICs[i].loopCounterStart = hotloop;
jitTraceICs[i].loopCounter = hotloop;
jitTraceICs[i].loopCounter = hotloop < prevCount ? 1 : hotloop - prevCount;
#endif
stubCode.patch(traceICs[i].addrLabel, &jitTraceICs[i]);

View File

@ -107,11 +107,22 @@ CanMethodJITAtBranch(JSContext *cx, JSScript *script, StackFrame *fp, jsbytecode
JITScriptStatus status = script->getJITStatus(fp->isConstructing());
if (status == JITScript_Invalid)
return Compile_Abort;
if (status == JITScript_None &&
!cx->hasRunOption(JSOPTION_METHODJIT_ALWAYS) &&
script->incUseCount() <= USES_BEFORE_COMPILE)
{
return Compile_Skipped;
if (status == JITScript_None && !cx->hasRunOption(JSOPTION_METHODJIT_ALWAYS)) {
/*
* Backedges are counted differently with type inference vs. with the
* tracer. For inference, we use the script's use count, so that we can
* easily reset the script's uses if we end up recompiling it. For the
* tracer, we use the compartment's backedge table so that when
* compiling trace ICs we will retain counts for each loop and respect
* the HOTLOOP value when deciding to start recording traces.
*/
if (cx->typeInferenceEnabled()) {
if (script->incUseCount() <= USES_BEFORE_COMPILE)
return Compile_Skipped;
} else {
if (cx->compartment->incBackEdgeCount(pc) <= USES_BEFORE_COMPILE)
return Compile_Skipped;
}
}
if (status == JITScript_None)
return TryCompile(cx, fp);