Bug 1501328 - CacheIR stub for out-of-bounds indexed GetElement on Arrays. r=mgaudet
authorKannan Vijayan <kvijayan@mozilla.com>
Wed, 24 Oct 2018 23:51:54 -0400
changeset 491210 d12102a7cea7849009ff99668c23a3d7626e47f7
parent 491209 109ff0694f9a131d47f08dbe25c941cbb0f014b6
child 491250 41a7fd6bfe54e1afcbf5e25eb057602debc1dce9
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersmgaudet
bugs1501328
milestone65.0a1
Bug 1501328 - CacheIR stub for out-of-bounds indexed GetElement on Arrays. r=mgaudet
js/src/jit/BaselineCacheIRCompiler.cpp
js/src/jit/CacheIR.cpp
js/src/jit/CacheIR.h
js/src/jit/IonCacheIRCompiler.cpp
js/src/jit/VMFunctions.cpp
js/src/jit/VMFunctions.h
js/src/vm/NativeObject.cpp
js/src/vm/NativeObject.h
--- a/js/src/jit/BaselineCacheIRCompiler.cpp
+++ b/js/src/jit/BaselineCacheIRCompiler.cpp
@@ -1893,16 +1893,37 @@ BaselineCacheIRCompiler::emitCallAddOrUp
 
     if (!callVM(masm, AddOrUpdateSparseElementHelperInfo)) {
         return false;
     }
     stubFrame.leave(masm);
     return true;
 }
 
+bool
+BaselineCacheIRCompiler::emitCallGetSparseElementResult()
+{
+    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    Register id = allocator.useRegister(masm, reader.int32OperandId());
+    AutoScratchRegister scratch(allocator, masm);
+
+    allocator.discardStack(masm);
+
+    AutoStubFrame stubFrame(*this);
+    stubFrame.enter(masm, scratch);
+
+    masm.Push(id);
+    masm.Push(obj);
+
+    if (!callVM(masm, GetSparseElementHelperInfo)) {
+        return false;
+    }
+    stubFrame.leave(masm);
+    return true;
+}
 
 bool
 BaselineCacheIRCompiler::emitMegamorphicSetElement()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     ValueOperand idVal = allocator.useValueRegister(masm, reader.valOperandId());
     ValueOperand val = allocator.useValueRegister(masm, reader.valOperandId());
     bool strict = reader.readBool();
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -279,16 +279,19 @@ GetPropIRGenerator::tryAttachStub()
                 return true;
             }
             if (tryAttachDenseElement(obj, objId, index, indexId)) {
                 return true;
             }
             if (tryAttachDenseElementHole(obj, objId, index, indexId)) {
                 return true;
             }
+            if (tryAttachSparseElement(obj, objId, index, indexId)) {
+                return true;
+            }
             if (tryAttachUnboxedElementHole(obj, objId, index, indexId)) {
                 return true;
             }
             if (tryAttachArgumentsObjectArg(obj, objId, indexId)) {
                 return true;
             }
             if (tryAttachGenericElement(obj, objId, index, indexId)) {
                 return true;
@@ -2208,16 +2211,80 @@ GetPropIRGenerator::tryAttachDenseElemen
     GeneratePrototypeHoleGuards(writer, nobj, objId);
     writer.loadDenseElementHoleResult(objId, indexId);
     writer.typeMonitorResult();
 
     trackAttached("DenseElementHole");
     return true;
 }
 
