Bug 823061 - GetPropertyCache supports length properties of arrays. r=jandem
authorNicolas B. Pierron <nicolas.b.pierron@mozilla.com>
Thu, 20 Dec 2012 04:14:05 -0800
changeset 125724 259aa51773334998c074fa8676acbd50bab9d673
parent 125723 d08057e095a2e78e8e102fb022100be0efbe8608
child 125725 8b94cd856c5959d7464ee7ed1d8e75fb9bb152e0
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs823061
milestone20.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 823061 - GetPropertyCache supports length properties of arrays. r=jandem
js/src/ion/IonCaches.cpp
js/src/ion/IonCaches.h
js/src/jit-test/tests/ion/ArrayLengthGetPropertyIC.js
--- a/js/src/ion/IonCaches.cpp
+++ b/js/src/ion/IonCaches.cpp
@@ -701,16 +701,153 @@ IonCacheGetProperty::attachCallGetter(JS
     updateLastJump(exitJump);
 
     IonSpew(IonSpew_InlineCaches, "Generated native GETPROP stub at %p %s", code->raw(),
             idempotent() ? "(idempotent)" : "(not idempotent)");
 
     return true;
 }
 
+bool
+IonCacheGetProperty::attachDenseArrayLength(JSContext *cx, IonScript *ion, JSObject *obj)
+{
+    JS_ASSERT(obj->isDenseArray());
+    JS_ASSERT(!idempotent());
+
+    Label failures;
+    MacroAssembler masm;
+
+    // Guard object is a dense array.
+    RootedObject globalObj(cx, &script->global());
+    RootedShape shape(cx, GetDenseArrayShape(cx, globalObj));
+    if (!shape)
+        return false;
+    masm.branchTestObjShape(Assembler::NotEqual, object(), shape, &failures);
+
+    // Load length.
+    Register outReg;
+    if (output().hasValue()) {
+        outReg = output().valueReg().scratchReg();
+    } else {
+        JS_ASSERT(output().type() == MIRType_Int32);
+        outReg = output().typedReg().gpr();
+    }
+
+    masm.loadPtr(Address(object(), JSObject::offsetOfElements()), outReg);
+    masm.load32(Address(outReg, ObjectElements::offsetOfLength()), outReg);
+
+    // The length is an unsigned int, but the value encodes a signed int.
+    JS_ASSERT(object() != outReg);
+    masm.branchTest32(Assembler::Signed, outReg, outReg, &failures);
+
+    if (output().hasValue())
+        masm.tagValue(JSVAL_TYPE_INT32, outReg, output().valueReg());
+
+    u.getprop.hasDenseArrayLengthStub = true;
+    incrementStubCount();
+
+    /* Success. */
+    RepatchLabel rejoin_;
+    CodeOffsetJump rejoinOffset = masm.jumpWithPatch(&rejoin_);
+    masm.bind(&rejoin_);
+
+    /* Failure. */
+    masm.bind(&failures);
+    RepatchLabel exit_;
+    CodeOffsetJump exitOffset = masm.jumpWithPatch(&exit_);
+    masm.bind(&exit_);
+
+    Linker linker(masm);
+    IonCode *code = linker.newCode(cx);
+    if (!code)
+        return false;
+
+    rejoinOffset.fixup(&masm);
+    exitOffset.fixup(&masm);
+
+    if (ion->invalidated())
+        return true;
+
+    CodeLocationJump rejoinJump(code, rejoinOffset);
+    CodeLocationJump exitJump(code, exitOffset);
+    CodeLocationJump lastJump_ = lastJump();
+    PatchJump(lastJump_, CodeLocationLabel(code));
+    PatchJump(rejoinJump, rejoinLabel());
+    PatchJump(exitJump, cacheLabel());
+    updateLastJump(exitJump);
+
+    IonSpew(IonSpew_InlineCaches, "Generated GETPROP dense array length stub at %p", code->raw());
+
+    return true;
+}
+
+bool
+IonCacheGetProperty::attachTypedArrayLength(JSContext *cx, IonScript *ion, JSObject *obj)
+{
+    JS_ASSERT(obj->isTypedArray());
+    JS_ASSERT(!idempotent());
+
+    Label failures;
+    MacroAssembler masm;
+
+    Register tmpReg;
+    if (output().hasValue()) {
+        tmpReg = output().valueReg().scratchReg();
+    } else {
+        JS_ASSERT(output().type() == MIRType_Int32);
+        tmpReg = output().typedReg().gpr();
+    }
+    JS_ASSERT(object() != tmpReg);
+
+    // Implement the negated version of JSObject::isTypedArray predicate.
+    masm.loadObjClass(object(), tmpReg);
+    masm.branchPtr(Assembler::Below, tmpReg, ImmWord(&TypedArray::classes[0]), &failures);
+    masm.branchPtr(Assembler::AboveOrEqual, tmpReg, ImmWord(&TypedArray::classes[TypedArray::TYPE_MAX]), &failures);
+
+    // Load length.
+    masm.loadTypedOrValue(Address(object(), TypedArray::lengthOffset()), output());
+
+    u.getprop.hasTypedArrayLengthStub = true;
+    incrementStubCount();
+
+    /* Success. */
+    RepatchLabel rejoin_;
+    CodeOffsetJump rejoinOffset = masm.jumpWithPatch(&rejoin_);
+    masm.bind(&rejoin_);
+
+    /* Failure. */
+    masm.bind(&failures);
+    RepatchLabel exit_;
+    CodeOffsetJump exitOffset = masm.jumpWithPatch(&exit_);
+    masm.bind(&exit_);
+
+    Linker linker(masm);
+    IonCode *code = linker.newCode(cx);
+    if (!code)
+        return false;
+
+    rejoinOffset.fixup(&masm);
+    exitOffset.fixup(&masm);
+
+    if (ion->invalidated())
+        return true;
+
+    CodeLocationJump rejoinJump(code, rejoinOffset);
+    CodeLocationJump exitJump(code, exitOffset);
+    CodeLocationJump lastJump_ = lastJump();
+    PatchJump(lastJump_, CodeLocationLabel(code));
+    PatchJump(rejoinJump, rejoinLabel());
+    PatchJump(exitJump, cacheLabel());
+    updateLastJump(exitJump);
+
+    IonSpew(IonSpew_InlineCaches, "Generated GETPROP typed array length stub at %p", code->raw());
+
+    return true;
+}
+
 static bool
 TryAttachNativeGetPropStub(JSContext *cx, IonScript *ion,
                            IonCacheGetProperty &cache, HandleObject obj,
                            HandlePropertyName name,
                            const SafepointIndex *safepointIndex,
                            void *returnAddr, bool *isCacheable)
 {
     JS_ASSERT(!*isCacheable);
@@ -820,16 +957,32 @@ js::ion::GetPropertyCache(JSContext *cx,
     bool isCacheable = false;
     if (!TryAttachNativeGetPropStub(cx, ion, cache, obj, name,
                                     safepointIndex, returnAddr,
                                     &isCacheable))
     {
         return false;
     }
 
+    if (!isCacheable && !cache.idempotent() && cx->names().length == name) {
+        if (cache.output().type() != MIRType_Value && cache.output().type() != MIRType_Int32) {
+            // The next execution should cause an invalidation because the type
+            // does not fit.
+            isCacheable = false;
+        } else if (obj->isDenseArray() && !cache.hasDenseArrayLengthStub()) {
+            isCacheable = true;
+            if (!cache.attachDenseArrayLength(cx, ion, obj))
+                return false;
+        } else if (obj->isTypedArray() && !cache.hasTypedArrayLengthStub()) {
+            isCacheable = true;
+            if (!cache.attachTypedArrayLength(cx, ion, obj))
+                return false;
+        }
+    }
+
     if (cache.idempotent() && !isCacheable) {
         // Invalidate the cache if the property was not found, or was found on
         // a non-native object. This ensures:
         // 1) The property read has no observable side-effects.
         // 2) There's no need to dynamically monitor the return type. This would
         //    be complicated since (due to GVN) there can be multiple pc's
         //    associated with a single idempotent cache.
         IonSpew(IonSpew_InlineCaches, "Invalidating from idempotent cache %s:%d",
--- a/js/src/ion/IonCaches.h
+++ b/js/src/ion/IonCaches.h
@@ -106,17 +106,19 @@ class IonCache
 #else
     static const size_t REJOIN_LABEL_OFFSET = 0;
 #endif
     union {
         struct {
             Register object;
             PropertyName *name;
             TypedOrValueRegisterSpace output;
-            bool allowGetters;
+            bool allowGetters : 1;
+            bool hasDenseArrayLengthStub : 1;
+            bool hasTypedArrayLengthStub : 1;
         } getprop;
         struct {
             Register object;
             PropertyName *name;
             ConstantOrRegisterSpace value;
             bool strict;
         } setprop;
         struct {
@@ -260,28 +262,34 @@ class IonCacheGetProperty : public IonCa
                         TypedOrValueRegister output,
                         bool allowGetters)
     {
         init(GetProperty, liveRegs, initialJump, rejoinLabel, cacheLabel);
         u.getprop.object = object;
         u.getprop.name = name;
         u.getprop.output.data() = output;
         u.getprop.allowGetters = allowGetters;
+        u.getprop.hasDenseArrayLengthStub = false;
+        u.getprop.hasTypedArrayLengthStub = false;
     }
 
     Register object() const { return u.getprop.object; }
     PropertyName *name() const { return u.getprop.name; }
     TypedOrValueRegister output() const { return u.getprop.output.data(); }
     bool allowGetters() const { return u.getprop.allowGetters; }
+    bool hasDenseArrayLengthStub() const { return u.getprop.hasDenseArrayLengthStub; }
+    bool hasTypedArrayLengthStub() const { return u.getprop.hasTypedArrayLengthStub; }
 
     bool attachReadSlot(JSContext *cx, IonScript *ion, JSObject *obj, JSObject *holder,
                         HandleShape shape);
     bool attachCallGetter(JSContext *cx, IonScript *ion, JSObject *obj, JSObject *holder,
                           HandleShape shape,
                           const SafepointIndex *safepointIndex, void *returnAddr);
+    bool attachDenseArrayLength(JSContext *cx, IonScript *ion, JSObject *obj);
+    bool attachTypedArrayLength(JSContext *cx, IonScript *ion, JSObject *obj);
 };
 
 class IonCacheSetProperty : public IonCache
 {
   public:
     IonCacheSetProperty(CodeOffsetJump initialJump,
                         CodeOffsetLabel rejoinLabel,
                         CodeOffsetLabel cacheLabel,
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/ArrayLengthGetPropertyIC.js
@@ -0,0 +1,54 @@
+function intLength (a, l) {
+    var res = 0;
+    for (var i = 0; i < l; i++)
+        res += a.length;
+    return res / l;
+}
+
+function valueLength (a, l) {
+    var res = 0;
+    for (var i = 0; i < l; i++)
+        res += a.length;
+    return res / l;
+}
+
+var denseArray = [0,1,2,3,4,5,6,7,8,9];
+var typedArray = new Uint8Array(10);
+var hugeArray  = new Array(4294967295);
+var fakeArray1 = { length: 10 };
+var fakeArray2 = { length: 10.5 };
+
+// Check the interpreter result and play with TI type objects.
+assertEq(intLength(denseArray, 10), 10);
+assertEq(intLength(typedArray, 10), 10);
+// assertEq(intLength(fakeArray1, 10), 10);
+
+assertEq(valueLength(denseArray, 10), 10);
+assertEq(valueLength(typedArray, 10), 10);
+assertEq(valueLength(hugeArray , 10), 4294967295);
+assertEq(valueLength(fakeArray2, 10), 10.5);
+
+// Heat up to compile (either JM / Ion)
+assertEq(intLength(denseArray, 100), 10);
+assertEq(valueLength(denseArray, 100), 10);
+
+// No bailout should occur during any of the following checks:
+
+// Check get-property length IC with dense array.
+assertEq(intLength(denseArray, 1), 10);
+assertEq(valueLength(denseArray, 1), 10);
+
+// Check get-property length IC with typed array.
+assertEq(intLength(typedArray, 1), 10);
+assertEq(valueLength(typedArray, 1), 10);
+
+// Check length which do not fit on non-double value.
+assertEq(valueLength(hugeArray, 1), 4294967295);
+
+// Check object length property.
+assertEq(intLength(fakeArray1, 1), 10);
+assertEq(valueLength(fakeArray2, 1), 10.5);
+
+// Cause invalidation of intLength by returning a double.
+assertEq(intLength(hugeArray, 1), 4294967295);
+assertEq(intLength(fakeArray2, 1), 10.5);