From a8fa6406880b56a4f7fe5f63caecc92b3780c37f Mon Sep 17 00:00:00 2001 From: Cameron Kaiser Date: Mon, 29 Oct 2018 19:11:29 -0700 Subject: [PATCH] #529: jsop_in folding M1244098 M1041586 (partial) --- js/public/TrackedOptimizationInfo.h | 2 + js/src/jit-test/tests/ion/fold-in.js | 32 +++++ js/src/jit-test/tests/ion/recover-objects.js | 4 +- js/src/jit/IonBuilder.cpp | 143 +++++++++++++++++-- js/src/jit/IonBuilder.h | 10 +- 5 files changed, 173 insertions(+), 18 deletions(-) create mode 100644 js/src/jit-test/tests/ion/fold-in.js diff --git a/js/public/TrackedOptimizationInfo.h b/js/public/TrackedOptimizationInfo.h index ee9931278..061ef0c29 100644 --- a/js/public/TrackedOptimizationInfo.h +++ b/js/public/TrackedOptimizationInfo.h @@ -16,6 +16,7 @@ namespace JS { _(GetProp_ArgumentsCallee) \ _(GetProp_InferredConstant) \ _(GetProp_Constant) \ + _(GetProp_NotDefined) \ _(GetProp_StaticName) \ _(GetProp_SimdGetter) \ _(GetProp_TypedObject) \ @@ -74,6 +75,7 @@ namespace JS { _(NotObject) \ _(NotStruct) \ _(NotUnboxed) \ + _(NotUndefined) \ _(UnboxedConvertedToNative) \ _(StructNoField) \ _(InconsistentFieldType) \ diff --git a/js/src/jit-test/tests/ion/fold-in.js b/js/src/jit-test/tests/ion/fold-in.js new file mode 100644 index 000000000..cc48588b9 --- /dev/null +++ b/js/src/jit-test/tests/ion/fold-in.js @@ -0,0 +1,32 @@ +// Singleton +function f() { + var res = 0; + for (var i=0; i<500; i++) + res += ("abcd" in Math); + return res; +} +assertEq(f(), 0); +Math.abcd = 3; +assertEq(f(), 500); +delete Math.abcd; +assertEq(f(), 0); + +// Non-singleton +function O(x) { if (x) this.x = 1; } + +var arr = []; +for (var i=0; i<4; i++) + arr.push(new O(i % 2)); + +function g(arr) { + var res = 0; + for (var i=0; i<500; i++) { + var o = arr[i % arr.length]; + res += "x" in o; + res += "abcd" in o; + } + return res; +} +assertEq(g(arr), 250); +arr[0].abcd = 3; +assertEq(g(arr), 375); diff --git a/js/src/jit-test/tests/ion/recover-objects.js b/js/src/jit-test/tests/ion/recover-objects.js index 917e84fa8..df2e12bef 100644 --- a/js/src/jit-test/tests/ion/recover-objects.js +++ b/js/src/jit-test/tests/ion/recover-objects.js @@ -135,9 +135,9 @@ function withinIf(i) { // Check case where one successor can have multiple times the same predecessor. function unknownLoad(i) { var obj = { foo: i }; + // Unknown properties are inlined as undefined. assertEq(obj.bar, undefined); - // Unknown properties are using GetPropertyCache. - assertRecoveredOnBailout(obj, false); + assertRecoveredOnBailout(obj, true); } // Check with dynamic slots. diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index a57760bcb..f62358572 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -7928,6 +7928,51 @@ IonBuilder::testSingletonPropertyTypes(MDefinition* obj, jsid id) return nullptr; } +bool +IonBuilder::testNotDefinedProperty(MDefinition* obj, jsid id) +{ + TemporaryTypeSet* types = obj->resultTypeSet(); + if (!types || types->unknownObject() || types->getKnownMIRType() != MIRType_Object) + return false; + + for (unsigned i = 0, count = types->getObjectCount(); i < count; i++) { + TypeSet::ObjectKey* key = types->getObject(i); + if (!key) + continue; + + while (true) { + if (!key->hasStableClassAndProto(constraints()) || key->unknownProperties()) + return false; + + const Class* clasp = key->clasp(); + if (!ClassHasEffectlessLookup(clasp) || ObjectHasExtraOwnProperty(compartment, key, +id)) + return false; + + // If the object is a singleton, we can do a lookup now to avoid + // unnecessary invalidations later on, in case the property types + // have not yet been instantiated. + if (key->isSingleton() && + key->singleton()->is() && + key->singleton()->as().lookupPure(id)) + { + return false; + } + + HeapTypeSetKey property = key->property(id); + if (property.isOwnProperty(constraints())) + return false; + + JSObject* proto = checkNurseryObject(key->proto().toObjectOrNull()); + if (!proto) + break; + key = TypeSet::ObjectKey::get(proto); + } + } + + return true; +} + bool IonBuilder::pushTypeBarrier(MDefinition* def, TemporaryTypeSet* observed, BarrierKind kind) { @@ -8931,6 +8976,13 @@ IonBuilder::getElemTryGetProp(bool* emitted, MDefinition* obj, MDefinition* inde return *emitted; } + trackOptimizationAttempt(TrackedStrategy::GetProp_NotDefined); + if (!getPropTryNotDefined(emitted, obj, id, types) || *emitted) { + if (*emitted) + index->setImplicitlyUsedUnchecked(); + return *emitted; + } + return true; } @@ -10966,6 +11018,11 @@ IonBuilder::jsop_getprop(PropertyName* name) if (!getPropTryConstant(&emitted, obj, NameToId(name), types) || emitted) return emitted; + // Try to hardcode known not-defined + trackOptimizationAttempt(TrackedStrategy::GetProp_NotDefined); + if (!getPropTryNotDefined(&emitted, obj, NameToId(name), types) || emitted) + return emitted; + // Try to emit SIMD getter loads trackOptimizationAttempt(TrackedStrategy::GetProp_SimdGetter); if (!getPropTrySimdGetter(&emitted, obj, name) || emitted) @@ -11212,6 +11269,30 @@ IonBuilder::getPropTryConstant(bool* emitted, MDefinition* obj, jsid id, Tempora return true; } +bool +IonBuilder::getPropTryNotDefined(bool* emitted, MDefinition* obj, jsid id, TemporaryTypeSet* types) +{ + MOZ_ASSERT(*emitted == false); + + if (!types->mightBeMIRType(MIRType_Undefined)) { + // Only optimize if we expect this property access to return undefined. + trackOptimizationOutcome(TrackedOutcome::NotUndefined); + return true; + } + + if (!testNotDefinedProperty(obj, id)) { + trackOptimizationOutcome(TrackedOutcome::GenericFailure); + return true; + } + + obj->setImplicitlyUsedUnchecked(); + pushConstant(UndefinedValue()); + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + MIRType IonBuilder::SimdTypeDescrToMIRType(SimdTypeDescr::Type type) { @@ -13289,21 +13370,13 @@ IonBuilder::jsop_in() MDefinition* obj = convertUnboxedObjects(current->pop()); MDefinition* id = current->pop(); - do { - if (shouldAbortOnPreliminaryGroups(obj)) - break; + bool emitted = false; - JSValueType unboxedType = UnboxedArrayElementType(constraints(), obj, id); - if (unboxedType == JSVAL_TYPE_MAGIC) { - if (!ElementAccessIsDenseNative(constraints(), obj, id)) - break; - } + if (!inTryDense(&emitted, obj, id) || emitted) + return emitted; - if (ElementAccessHasExtraIndexedProperty(this, obj)) - break; - - return jsop_in_dense(obj, id, unboxedType); - } while (false); + if (!inTryFold(&emitted, obj, id) || emitted) + return emitted; MIn* ins = MIn::New(alloc(), id, obj); @@ -13314,8 +13387,24 @@ IonBuilder::jsop_in() } bool -IonBuilder::jsop_in_dense(MDefinition* obj, MDefinition* id, JSValueType unboxedType) +IonBuilder::inTryDense(bool* emitted, MDefinition* obj, MDefinition* id) { + MOZ_ASSERT(!*emitted); + + if (shouldAbortOnPreliminaryGroups(obj)) + return true; + + JSValueType unboxedType = UnboxedArrayElementType(constraints(), obj, id); + if (unboxedType == JSVAL_TYPE_MAGIC) { + if (!ElementAccessIsDenseNative(constraints(), obj, id)) + return true; + } + + if (ElementAccessHasExtraIndexedProperty(this, obj)) + return true; + + *emitted = true; + bool needsHoleCheck = !ElementAccessIsPacked(constraints(), obj); // Ensure id is an integer. @@ -13345,6 +13434,32 @@ IonBuilder::jsop_in_dense(MDefinition* obj, MDefinition* id, JSValueType unboxed return true; } +bool +IonBuilder::inTryFold(bool* emitted, MDefinition* obj, MDefinition* id) +{ + // Fold |id in obj| to |false|, if we know the object (or an object on its + // prototype chain) does not have this property. + + MOZ_ASSERT(!*emitted); + + jsid propId; + if (!id->isConstantValue() || !ValueToIdPure(id->constantValue(), &propId)) + return true; + + if (propId != IdToTypeId(propId)) + return true; + + if (!testNotDefinedProperty(obj, propId)) + return true; + + *emitted = true; + + pushConstant(BooleanValue(false)); + obj->setImplicitlyUsedUnchecked(); + id->setImplicitlyUsedUnchecked(); + return true; +} + bool IonBuilder::hasOnProtoChain(TypeSet::ObjectKey* key, JSObject* protoObject, bool* hasOnProto) { diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index a41b3ae01..d15004cc3 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -431,6 +431,7 @@ class IonBuilder bool getPropTryArgumentsLength(bool* emitted, MDefinition* obj); bool getPropTryArgumentsCallee(bool* emitted, MDefinition* obj, PropertyName* name); bool getPropTryConstant(bool* emitted, MDefinition* obj, jsid id, TemporaryTypeSet* types); + bool getPropTryNotDefined(bool* emitted, MDefinition* obj, jsid id, TemporaryTypeSet* types); bool getPropTryDefiniteSlot(bool* emitted, MDefinition* obj, PropertyName* name, BarrierKind barrier, TemporaryTypeSet* types); bool getPropTryModuleNamespace(bool* emitted, MDefinition* obj, PropertyName* name, @@ -503,13 +504,17 @@ class IonBuilder // jsop_bitnot helpers. bool bitnotTrySpecialized(bool* emitted, MDefinition* input); - // jsop_compare helpes. + // jsop_compare helpers. bool compareTrySpecialized(bool* emitted, JSOp op, MDefinition* left, MDefinition* right); bool compareTryBitwise(bool* emitted, JSOp op, MDefinition* left, MDefinition* right); bool compareTrySpecializedOnBaselineInspector(bool* emitted, JSOp op, MDefinition* left, MDefinition* right); bool compareTrySharedStub(bool* emitted, JSOp op, MDefinition* left, MDefinition* right); + // jsop_in helpers. + bool inTryDense(bool* emitted, MDefinition* obj, MDefinition* id); + bool inTryFold(bool* emitted, MDefinition* obj, MDefinition* id); + // binary data lookup helpers. TypedObjectPrediction typedObjectPrediction(MDefinition* typedObj); TypedObjectPrediction typedObjectPrediction(TemporaryTypeSet* types); @@ -732,7 +737,6 @@ class IonBuilder bool jsop_isnoiter(); bool jsop_iterend(); bool jsop_in(); - bool jsop_in_dense(MDefinition* obj, MDefinition* id, JSValueType unboxedType); bool jsop_instanceof(); bool jsop_getaliasedvar(ScopeCoordinate sc); bool jsop_setaliasedvar(ScopeCoordinate sc); @@ -980,6 +984,8 @@ class IonBuilder JSObject* testSingletonProperty(JSObject* obj, jsid id); JSObject* testSingletonPropertyTypes(MDefinition* obj, jsid id); + bool testNotDefinedProperty(MDefinition* obj, jsid id); + uint32_t getDefiniteSlot(TemporaryTypeSet* types, PropertyName* name, uint32_t* pnfixed); MDefinition* convertUnboxedObjects(MDefinition* obj); MDefinition* convertUnboxedObjects(MDefinition* obj,