+bool
+GetPropIRGenerator::tryAttachSparseElement(HandleObject obj, ObjOperandId objId,
+                                           uint32_t index, Int32OperandId indexId)
+{
+    if (!obj->isNative()) {
+        return false;
+    }
+    NativeObject* nobj = &obj->as<NativeObject>();
+
+    // Stub doesn't handle negative indices.
+    if (index > INT_MAX) {
+        return false;
+    }
+
+    // We also need to be past the end of the dense capacity, to ensure sparse.
+    if (index < nobj->getDenseInitializedLength()) {
+        return false;
+    }
+
+    // Only handle Array objects in this stub.
+    if (!nobj->is<ArrayObject>()) {
+        return false;
+    }
+
+    // Here, we ensure that the prototype chain does not define any sparse
+    // indexed properties on the shape lineage. This allows us to guard on
+    // the shapes up the prototype chain to ensure that no indexed properties
+    // exist outside of the dense elements.
+    //
+    // The `GeneratePrototypeHoleGuards` call below will guard on the shapes,
+    // as well as ensure that no prototypes contain dense elements, allowing
+    // us to perform a pure shape-search for out-of-bounds integer-indexed
+    // properties on the recevier object.
+    if ((nobj->staticPrototype() != nullptr) &&
+        ObjectMayHaveExtraIndexedProperties(nobj->staticPrototype()))
+    {
+        return false;
+    }
+
+    // Ensure that obj is an Array.
+    writer.guardClass(objId, GuardClassKind::Array);
+
+    // The helper we are going to call only applies to non-dense elements.
+    writer.guardIndexGreaterThanDenseInitLength(objId, indexId);
+
+    // Ensures we are able to efficiently able to map to an integral jsid.
+    writer.guardIndexIsNonNegative(indexId);
+
+    // Shape guard the prototype chain to avoid shadowing indexes from appearing.
+    // The helper function also ensures that the index does not appear within the
+    // dense element set of the prototypes.
+    GeneratePrototypeHoleGuards(writer, nobj, objId);
+
+    // At this point, we are guaranteed that the indexed property will not
+    // be found on one of the prototypes. We are assured that we only have
+    // to check that the receiving object has the property.
+
+    writer.callGetSparseElementResult(objId, indexId);
+    writer.typeMonitorResult();
+
+    trackAttached("GetSparseElement");
+    return true;
+}
+
 static bool
 IsPrimitiveArrayTypedObject(JSObject* obj)
 {
     if (!obj->is<TypedObject>()) {
         return false;
     }
     TypeDescr& descr = obj->as<TypedObject>().typeDescr();
     return descr.is<ArrayTypeDescr>() &&
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -277,16 +277,17 @@ extern const char* const CacheKindNames[
                                           \
     /* The *Result ops load a value into the cache's result register. */ \
     _(LoadFixedSlotResult)                \
     _(LoadDynamicSlotResult)              \
     _(LoadUnboxedPropertyResult)          \
     _(LoadTypedObjectResult)              \
     _(LoadDenseElementResult)             \
     _(LoadDenseElementHoleResult)         \
+    _(CallGetSparseElementResult)         \
     _(LoadDenseElementExistsResult)       \
     _(LoadTypedElementExistsResult)       \
     _(LoadDenseElementHoleExistsResult)   \
     _(LoadTypedElementResult)             \
     _(LoadInt32ArrayLengthResult)         \
     _(LoadArgumentsObjectArgResult)       \
     _(LoadArgumentsObjectLengthResult)    \
     _(LoadFunctionLengthResult)           \
@@ -1233,16 +1234,20 @@ class MOZ_RAII CacheIRWriter : public JS
     void loadDenseElementResult(ObjOperandId obj, Int32OperandId index) {
         writeOpWithOperandId(CacheOp::LoadDenseElementResult, obj);
         writeOperandId(index);
     }
     void loadDenseElementHoleResult(ObjOperandId obj, Int32OperandId index) {
         writeOpWithOperandId(CacheOp::LoadDenseElementHoleResult, obj);
         writeOperandId(index);
     }
+    void callGetSparseElementResult(ObjOperandId obj, Int32OperandId index) {
+        writeOpWithOperandId(CacheOp::CallGetSparseElementResult, obj);
+        writeOperandId(index);
+    }
     void loadDenseElementExistsResult(ObjOperandId obj, Int32OperandId index) {
         writeOpWithOperandId(CacheOp::LoadDenseElementExistsResult, obj);
         writeOperandId(index);
     }
     void loadTypedElementExistsResult(ObjOperandId obj, Int32OperandId index, TypedThingLayout layout) {
         writeOpWithOperandId(CacheOp::LoadTypedElementExistsResult, obj);
         writeOperandId(index);
         buffer_.writeByte(uint32_t(layout));
@@ -1595,16 +1600,18 @@ class MOZ_RAII GetPropIRGenerator : publ
     bool tryAttachMagicArgument(ValOperandId valId, ValOperandId indexId);
     bool tryAttachArgumentsObjectArg(HandleObject obj, ObjOperandId objId,
                                      Int32OperandId indexId);
 
     bool tryAttachDenseElement(HandleObject obj, ObjOperandId objId,
                                uint32_t index, Int32OperandId indexId);
     bool tryAttachDenseElementHole(HandleObject obj, ObjOperandId objId,
                                    uint32_t index, Int32OperandId indexId);
+    bool tryAttachSparseElement(HandleObject obj, ObjOperandId objId,
+                                uint32_t index, Int32OperandId indexId);
     bool tryAttachTypedElement(HandleObject obj, ObjOperandId objId,
                                uint32_t index, Int32OperandId indexId);
     bool tryAttachUnboxedElementHole(HandleObject obj, ObjOperandId objId,
                                      uint32_t index, Int32OperandId indexId);
 
     bool tryAttachGenericElement(HandleObject obj, ObjOperandId objId,
                                  uint32_t index, Int32OperandId indexId);
 
--- a/js/src/jit/IonCacheIRCompiler.cpp
+++ b/js/src/jit/IonCacheIRCompiler.cpp
@@ -2281,16 +2281,38 @@ IonCacheIRCompiler::emitCallAddOrUpdateS
     masm.Push(Imm32(strict));
     masm.Push(val);
     masm.Push(id);
     masm.Push(obj);
 
     return callVM(masm, AddOrUpdateSparseElementHelperInfo);
 }
 
+bool
+IonCacheIRCompiler::emitCallGetSparseElementResult()
+{
+    AutoSaveLiveRegisters save(*this);
+    AutoOutputRegister output(*this);
+
+    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    Register id = allocator.useRegister(masm, reader.int32OperandId());
+
+    allocator.discardStack(masm);
+    prepareVMCall(masm, save);
+    masm.Push(id);
+    masm.Push(obj);
+
+    if (!callVM(masm, GetSparseElementHelperInfo)) {
+        return false;
+    }
+
+    masm.storeCallResultValue(output);
+    return true;
+}
+
 
 bool
 IonCacheIRCompiler::emitMegamorphicSetElement()
 {
     AutoSaveLiveRegisters save(*this);
 
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     ConstantOrRegister idVal = allocator.useConstantOrRegister(masm, reader.valOperandId());
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -2080,10 +2080,15 @@ typedef bool (*NativeGetElementFn)(JSCon
 const VMFunction NativeGetElementInfo =
     FunctionInfo<NativeGetElementFn>(NativeGetElement, "NativeGetProperty");
 
 typedef bool (*AddOrUpdateSparseElementHelperFn)(JSContext* cx, HandleArrayObject obj,
                                                  int32_t int_id, HandleValue v, bool strict);
 const VMFunction AddOrUpdateSparseElementHelperInfo =
     FunctionInfo<AddOrUpdateSparseElementHelperFn>(AddOrUpdateSparseElementHelper, "AddOrUpdateSparseElementHelper");
 
+typedef bool (*GetSparseElementHelperFn)(JSContext* cx, HandleArrayObject obj,
+                                         int32_t int_id, MutableHandleValue result);
+const VMFunction GetSparseElementHelperInfo =
+    FunctionInfo<GetSparseElementHelperFn>(GetSparseElementHelper, "getSparseElementHelper");
+
 } // namespace jit
 } // namespace js
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -978,16 +978,17 @@ extern const VMFunction ProxyGetProperty
 extern const VMFunction ProxySetPropertyInfo;
 extern const VMFunction ProxySetPropertyByValueInfo;
 extern const VMFunction ProxyHasInfo;
 extern const VMFunction ProxyHasOwnInfo;
 
 extern const VMFunction NativeGetElementInfo;
 
 extern const VMFunction AddOrUpdateSparseElementHelperInfo;
+extern const VMFunction GetSparseElementHelperInfo;
 
 // TailCall VMFunctions
 extern const VMFunction DoConcatStringObjectInfo;
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_VMFunctions_h */
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -2529,16 +2529,42 @@ GeneralizedGetProperty(JSContext* cx, JS
         return false;
     }
     if (nameLookup) {
         return false;
     }
     return GetPropertyNoGC(cx, obj, receiver, id, vp.address());
 }
 
+bool
+js::GetSparseElementHelper(JSContext* cx, HandleArrayObject obj, int32_t int_id,
+                           MutableHandleValue result)
+{
+    // Callers should have ensured that this object has a static prototype.
+    MOZ_ASSERT(obj->hasStaticPrototype());
+
+    // Indexed properties can not exist on the prototype chain.
+    MOZ_ASSERT_IF(obj->staticPrototype() != nullptr,
+                  !ObjectMayHaveExtraIndexedProperties(obj->staticPrototype()));
+
+    MOZ_ASSERT(INT_FITS_IN_JSID(int_id));
+    RootedId id(cx, INT_TO_JSID(int_id));
+
+    Shape* rawShape = obj->lastProperty()->search(cx, id);
+    if (!rawShape) {
+        // Property not found, return directly.
+        result.setUndefined();
+        return true;
+    }
+
+    RootedValue receiver(cx, ObjectValue(*obj));
+    RootedShape shape(cx, rawShape);
+    return GetExistingProperty<CanGC>(cx, receiver, obj, shape, result);
+}
+
 template <AllowGC allowGC>
 static MOZ_ALWAYS_INLINE bool
 NativeGetPropertyInline(JSContext* cx,
                         typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
                         typename MaybeRooted<Value, allowGC>::HandleType receiver,
                         typename MaybeRooted<jsid, allowGC>::HandleType id,
                         IsNameLookup nameLookup,
                         typename MaybeRooted<Value, allowGC>::MutableHandleType vp)
--- a/js/src/vm/NativeObject.h
+++ b/js/src/vm/NativeObject.h
@@ -1610,16 +1610,20 @@ NativeGetProperty(JSContext* cx, HandleN
     return NativeGetProperty(cx, obj, receiver, id, vp);
 }
 
 extern bool
 NativeGetElement(JSContext* cx, HandleNativeObject obj, HandleValue reciever, int32_t index,
                  MutableHandleValue vp);
 
 bool
+GetSparseElementHelper(JSContext* cx, HandleArrayObject obj, int32_t int_id,
+                       MutableHandleValue result);
+
+bool
 SetPropertyByDefining(JSContext* cx, HandleId id, HandleValue v, HandleValue receiver,
                       ObjectOpResult& result);
 
 bool
 SetPropertyOnProto(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
                    HandleValue receiver, ObjectOpResult& result);
 
 bool