-
Notifications
You must be signed in to change notification settings - Fork 869
ConstraintAnalysis: Sort constraints internally #8900
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
eac1f8d
0ed4896
ebaf4ce
efec239
7c27fe6
c92f2fa
a831f55
16a16e4
bf1f559
6074460
8f510a2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -159,6 +159,9 @@ struct FuncData { | |
| bool operator==(const FuncData& other) const { | ||
| return name == other.name && self == other.self; | ||
| } | ||
| bool operator<(const FuncData& other) const { | ||
| return std::tie(name, self) < std::tie(other.name, other.self); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. neat trick!
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
|
|
||
| Flow doCall(const Literals& arguments) { | ||
| assert(call); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -520,6 +520,59 @@ bool Literal::operator!=(const Literal& other) const { | |
| return !(*this == other); | ||
| } | ||
|
|
||
| bool Literal::operator<(const Literal& other) const { | ||
| if (type != other.type) { | ||
| // This is not deterministic between runs, and also FuncData, below. If this | ||
| // matters some day, we would need to find a stable way to compute it. | ||
| return type.getID() < other.type.getID(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not going to be deterministic, which may or may not be a problem. We could alternatively do a breadth-first comparison on the structure of the value. Probably not worth implementing now, but we can add a TODO comment about it.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, this is not deterministic between runs, the FuncData too. I'll add a comment. |
||
| } | ||
|
|
||
| if (type.isBasic()) { | ||
| switch (type.getBasic()) { | ||
| case Type::none: | ||
| return false; | ||
| case Type::i32: | ||
| case Type::f32: | ||
| return i32 < other.i32; | ||
| case Type::i64: | ||
| case Type::f64: | ||
| return i64 < other.i64; | ||
| case Type::v128: | ||
| return memcmp(v128, other.v128, 16) < 0; | ||
| case Type::unreachable: | ||
| WASM_UNREACHABLE("invalid literal type"); | ||
| } | ||
| } | ||
|
|
||
| assert(type.isRef()); | ||
| if (type.isNull()) { | ||
| // All nulls are equal, and hence not < | ||
| return false; | ||
| } | ||
| if (type.isFunction()) { | ||
| return *funcData < *other.funcData; | ||
| } | ||
| if (type.isData() || type.isString()) { | ||
| return gcData < other.gcData; | ||
| } | ||
| auto heapType = type.getHeapType(); | ||
| assert(heapType.isBasic()); | ||
| if (heapType.isMaybeShared(HeapType::i31)) { | ||
| return i32 < other.i32; | ||
| } | ||
| if (heapType.isMaybeShared(HeapType::ext)) { | ||
| if (hasExternPayload() != other.hasExternPayload()) { | ||
| return hasExternPayload() < other.hasExternPayload(); | ||
| } | ||
| if (hasExternPayload()) { | ||
| return getExternPayload() < other.getExternPayload(); | ||
| } | ||
| return internalize() < other.internalize(); | ||
| } | ||
| assert(heapType.isMaybeShared(HeapType::any)); | ||
| return externalize() < other.externalize(); | ||
| } | ||
|
|
||
| bool Literal::isNaN() { | ||
| if (type == Type::f32 && std::isnan(getf32())) { | ||
| return true; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3544,5 +3544,89 @@ | |
| (then) | ||
| ) | ||
| ) | ||
|
|
||
| ;; CHECK: (func $iloop (type $8) (param $0 f32) | ||
| ;; CHECK-NEXT: (local $1 f32) | ||
| ;; CHECK-NEXT: (local.set $0 | ||
| ;; CHECK-NEXT: (local.get $1) | ||
| ;; CHECK-NEXT: ) | ||
| ;; CHECK-NEXT: (if | ||
| ;; CHECK-NEXT: (i32.const 0) | ||
| ;; CHECK-NEXT: (then | ||
| ;; CHECK-NEXT: (loop | ||
| ;; CHECK-NEXT: (local.set $1 | ||
| ;; CHECK-NEXT: (local.get $0) | ||
| ;; CHECK-NEXT: ) | ||
| ;; CHECK-NEXT: ) | ||
| ;; CHECK-NEXT: ) | ||
| ;; CHECK-NEXT: (else | ||
| ;; CHECK-NEXT: ) | ||
| ;; CHECK-NEXT: ) | ||
| ;; CHECK-NEXT: (loop $label | ||
| ;; CHECK-NEXT: (loop | ||
| ;; CHECK-NEXT: ) | ||
| ;; CHECK-NEXT: (br_if $label | ||
| ;; CHECK-NEXT: (i32.const 0) | ||
| ;; CHECK-NEXT: ) | ||
| ;; CHECK-NEXT: ) | ||
| ;; CHECK-NEXT: ) | ||
| ;; OPTIN: (func $iloop (type $8) (param $0 f32) | ||
| ;; OPTIN-NEXT: (local $1 f32) | ||
| ;; OPTIN-NEXT: (local.set $0 | ||
| ;; OPTIN-NEXT: (local.get $1) | ||
| ;; OPTIN-NEXT: ) | ||
| ;; OPTIN-NEXT: (if | ||
| ;; OPTIN-NEXT: (i32.const 0) | ||
| ;; OPTIN-NEXT: (then | ||
| ;; OPTIN-NEXT: (loop | ||
| ;; OPTIN-NEXT: (local.set $1 | ||
| ;; OPTIN-NEXT: (local.get $0) | ||
| ;; OPTIN-NEXT: ) | ||
| ;; OPTIN-NEXT: ) | ||
| ;; OPTIN-NEXT: ) | ||
| ;; OPTIN-NEXT: (else | ||
| ;; OPTIN-NEXT: ) | ||
| ;; OPTIN-NEXT: ) | ||
| ;; OPTIN-NEXT: (loop $label | ||
| ;; OPTIN-NEXT: (loop | ||
| ;; OPTIN-NEXT: ) | ||
| ;; OPTIN-NEXT: (br_if $label | ||
| ;; OPTIN-NEXT: (i32.const 0) | ||
| ;; OPTIN-NEXT: ) | ||
| ;; OPTIN-NEXT: ) | ||
| ;; OPTIN-NEXT: ) | ||
| (func $iloop (param $0 f32) | ||
| ;; Regression test for an infinite loop: the specific cfg here + the | ||
| ;; constraints lead to a situation where, if we were not careful, we would | ||
| ;; think we have an infinite stream of updates in flow(). Specifically, we | ||
| ;; end up updating a location to a combination of two constraints {A, B} and | ||
| ;; then end up finding {B, A} in the next cycle, and then alternate those | ||
| ;; two forever. This is fixed by sorting the constraints. | ||
| ;; | ||
| ;; (There is nothing to optimize here, we just should not hang or error.) | ||
|
Comment on lines
+3599
to
+3606
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A lattice wouldn't have had this problem 🥲 |
||
| (local $1 f32) | ||
| (local.set $0 | ||
| (local.get $1) | ||
| ) | ||
| (if | ||
| (i32.const 0) | ||
| (then | ||
| (loop | ||
| (local.set $1 | ||
| (local.get $0) | ||
| ) | ||
| ) | ||
| ) | ||
| (else | ||
| ) | ||
| ) | ||
| (loop $label | ||
| (loop | ||
| ) | ||
| (br_if $label | ||
| (i32.const 0) | ||
| ) | ||
| ) | ||
| ) | ||
| ) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may be worth taking on extra complexity to have more precise comparisons ordered lower to prioritize keeping them. For example,
eq lt gt le ge nemight be a good order.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, added a TODO.