From d6c8fe6f1a1135aa2528b775bbd4662c625c0088 Mon Sep 17 00:00:00 2001 From: Tzvetan Mikov Date: Tue, 7 Jan 2025 21:19:08 -0800 Subject: [PATCH] InstSimplify: optimize equality comparisons based on type Summary: Strict equality of non-overlapping types doesn't need to be performed. Loose equality is trickier, so we constrain ourselves only to comparing against `null|undefined`. Reviewed By: davedets Differential Revision: D67905084 fbshipit-source-id: ca0b7915973962de66ebddd9ecf61981e51d60c0 --- lib/Optimizer/Scalar/InstSimplify.cpp | 26 +++++++-- test/Optimizer/simplify-eq-null-undefined.js | 56 ++++++++++++++++++++ 2 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 test/Optimizer/simplify-eq-null-undefined.js diff --git a/lib/Optimizer/Scalar/InstSimplify.cpp b/lib/Optimizer/Scalar/InstSimplify.cpp index b7b1c15cd07..d6e4d700c0c 100644 --- a/lib/Optimizer/Scalar/InstSimplify.cpp +++ b/lib/Optimizer/Scalar/InstSimplify.cpp @@ -407,6 +407,14 @@ class InstSimplifyImpl { return builder_.createBinaryOperatorInst( lhs, rhs, ValueKind::BinaryStrictlyEqualInstKind); } + + // ~(null|undefined) == null|undefined is always false. + if ((Type::intersectTy(leftTy, kNullOrUndef).isNoType() && + rightTy.isSubsetOf(kNullOrUndef)) || + (Type::intersectTy(rightTy, kNullOrUndef).isNoType() && + leftTy.isSubsetOf(kNullOrUndef))) { + return builder_.getLiteralBool(false); + } break; case ValueKind::BinaryNotEqualInstKind: // != @@ -421,6 +429,14 @@ class InstSimplifyImpl { return builder_.createBinaryOperatorInst( lhs, rhs, ValueKind::BinaryStrictlyNotEqualInstKind); } + + // ~(null|undefined) != null|undefined is always true. + if ((Type::intersectTy(leftTy, kNullOrUndef).isNoType() && + rightTy.isSubsetOf(kNullOrUndef)) || + (Type::intersectTy(rightTy, kNullOrUndef).isNoType() && + leftTy.isSubsetOf(kNullOrUndef))) { + return builder_.getLiteralBool(true); + } break; case ValueKind::BinaryStrictlyEqualInstKind: // === @@ -430,10 +446,8 @@ class InstSimplifyImpl { } // Operands of different types can't be strictly equal. - if (leftTy.isPrimitive() && rightTy.isPrimitive()) { - if (Type::intersectTy(leftTy, rightTy).isNoType()) { - return builder_.getLiteralBool(false); - } + if (Type::intersectTy(leftTy, rightTy).isNoType()) { + return builder_.getLiteralBool(false); } break; @@ -442,6 +456,10 @@ class InstSimplifyImpl { if (identicalForEquality) { return builder_.getLiteralBool(false); } + // Operands of different types can't be strictly equal. + if (Type::intersectTy(leftTy, rightTy).isNoType()) { + return builder_.getLiteralBool(true); + } break; case ValueKind::BinaryLessThanInstKind: // < diff --git a/test/Optimizer/simplify-eq-null-undefined.js b/test/Optimizer/simplify-eq-null-undefined.js new file mode 100644 index 00000000000..0c4a7379a8c --- /dev/null +++ b/test/Optimizer/simplify-eq-null-undefined.js @@ -0,0 +1,56 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// RUN: %hermesc -O -dump-ir %s | %FileCheckOrRegen %s + +// Test equality comparisons against null|undefined when the other type can +// be neither type. + +let o = {}; +sink = o !== null; +sink = o === null; +sink = o != null; +sink = o == null; +sink = undefined !== o; +sink = undefined === o; +sink = undefined != o; +sink = undefined == o; + +let n = +glob; +sink = n !== undefined; +sink = n === undefined; +sink = n != undefined; +sink = n == undefined; +sink = null !== n; +sink = null === n; +sink = null != n; +sink = null == n; + +// Auto-generated content below. Please do not modify manually. + +// CHECK:function global(): boolean +// CHECK-NEXT:%BB0: +// CHECK-NEXT: StorePropertyLooseInst true: boolean, globalObject: object, "sink": string +// CHECK-NEXT: StorePropertyLooseInst false: boolean, globalObject: object, "sink": string +// CHECK-NEXT: StorePropertyLooseInst true: boolean, globalObject: object, "sink": string +// CHECK-NEXT: StorePropertyLooseInst false: boolean, globalObject: object, "sink": string +// CHECK-NEXT: StorePropertyLooseInst true: boolean, globalObject: object, "sink": string +// CHECK-NEXT: StorePropertyLooseInst false: boolean, globalObject: object, "sink": string +// CHECK-NEXT: StorePropertyLooseInst true: boolean, globalObject: object, "sink": string +// CHECK-NEXT: StorePropertyLooseInst false: boolean, globalObject: object, "sink": string +// CHECK-NEXT: %8 = TryLoadGlobalPropertyInst (:any) globalObject: object, "glob": string +// CHECK-NEXT: %9 = AsNumberInst (:number) %8: any +// CHECK-NEXT: StorePropertyLooseInst true: boolean, globalObject: object, "sink": string +// CHECK-NEXT: StorePropertyLooseInst false: boolean, globalObject: object, "sink": string +// CHECK-NEXT: StorePropertyLooseInst true: boolean, globalObject: object, "sink": string +// CHECK-NEXT: StorePropertyLooseInst false: boolean, globalObject: object, "sink": string +// CHECK-NEXT: StorePropertyLooseInst true: boolean, globalObject: object, "sink": string +// CHECK-NEXT: StorePropertyLooseInst false: boolean, globalObject: object, "sink": string +// CHECK-NEXT: StorePropertyLooseInst true: boolean, globalObject: object, "sink": string +// CHECK-NEXT: StorePropertyLooseInst false: boolean, globalObject: object, "sink": string +// CHECK-NEXT: ReturnInst false: boolean +// CHECK-NEXT:function_end