Bug 1695736 - Handle GetElem/SetElem/Has with undefined/null in CacheIR. r=jandem
authorTom Schuster <evilpies@gmail.com>
Wed, 03 Mar 2021 10:12:55 +0000
changeset 569467 0963e86271f75690c758d6f0716d8d0960d9d49e
parent 569466 6f29f36ea34c503e320b8c307eee92345012aa81
child 569468 2ee3856bd2aebb4ae25455088809895fc408a4ac
push id38260
push userapavel@mozilla.com
push dateWed, 03 Mar 2021 21:50:27 +0000
treeherdermozilla-central@46580fa5a4ce [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1695736
milestone88.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 1695736 - Handle GetElem/SetElem/Has with undefined/null in CacheIR. r=jandem Differential Revision: https://phabricator.services.mozilla.com/D106820
js/src/jit-test/tests/cacheir/getelem-undefined-null.js
js/src/jit-test/tests/cacheir/has-undefined-null.js
js/src/jit-test/tests/cacheir/setelem-undefined-null.js
js/src/jit/CacheIR.cpp
js/src/jit/CacheIR.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/cacheir/getelem-undefined-null.js
@@ -0,0 +1,52 @@
+function exists() {
+  var a = {'null': 1, 'undefined': 2};
+  for (var i = 0; i < 100; i++) {
+    assertEq(a[null], 1);
+    assertEq(a[undefined], 2);
+  }
+}
+
+function missing() {
+  var a = {};
+  for (var i = 0; i < 100; i++) {
+    assertEq(a[null], undefined);
+    assertEq(a[undefined], undefined);
+  }
+}
+
+function getter() {
+  var a = {
+    get null() {
+      return 1;
+    },
+    get undefined() {
+      return 2;
+    }
+  }
+  for (var i = 0; i < 100; i++) {
+    assertEq(a[null], 1);
+    assertEq(a[undefined], 2);
+  }
+}
+
+function primitive() {
+  var v = true;
+  for (var i = 0; i < 100; i++) {
+    assertEq(v[null], undefined);
+    assertEq(v[undefined], undefined);
+  }
+}
+
+function mixed() {
+  var a = {'null': 'null', 'undefined': 'undefined'};
+  for (var i = 0; i < 100; i++) {
+    var v = a[i % 2 ? null : undefined]
+    assertEq(a[v], i % 2 ? 'null' : 'undefined');
+  }
+}
+
+exists();
+missing()
+getter();
+primitive();
+mixed();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/cacheir/has-undefined-null.js
@@ -0,0 +1,28 @@
+function exists() {
+  var a = {'null': 0, 'undefined': 0};
+  for (var i = 0; i < 100; i++) {
+    assertEq(null in a, true);
+    assertEq(undefined in a, true);
+  }
+}
+
+function missing() {
+  var a = {};
+  for (var i = 0; i < 100; i++) {
+    assertEq(null in a, false);
+    assertEq(undefined in a, false);
+  }
+}
+
+function mixed() {
+  var x = [{'null': 0}, {'undefined': 0}]
+  for (var i = 0; i < 100; i++) {
+    var a = x[i % 2];
+    assertEq(null in a, i % 2 == 0);
+    assertEq(undefined in a, i % 2 == 1);
+  }
+}
+
+exists();
+missing();
+mixed();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/cacheir/setelem-undefined-null.js
@@ -0,0 +1,50 @@
+function exists() {
+  var a = {'null': 0, 'undefined': 0};
+  for (var i = 0; i < 100; i++) {
+    a[null] = i;
+    a[undefined] = i * 2;
+    assertEq(a['null'], i);
+    assertEq(a['undefined'], i * 2);
+  }
+}
+
+function adding() {
+  for (var i = 0; i < 100; i++) {
+    var a = {};
+    a[null] = i;
+    a[undefined] = i * 2;
+    assertEq(a['null'], i);
+    assertEq(a['undefined'], i * 2);
+  }
+}
+
+function setter() {
+  var test = 0;
+  var a = {
+    set null(v) {
+      test = v;
+    },
+    set undefined(v) {
+      test = v * 2;
+    }
+  }
+  for (var i = 0; i < 100; i++) {
+    a[null] = i;
+    assertEq(test, i);
+    a[undefined] = i;
+    assertEq(test, i * 2);
+  }
+}
+
+function mixed() {
+  var a = {'null': void 0, 'undefined': void 0};
+  for (var i = 0; i < 100; i++) {
+    a[i % 2 ? null : undefined] = i;
+    assertEq(a[i % 2 ? 'null' : 'undefined'], i)
+  }
+}
+
+exists();
+adding()
+setter();
+mixed();
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -310,35 +310,36 @@ static ProxyStubType GetProxyStubType(JS
     return ProxyStubType::DOMShadowed;
   }
 
   MOZ_ASSERT(shadows == DOMProxyShadowsResult::DoesntShadow ||
              shadows == DOMProxyShadowsResult::DoesntShadowUnique);
   return ProxyStubType::DOMUnshadowed;
 }
 
