-
Notifications
You must be signed in to change notification settings - Fork 13.7k
[TableGen] Make !and
and !or
short-circuit
#113963
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
[TableGen] Make !and
and !or
short-circuit
#113963
Conversation
By preemptively simplifying the result of `!and`, we can fold some of the conditional operators, like `!if` or `!cond`, as early as possible.
@llvm/pr-subscribers-tablegen Author: Min-Yih Hsu (mshockwave) ChangesBy preemptively simplifying the result of Full diff: https://github.com/llvm/llvm-project/pull/113963.diff 2 Files Affected:
diff --git a/llvm/lib/TableGen/Record.cpp b/llvm/lib/TableGen/Record.cpp
index 1d71482b020b22..1f01b82685718a 100644
--- a/llvm/lib/TableGen/Record.cpp
+++ b/llvm/lib/TableGen/Record.cpp
@@ -1543,6 +1543,23 @@ const Init *BinOpInit::resolveReferences(Resolver &R) const {
const Init *lhs = LHS->resolveReferences(R);
const Init *rhs = RHS->resolveReferences(R);
+ if (getOpcode() == AND) {
+ // Short-circuit. Regardless whether this is a logical or bitwise
+ // AND.
+ if (lhs != LHS)
+ if (const auto *LHSi = dyn_cast_or_null<IntInit>(
+ lhs->convertInitializerTo(IntRecTy::get(getRecordKeeper())))) {
+ if (!LHSi->getValue())
+ return LHSi;
+ }
+ if (rhs != RHS)
+ if (const auto *RHSi = dyn_cast_or_null<IntInit>(
+ rhs->convertInitializerTo(IntRecTy::get(getRecordKeeper())))) {
+ if (!RHSi->getValue())
+ return RHSi;
+ }
+ }
+
if (LHS != lhs || RHS != rhs)
return (BinOpInit::get(getOpcode(), lhs, rhs, getType()))
->Fold(R.getCurrentRecord());
diff --git a/llvm/test/TableGen/true-false.td b/llvm/test/TableGen/true-false.td
index 597ad9f5ecc8e7..a9094884dbdb9f 100644
--- a/llvm/test/TableGen/true-false.td
+++ b/llvm/test/TableGen/true-false.td
@@ -67,6 +67,20 @@ def rec7 {
bits<3> flags = { true, false, true };
}
+// The `!and` should be short-circuit such that `!tail` on empty list will never
+// be evaluated.
+// CHECK: def rec8
+// CHECK: list<int> newSeq = [];
+// CHECK: list<int> newSeq2 = [];
+
+class Foo <list<int> seq = []> {
+ bit containsStr = !ne(!find(NAME, "BAR"), -1);
+ list<int> newSeq = !if(!and(!not(!empty(seq)), containsStr), !tail(seq), seq);
+ list<int> newSeq2 = !if(!and(containsStr, !not(!empty(seq))), !tail(seq), seq);
+}
+
+def rec8 : Foo<>;
+
#ifdef ERROR1
// ERROR1: Record name '1' is not a string
|
llvm/lib/TableGen/Record.cpp
Outdated
@@ -1543,6 +1543,23 @@ const Init *BinOpInit::resolveReferences(Resolver &R) const { | |||
const Init *lhs = LHS->resolveReferences(R); | |||
const Init *rhs = RHS->resolveReferences(R); | |||
|
|||
if (getOpcode() == AND) { |
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.
Do we need similar short circuit for OR as well?
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 point, I had thought this before. The problem is that since !or
is for both logical and bitwise operation, we can't short circuit something like this !or(true, <unknown / unresolved>)
until the second operand is resolved (in which case it's not a short circuit anymore) because we're not sure if the second operand is a int (bitwise OR) or a bit (logical OR).
That said, I think a separate logical OR / AND like you suggested might help us to apply short-circuit with more confidence.
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 point, I had thought this before. The problem is that since
!or
is for both logical and bitwise operation, we can't short circuit something like this!or(true, <unknown / unresolved>)
until the second operand is resolved (in which case it's not a short circuit anymore) because we're not sure if the second operand is a int (bitwise OR) or a bit (logical OR).
I just made a blunder and forgot that !or(<all ones>, ...)
can also be short-circuited. I'll add this case into this patch as well.
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.
The short-circuit for !or
against all ones was just added.
!and and !or are bitwise operators that take an arbitrary number of arguments. Given that they are not documented to short-circuit, isn't this an incompatible change? (It's been awhile, so my memory may be failing me.) |
I had that concern as well, but I thought these lhs and rhs expressions are
not side effecting (modulo error checkin). Or do you propose adding short
circuit variants?
…On Mon, Oct 28, 2024 at 5:43 PM Paul C. Anagnostopoulos < ***@***.***> wrote:
!and and !or are bitwise operators that take an arbitrary number of
arguments. Given that they are not documented to short-circuit, isn't this
an incompatible change?
(It's been awhile, so my memory may be failing me.)
—
Reply to this email directly, view it on GitHub
<#113963 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/APRMUB354BKDXHUOA3UPSHTZ53K4VAVCNFSM6AAAAABQYLEIAKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDINBSHEZTQOBQGE>
.
You are receiving this because your review was requested.Message ID:
***@***.***>
|
Yes, it may be that all the operators that return integers or booleans have no side-effects, but that seems like something we should not rely on. I'm tempted to suggest that we add !land and !lor instead, which would take only bit arguments. But that isn't very C++-ish. I'll leave the final decision to someone with a stronger opinion on the matter. |
Is there a case where side effects might be produced in a (bang operator) expression? To my best understandings bang operators are all designed to be very..."Lisp" without any side effect. And even if we intermix other TableGen constructions into the expression, like More generally speaking, given the fact that our current
@Paul-C-Anagnostopoulos could you elaborate a little more on this point? I thought C/C++ also have separate logical and bitwise operators. |
To give a little more background, this is the case (which is also the attached test case) that motivates me to come up with this patch:
Since the There are two ways to solve this problem: (1) Fix I post this patch to do (2) first because I thought it can benefit more cases in addition to |
Sheesh, of course you are right. C++ has both bitwise and logical operators. See what happens when I don't code any C/C++ for a year? It all goes right out of my brain. I believe you are correct that no bang operators produce side-effects. So therefore it should be okay to short-circuit !and when a 0 argument is encountered, and !or when an all-1s argument is encountered. Be sure to document this, or otherwise there is no point in doing it. Or, in keeping with C++, we could leave !and and !or alone and add !land and !lor specifically for this purpose. They would accept only bit arguments. If we go this route, I'd add !lxor, too, even though it doesn't short-circuit. |
(1) would be very helpful imo |
Thanks for pointing out. I was actually thinking of a simple fix which removes the immediate resolutions of both operand in |
I haven't tried removing the immediate resolution yet, but unifying the resolution works around this bug in !if - but only for the case of instantiating anonymous records (not for |
I just added short-circuit for |
!and
short-circuit when either of the operand is zero!and
and !or
short-circuit
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.
LGTM atleast;
maybe others will review?
llvm/test/TableGen/true-false.td
Outdated
class Foo <list<int> seq = []> { | ||
bit containsStr = !ne(!find(NAME, "BAR"), -1); | ||
list<int> newSeq = !if(!and(!not(!empty(seq)), containsStr), !tail(seq), seq); | ||
list<int> newSeq2 = !if(!and(containsStr, !not(!empty(seq))), !tail(seq), seq); | ||
list<int> newSeq3 = !if(!or(containsStr, -1), seq, !tail(seq)); | ||
list<int> newSeq4 = !if(!or(-1, containsStr), seq, !tail(seq)); | ||
} |
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.
Is it better to have a simpler test since it's supposed to only check if !and/!or
is shorting properly?
Something like this perhaps:
class Foo <int zero = 0, list<int> seq = []> {
int unresolved = zero;
list<int> andLHS = !if(!and(zero, unresolved), !tail(seq), seq);
...
}
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.
Done
if (Opc == AND || Opc == OR) { | ||
// Short-circuit. Regardless whether this is a logical or bitwise | ||
// AND/OR. | ||
// Ideally we could also short-circuit `!or(true, ...)`, but it's |
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.
The comment about !or
now seems stale.
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.
The comment about
!or
now seems stale.
It is up-to-date: in the case of !or
, TableGen first casts every operands into int
, which is a 64 integer underlying. A true
is casted to 1 (rather than all ones) and because we only support short-circuit against all ones -- namely -1 -- a true
is actually not short circuited. The comment that follows also clarifies why we can't short circuit against 1.
llvm/lib/TableGen/Record.cpp
Outdated
(Opc == OR && LHSi->getValue() == -1)) | ||
return LHSi; | ||
} | ||
if (const auto *RHSi = dyn_cast_or_null<IntInit>( |
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.
In C/C++, the short-circuit is only applied to the LHS of the operator. That is, if the RHS happens to be 0 for an &&, the LHS is still evaluated. This seems to diverge from that, in the sense if RHS is 0, the LHS won't be evaluated. Is that the intent? Or should we abide by C/C++ style short-circuit to reduce the element of surprise?
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.
Or is this still under the umbrella that these evaluations do not produce side effects? I'd think even then, there may be value in just sticking with well known rules. I am assuming this is also sufficient for the cases you'd like to handle.
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.
You have a good point here. My intention was that in the case of something like !and(<unresolved>, 0)
we can avoid resolving the first operand, because it's futile.
may be value in just sticking with well known rules. I am assuming this is also sufficient for the cases you'd like to handle
Yeah I agree, I'll update the PR accordingly.
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's done now.
And cleanup the test
Change LGTM to me, so approving, but would like another approval from someone else as well. |
The idea is that by preemptively simplifying the result of `!and` and `!or`, we can fold some of the conditional operators, like `!if` or `!cond`, as early as possible.
By preemptively simplifying the result of
!and
and!or
, we can fold some of the conditional operators, like!if
or!cond
, as early as possible.