From affc0dee90eb63d43fd6e82e897889e0b2466a28 Mon Sep 17 00:00:00 2001 From: Tom Schuster Date: Mon, 23 Feb 2015 18:21:51 +0100 Subject: [PATCH] Bug 1131531 - Ion GetElement IC should handle dense element holes. r=jandem --- .../tests/basic/array-proto-outofrange.js | 18 ++ .../tests/basic/dense-elements-appear.js | 28 +++ js/src/jit/IonCaches.cpp | 161 ++++++++++++++++++ js/src/jit/IonCaches.h | 5 + js/src/vm/TypeInference.cpp | 4 +- js/src/vm/TypeInference.h | 3 + 6 files changed, 217 insertions(+), 2 deletions(-) create mode 100644 js/src/jit-test/tests/basic/array-proto-outofrange.js create mode 100644 js/src/jit-test/tests/basic/dense-elements-appear.js diff --git a/js/src/jit-test/tests/basic/array-proto-outofrange.js b/js/src/jit-test/tests/basic/array-proto-outofrange.js new file mode 100644 index 000000000000..795f41f72d47 --- /dev/null +++ b/js/src/jit-test/tests/basic/array-proto-outofrange.js @@ -0,0 +1,18 @@ +function f(obj) { + return typeof obj[15]; +} + +function test() { + var a = [1, 2]; + a.__proto__ = {15: 1337}; + var b = [1, 2, 3, 4]; + + for (var i = 0; i < 1000; i++) { + var r = f(i % 2 ? a : b); + assertEq(r, i % 2 ? "number" : "undefined"); + } +} + +test(); +test(); +test(); diff --git a/js/src/jit-test/tests/basic/dense-elements-appear.js b/js/src/jit-test/tests/basic/dense-elements-appear.js new file mode 100644 index 000000000000..351f8f676316 --- /dev/null +++ b/js/src/jit-test/tests/basic/dense-elements-appear.js @@ -0,0 +1,28 @@ +function f() { + var x = [1, 2, 3]; + var y = {}; + x.__proto__ = y; + + for (var i = 0; i < 200; i++) { + if (i == 100) + y[100000] = 15; + else + assertEq(typeof x[100000], i > 100 ? "number" : "undefined"); + } +} + +function g() { + var x = [1, 2, 3]; + var y = {}; + x.__proto__ = y; + + for (var i = 0; i < 200; i++) { + if (i == 100) + y[4] = 15; + else + assertEq(typeof x[4], i > 100 ? "number" : "undefined"); + } +} + +f(); +g(); diff --git a/js/src/jit/IonCaches.cpp b/js/src/jit/IonCaches.cpp index 226ff2bb8180..53f8dddfec08 100644 --- a/js/src/jit/IonCaches.cpp +++ b/js/src/jit/IonCaches.cpp @@ -3258,6 +3258,160 @@ GetElementIC::attachDenseElement(JSContext *cx, HandleScript outerScript, IonScr return linkAndAttachStub(cx, masm, attacher, ion, "dense array"); } + +/* static */ bool +GetElementIC::canAttachDenseElementHole(JSObject *obj, const Value &idval, TypedOrValueRegister output) +{ + if (!idval.isInt32()) + return false; + + if (!output.hasValue()) + return false; + + if (!obj->isNative()) + return false; + + if (obj->as().getDenseInitializedLength() == 0) + return false; + + while (obj) { + if (obj->isIndexed()) + return false; + + if (ClassCanHaveExtraProperties(obj->getClass())) + return false; + + JSObject *proto = obj->getProto(); + if (!proto) + break; + + if (!proto->isNative()) + return false; + + // Make sure objects on the prototype don't have dense elements. + if (proto->as().getDenseInitializedLength() != 0) + return false; + + obj = proto; + } + + return true; +} + +static bool +GenerateDenseElementHole(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &attacher, + IonScript *ion, JSObject *obj, const Value &idval, + Register object, ConstantOrRegister index, TypedOrValueRegister output) +{ + MOZ_ASSERT(GetElementIC::canAttachDenseElementHole(obj, idval, output)); + MOZ_ASSERT(obj->lastProperty()); + + Register scratchReg = output.valueReg().scratchReg(); + + // Guard on the shape and group, to prevent non-dense elements from appearing. + Label failures; + attacher.branchNextStubOrLabel(masm, Assembler::NotEqual, + Address(object, JSObject::offsetOfShape()), + ImmGCPtr(obj->lastProperty()), &failures); + + + if (obj->hasUncacheableProto()) { + masm.loadPtr(Address(object, JSObject::offsetOfGroup()), scratchReg); + Address proto(scratchReg, ObjectGroup::offsetOfProto()); + masm.branchPtr(Assembler::NotEqual, proto, + ImmMaybeNurseryPtr(obj->getProto()), &failures); + } + + JSObject *pobj = obj->getProto(); + while (pobj) { + MOZ_ASSERT(pobj->lastProperty()); + + masm.movePtr(ImmMaybeNurseryPtr(pobj), scratchReg); + if (pobj->hasUncacheableProto()) { + MOZ_ASSERT(!pobj->isSingleton()); + Address groupAddr(scratchReg, JSObject::offsetOfGroup()); + masm.branchPtr(Assembler::NotEqual, groupAddr, ImmGCPtr(pobj->group()), &failures); + } + + // Make sure the shape matches, to avoid non-dense elements. + masm.branchPtr(Assembler::NotEqual, Address(scratchReg, JSObject::offsetOfShape()), + ImmGCPtr(pobj->lastProperty()), &failures); + + // Load elements vector. + masm.loadPtr(Address(scratchReg, NativeObject::offsetOfElements()), scratchReg); + + // Also make sure there are no dense elements. + Label hole; + Address initLength(scratchReg, ObjectElements::offsetOfInitializedLength()); + masm.branch32(Assembler::NotEqual, initLength, Imm32(0), &failures); + + pobj = pobj->getProto(); + } + + // Ensure the index is an int32 value. + Register indexReg = InvalidReg; + Register elementsReg = InvalidReg; + + if (index.reg().hasValue()) { + indexReg = scratchReg; + MOZ_ASSERT(indexReg != InvalidReg); + ValueOperand val = index.reg().valueReg(); + + masm.branchTestInt32(Assembler::NotEqual, val, &failures); + + // Unbox the index. + masm.unboxInt32(val, indexReg); + + // Save the object register. + masm.push(object); + elementsReg = object; + } else { + MOZ_ASSERT(!index.reg().typedReg().isFloat()); + indexReg = index.reg().typedReg().gpr(); + elementsReg = scratchReg; + } + + // Load elements vector. + masm.loadPtr(Address(object, NativeObject::offsetOfElements()), elementsReg); + + // Guard on the initialized length. + Label hole; + Address initLength(elementsReg, ObjectElements::offsetOfInitializedLength()); + masm.branch32(Assembler::BelowOrEqual, initLength, indexReg, &hole); + + // Load the value. + Label done; + masm.loadValue(BaseObjectElementIndex(elementsReg, indexReg), output.valueReg()); + masm.branchTestMagic(Assembler::NotEqual, output.valueReg(), &done); + + // Load undefined for the hole. + masm.bind(&hole); + masm.moveValue(UndefinedValue(), output.valueReg()); + + masm.bind(&done); + // Restore the object register. + if (elementsReg == object) + masm.pop(object); + attacher.jumpRejoin(masm); + + // All failure flows through here. + masm.bind(&failures); + attacher.jumpNextStub(masm); + + return true; +} + +bool +GetElementIC::attachDenseElementHole(JSContext *cx, HandleScript outerScript, IonScript *ion, + HandleObject obj, const Value &idval) +{ + MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_); + RepatchStubAppender attacher(*this); + GenerateDenseElementHole(cx, masm, attacher, ion, obj, idval, object(), index(), output()); + + return linkAndAttachStub(cx, masm, attacher, ion, "dense hole"); +} + /* static */ bool GetElementIC::canAttachTypedArrayElement(JSObject *obj, const Value &idval, TypedOrValueRegister output) @@ -3568,6 +3722,13 @@ GetElementIC::update(JSContext *cx, HandleScript outerScript, size_t cacheIndex, return false; attachedStub = true; } + if (!attachedStub && cache.monitoredResult() && + canAttachDenseElementHole(obj, idval, cache.output())) + { + if (!cache.attachDenseElementHole(cx, outerScript, ion, obj, idval)) + return false; + attachedStub = true; + } if (!attachedStub && canAttachTypedArrayElement(obj, idval, cache.output())) { if (!cache.attachTypedArrayElement(cx, outerScript, ion, obj, idval)) return false; diff --git a/js/src/jit/IonCaches.h b/js/src/jit/IonCaches.h index 3e4941b38549..3bb73ff98042 100644 --- a/js/src/jit/IonCaches.h +++ b/js/src/jit/IonCaches.h @@ -842,6 +842,8 @@ class GetElementIC : public RepatchIonCache static bool canAttachGetProp(JSObject *obj, const Value &idval, jsid id); static bool canAttachDenseElement(JSObject *obj, const Value &idval); + static bool canAttachDenseElementHole(JSObject *obj, const Value &idval, + TypedOrValueRegister output); static bool canAttachTypedArrayElement(JSObject *obj, const Value &idval, TypedOrValueRegister output); @@ -851,6 +853,9 @@ class GetElementIC : public RepatchIonCache bool attachDenseElement(JSContext *cx, HandleScript outerScript, IonScript *ion, HandleObject obj, const Value &idval); + bool attachDenseElementHole(JSContext *cx, HandleScript outerScript, IonScript *ion, + HandleObject obj, const Value &idval); + bool attachTypedArrayElement(JSContext *cx, HandleScript outerScript, IonScript *ion, HandleObject tarr, const Value &idval); diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp index 2b9a374cc418..021a703ca12a 100644 --- a/js/src/vm/TypeInference.cpp +++ b/js/src/vm/TypeInference.cpp @@ -2268,8 +2268,8 @@ TemporaryTypeSet::propertyNeedsBarrier(CompilerConstraintList *constraints, jsid return false; } -static inline bool -ClassCanHaveExtraProperties(const Class *clasp) +bool +js::ClassCanHaveExtraProperties(const Class *clasp) { return clasp->resolve || clasp->ops.lookupProperty diff --git a/js/src/vm/TypeInference.h b/js/src/vm/TypeInference.h index 7aa2c0eca82f..4724704fbb26 100644 --- a/js/src/vm/TypeInference.h +++ b/js/src/vm/TypeInference.h @@ -909,6 +909,9 @@ class TypeNewScript /* Is this a reasonable PC to be doing inlining on? */ inline bool isInlinableCall(jsbytecode *pc); +bool +ClassCanHaveExtraProperties(const Class *clasp); + /* * Whether Array.prototype, or an object on its proto chain, has an * indexed property.