-static bool ValueToNameOrSymbolId(JSContext* cx, HandleValue idval,
+static bool ValueToNameOrSymbolId(JSContext* cx, HandleValue idVal,
                                   MutableHandleId id, bool* nameOrSymbol) {
   *nameOrSymbol = false;
 
-  if (!idval.isString() && !idval.isSymbol()) {
+  if (!idVal.isString() && !idVal.isSymbol() && !idVal.isUndefined() &&
+      !idVal.isNull()) {
     return true;
   }
 
-  if (!PrimitiveValueToId<CanGC>(cx, idval, id)) {
+  if (!PrimitiveValueToId<CanGC>(cx, idVal, id)) {
     return false;
   }
 
-  if (!JSID_IS_STRING(id) && !JSID_IS_SYMBOL(id)) {
+  if (!id.isAtom() && !id.isSymbol()) {
     id.set(JSID_VOID);
     return true;
   }
 
   uint32_t dummy;
-  if (JSID_IS_STRING(id) && JSID_TO_ATOM(id)->isIndex(&dummy)) {
+  if (id.isAtom() && id.toAtom()->isIndex(&dummy)) {
     id.set(JSID_VOID);
     return true;
   }
 
   *nameOrSymbol = true;
   return true;
 }
 
@@ -2642,49 +2643,59 @@ void GetPropIRGenerator::trackAttached(c
 #ifdef JS_CACHEIR_SPEW
   if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
     sp.valueProperty("base", val_);
     sp.valueProperty("property", idVal_);
   }
 #endif
 }
 
-void IRGenerator::emitIdGuard(ValOperandId valId, jsid id) {
-  if (JSID_IS_SYMBOL(id)) {
+void IRGenerator::emitIdGuard(ValOperandId valId, HandleValue idVal, jsid id) {
+  if (id.isSymbol()) {
+    MOZ_ASSERT(idVal.toSymbol() == id.toSymbol());
     SymbolOperandId symId = writer.guardToSymbol(valId);
-    writer.guardSpecificSymbol(symId, JSID_TO_SYMBOL(id));
-  } else {
-    MOZ_ASSERT(JSID_IS_ATOM(id));
-    StringOperandId strId = writer.guardToString(valId);
-    writer.guardSpecificAtom(strId, JSID_TO_ATOM(id));
+    writer.guardSpecificSymbol(symId, id.toSymbol());
+  } else {
+    MOZ_ASSERT(id.isAtom());
+    if (idVal.isUndefined()) {
+      MOZ_ASSERT(id.isAtom(cx_->names().undefined));
+      writer.guardIsUndefined(valId);
+    } else if (idVal.isNull()) {
+      MOZ_ASSERT(id.isAtom(cx_->names().null));
+      writer.guardIsNull(valId);
+    } else {
+      MOZ_ASSERT(idVal.isString());
+      StringOperandId strId = writer.guardToString(valId);
+      writer.guardSpecificAtom(strId, id.toAtom());
+    }
   }
 }
 
 void GetPropIRGenerator::maybeEmitIdGuard(jsid id) {
   if (cacheKind_ == CacheKind::GetProp ||
       cacheKind_ == CacheKind::GetPropSuper) {
     // Constant PropertyName, no guards necessary.
     MOZ_ASSERT(&idVal_.toString()->asAtom() == JSID_TO_ATOM(id));
     return;
   }
 
   MOZ_ASSERT(cacheKind_ == CacheKind::GetElem ||
              cacheKind_ == CacheKind::GetElemSuper);
-  emitIdGuard(getElemKeyValueId(), id);
+  emitIdGuard(getElemKeyValueId(), idVal_, id);
 }
 
 void SetPropIRGenerator::maybeEmitIdGuard(jsid id) {
   if (cacheKind_ == CacheKind::SetProp) {
     // Constant PropertyName, no guards necessary.
     MOZ_ASSERT(&idVal_.toString()->asAtom() == JSID_TO_ATOM(id));
     return;
   }
 
   MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
-  emitIdGuard(setElemKeyValueId(), id);
+  emitIdGuard(setElemKeyValueId(), idVal_, id);
 }
 
 GetNameIRGenerator::GetNameIRGenerator(JSContext* cx, HandleScript script,
                                        jsbytecode* pc, ICState::Mode mode,
                                        HandleObject env,
                                        HandlePropertyName name)
     : IRGenerator(cx, script, pc, CacheKind::GetName, mode),
       env_(env),
@@ -3244,17 +3255,17 @@ AttachDecision HasPropIRGenerator::tryAt
     return AttachDecision::NoAction;
   }
 
   if (!IsCacheableProtoChain(obj, holder)) {
     return AttachDecision::NoAction;
   }
 
   Maybe<ObjOperandId> tempId;
-  emitIdGuard(keyId, key);
+  emitIdGuard(keyId, idVal_, key);
   EmitReadSlotGuard(writer, obj, holder, objId, &tempId);
   writer.loadBooleanResult(true);
   writer.returnFromIC();
 
   trackAttached("NativeHasProp");
   return AttachDecision::Attach;
 }
 
@@ -3279,17 +3290,17 @@ AttachDecision HasPropIRGenerator::tryAt
   trackAttached("TypedArrayObject");
   return AttachDecision::Attach;
 }
 
 AttachDecision HasPropIRGenerator::tryAttachSlotDoesNotExist(
     JSObject* obj, ObjOperandId objId, jsid key, ValOperandId keyId) {
   bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
 
-  emitIdGuard(keyId, key);
+  emitIdGuard(keyId, idVal_, key);
   if (hasOwn) {
     TestMatchingReceiver(writer, obj, objId);
   } else {
     Maybe<ObjOperandId> tempId;
     EmitReadSlotGuard(writer, obj, nullptr, objId, &tempId);
   }
   writer.loadBooleanResult(false);
   writer.returnFromIC();
@@ -3446,17 +3457,17 @@ AttachDecision CheckPrivateFieldIRGenera
                                                              jsid key,
                                                              ValOperandId keyId,
                                                              bool hasOwn) {
   if (!obj->is<NativeObject>()) {
     return AttachDecision::NoAction;
   }
 
   Maybe<ObjOperandId> tempId;
-  emitIdGuard(keyId, key);
+  emitIdGuard(keyId, idVal_, key);
   EmitReadSlotGuard(writer, obj, obj, objId, &tempId);
   writer.loadBooleanResult(hasOwn);
   writer.returnFromIC();
 
   trackAttached("NativeCheckPrivateField");
   return AttachDecision::Attach;
 }
 
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -1247,17 +1247,17 @@ class MOZ_RAII IRGenerator {
   IntPtrOperandId guardToIntPtrIndex(const Value& index, ValOperandId indexId,
                                      bool supportOOB);
 
   ObjOperandId guardDOMProxyExpandoObjectAndShape(JSObject* obj,
                                                   ObjOperandId objId,
                                                   const Value& expandoVal,
                                                   JSObject* expandoObj);
 
-  void emitIdGuard(ValOperandId valId, jsid id);
+  void emitIdGuard(ValOperandId valId, HandleValue idVal, jsid id);
 
   OperandId emitNumericGuard(ValOperandId valId, Scalar::Type type);
 
   friend class CacheIRSpewer;
 
  public:
   explicit IRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
                        CacheKind cacheKind, ICState::Mode mode);