-
Notifications
You must be signed in to change notification settings - Fork 0
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
How to construct isl sets. #1
base: master
Are you sure you want to change the base?
Conversation
Sounds like a good move indeed. The text can probably be polished a bit more, with shorter sentences, but it already makes a lot of sense, pedagogically speaking. |
e797d67
to
d595b23
Compare
The example is really good, but I feel it would be better by making the relation between the text and the example clearer (e.g. the parametric set is constructed via |
d595b23
to
d0b38d4
Compare
I like the idea. A few comments on the API:
|
README.md
Outdated
## Constructing an integer set or map (isl::set / isl::map) | ||
|
||
We create an integer set as follows. We first create a set of identifiers for | ||
each of the needed dimensions (`isl::id Id_N(ctx, "N"`). We then introduce |
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.
This seems like the most annoying part (and you also commented on this below). Given that our current take on exporting ids is to ignore the user pointers in the C++/Python, maybe we can assume a bijection between names and isl::id objects and just use string literals instead of ids in context where isl::ctx object is available.
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.
I don't think this will pay off actually. Id identity would then mean string value identity, instead of the reference identity we have now. Names are not generally literals, but could come from std::string
or const char*
or anything that resembles a string.
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.
I meant something like
auto A = isl::aff(isl::id(ctx, "A"));
auto AplusB = A.plus("B");
but it's not something that looks easy to get right from the first take.
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.
Sure, but this would require string compare to find the proper Id, and isl is already not the fastest library on the planet :)
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 wouldn't. When you construct an id with the same name, it is guaranteed to be the same id. Internally, it's a hash map based on name and user pointer (always null from C++) AFAIR.
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.
Yes, given that the user pointer is always null with the C++ interface, it should work with plain pointer ==
It will not work with hybrid C and C++ code however, but such hybrid schemes are unlikely to cause trouble in set/map construction. And also the user pointer may end up being deprecated in the C world as well.
Still, I like Philip's idea of implicit lifting of IDs into pw_aff.
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.
Right, I think constructing [pw_]affs from strings is easy. Where I don't feel there is a good way is for extracting an id from a pw_aff that has already been constructed. The reason is that even though the pw_aff just contains a single id when we constructed, an affine expression can obviously have multiple dimensions, so we cannot just add a function aff::get_id.
@pfaffe's idea of allowing conversion from id: to aff solves this problem nicely. However, it would mean we would need to provide operators which operations between ints , ids, affs, pw_affs, ... I feel this could make the interface a lot nicer, but we need to be careful how much operator overloading we actually allow in the end in terms of optimal clarity.
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.
Just my 2 cents, take with a grain of salt :)
re context where isl::ctx object is available.
Is there concretely a benefit in manipulating more than 1 threadlocal context at a time?
Could this be better captured by a global threadlocal context (the context) with maybe threadsafe RAII-style scopes like WithDevice
for CUDA, like we do here.
I think I remember there were arguments about handing off objects from one context to another in the case of multithreading but I am not sure. If there are such considerations, I would hope people plan to use modern threading concepts (C++11 onwards).
In any case, to be more concrete about the API / examples proposed here, I feel exposing ctx to all users is an abstaction leak. I would prefer things in a detail namespace where one must pass ctx and a more vanilla API
re: ref vs string id
FWIW Halide's take on this is to use strings as ids and it simplifies code greatly.
There were a bunch of rewrites in TC that Sven did for us because one could not rely on the name of the ID. I do not fully understand the underlying issues here but they seemed somewhat artificial to me.
How much of this comes from not using a simple unordered_map
in the entrails of ISL?
string compare to find the proper Id, and isl is already not the fastest library on the planet
Hashing should solve this. In any case, seems to me that worrying about strcmp
in a word where we routinely run ILPs in a single thread is premature optimization :)
Where I don't feel there is a good way is for extracting an id from a pw_aff that has already been constructed
string == id
solves that but of course it has to work with the entrails of ISL
Generally, I am wondering how much of the ISL interface quirks comes from the fact that it's written in C where STL is unavailable. As a consequence, are the C++ bindings the right abstraction for a user? In the same way operator overloading is useful I am wondering whether simple high-level wrappers could hide some of the complexity away.
README.md
Outdated
isl::id Id_N(ctx, "N"), Id_M(ctx, "M"), Id_i(ctx, "i"), Id_j(ctx, "j"); | ||
|
||
// One (piece-wise) affine expression per identifier | ||
isl::pw_aff N(Id_N), M(Id_M), i(Id_i), j(Id_j); |
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.
What is the space of these expressions? Assuming an implicitly constructed parameter space containing only the given parameter.
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.
I wouldn't use a pw aff for something that is not actually piece-wise
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 space is a parameter space with a single dimension.
Right now we need to use pw_aff as aff does not support auto-alignment of parameter dimensions. This should be improved eventually, but -- as Sven said -- this requires more work.
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.
Right now we need to use pw_aff as aff does not support auto-alignment
these type of concerns are exactly the reason why I wonder if the current C++ bindings are the right abstraction or whether we could have some simple higher level wrapper that may only expose a subset of ISL but that are much more natural in C++.
In other words a general comment I have here is that the design is bottom-up, which is very important to connect to reality and a working impl. But we should also maybe think about top down: how would we write these things ideally and can we implement them naturally with ISL.
Rinse, repeat.
README.md
Outdated
isl::pw_aff N(Id_N), M(Id_M), i(Id_i), j(Id_j); | ||
|
||
// One (piece-wise) affine expression per constant | ||
isl::pw_aff Ten(ctx, 10), Two(ctx, 2), Three(ctx, 3); |
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.
What is the space of these expressions? An implicitly constructed empty space?
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.
An implicitly constructed space with 0 param, 0 set, and 0 output dimensions.
README.md
Outdated
isl::pw_aff Ten(ctx, 10), Two(ctx, 2), Three(ctx, 3); | ||
|
||
// Build the left and right hand side of the expression | ||
isl::pw_aff LHS = Two.mul(M).add(Three.mul(N)); |
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.
Here is were overloaded operators come in handy. We can overload operators to combine pw_aff
, integers and val
, which would remove the need to create "constant" affs Ten
, Two
, Three
. If we proceed carefully, we can also overload operators betwee ids and integers so that they return an aff.
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.
Absolutely. The question I have at this point is, when do you know a constant/parameter should be lifted into an aff or a pw_aff?
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.
Since a single parameter or constant is unconstrained, it should be an aff.
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.
Right.
README.md
Outdated
// One (piece-wise) affine expression per identifier | ||
isl::pw_aff N(ctx, "N"), M(ctx, "M"), i(ctx, i"), j(ctx, "j"); | ||
``` | ||
This likely won't work as there is no way to obtain ids for the identifiers. |
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.
We can construct a new id, identifiers with identical names are guaranteed to be identical if all of them are constructed from C++.
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.
I'm not sure I get what you mean by identical here.
For this example, we can just write isl::pw_aff N({ctx, "N"}), M({ctx, "M"}), i({ctx, i"}), j({ctx, "j"});
which is only negligibly more verbose.
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.
isl::id id1(ctx, "A");
isl::id id2(ctx, "A");
id1 == id2; // must be true
actually, this
obtain ids for the identifiers.
is confusing
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 that implemented right now?
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.
Apparently it is, I had no idea. I'm used to Polly's version of the interface, where the user pointer is present.
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.
Not sure about the operator==
overload, but construction is implemented.
Generally, I like the idea. There are some syntactic improvements that I could propose, but it depends on the flavor of C++ that people prefer. Operator overloads would significantly improve readability here, as well as constructing affs from names instead of ids. isl::aff N(ctx, "N"), M(ctx, "M"), i(ctx, "i"), j(ctx, "j');
auto LHS = 2 * M + 3 * N; // natural precedence works
auto RHS = 2 * i + j + 10;
auto PSet = LHS.le_set(RHS);
// { [N, M] -> { [i,j] : 2 * M + 3 * N <= 2 * i + j + 10 }
auto Set = PSet.inputs(i, j);
// { [N, M] -> { [i]->[j] : 2 * M + 3 * N <= 2 * i + j + 10 }
auto Map = PSet.inputs(i).outputs(j); We can overload operators when one of the arguments is already a (pw)aff so as to implicitly construct an aff for another argument in the same space (or defined on the same domain for Additionally, we can have a marker type and overload comparison operators for parametric set construction. auto PSet = isl::set_maker(LHS) <= RHS;
// or directly
auto PSet = isl::set_maker(2 * M + 3 * N) <= 2 * i + j + 10; I suggest marker types because a non-boolean result of the |
Im not sure I agree with the marker approach. How is this |
Sounds like a great endeavor to me, thanks for starting this! |
+10e9 for overloads |
Mixing C and C++ API is already discouraged. And it seems there will be other functionality available only in C... |
Yes, this seems indeed problematic with the current functionality. I think we are missing some property inspection functions, but this can go to a separate use case. In particular, I would like to know if a (pw)aff is constant everywhere defined, or is a variable/parameter everywhere defined. There is a function for the first part in |
That's why I said it depends on the flavor of C++ you prefer. I would argue that it is more readable then Additional motivation, overloading comparison operators to not-default semantics (that is not comparison) may lead to some difficult to track errors. For example, one could call |
I don't seem to be able to request reviews on this PR, but please add @skimo-openhub |
9c02918
to
5f734c2
Compare
18d5e58
to
c470d0d
Compare
@ALL, thanks for participating in the discussion. I tried to summarize and structure the feedback so far. The one item I did not yet summarize well was the point "Return type of the comparision operator". I did not understand the marker stuff well enough to write a good summary. @ftynse, maybe you want expand here? The problem with Philip's proposal is that an isl::constraint really just is a single constraint, whereas at least isl::pw_affs can be set of constraints, so the actual element that is returned must be a set of constraints (an isl::set). |
@ftynse, Sven is not a member of the PollyLabs github organization. I invited him now. |
@alex: You should now also have all rights needed to invite and change things in the PollyLabs repos. Please check if adding reviewers works now. |
c470d0d
to
3c2d46e
Compare
@TobiG could you clarify for me in what way a pw_aff might induce a set of constraints? Every piece becomes a single constraint? We should clarify if we want to have pw_aff's or aff's as smallest building blocks here. From the discussion and the example, it looks to me like currently all the spaces involved are universe, and so there is no real way to produce any distinct pieces yet. @ftynse Looks like it comes down to a matter of taste. I dislike the markers because they trip up my reading if i'm familiar with what they mean. If I'm not, I'm forced to go back to the documentation to understand that this is non-optional syntactic sugar. It doesn't feel "natural" to me to write it this way. Regarding your second argument, std::less only has an issue with |
Indeed, we should discuss (separately) how do we construct something like
Yep, that's what I started my proposition with ;)
It is not supposed to be natural, it is supposed to force you to read the documentation or trip up on the non-optional syntactic sugar to understand what happens. I consider it an instance of "recognition over recall" UI design principle (and API design an instance of UI design). You recognize the markers rather than recall the types of LHS and RHS and that comparison operators on those do not mean comparison. However, the cases that we've seen so far are short so you probably won't forget the types before you need them. I'm not sure this how to implement this and trump ADL, but something like auto LHS = /*...*/;
auto RHS = /*...*/;
using namespace isl::set_construction;
auto PSet = LHS <= RHS; where construction-by-comparison operators are defined in the
They are not yet convertible in the mainline AFAIR. That will be up for a separate discussion. |
@pfaffe what is your take on https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Ro-conventional |
@pfaffe: An |
@ftynse : I like the idea with having some of the operators in separate name spaces. |
1ac114e
to
8ef76ee
Compare
@pfaffe, I am happy for us to drop the conversion to bool stuff from polly. I don't feel it's important. |
d91c31d
to
2748ab7
Compare
@ftynse I agree with you that @TobiG Can you make an example of how you'd want to write an expression which needs to be represented as a pw_aff? |
@pfaffe: Today the following functions require pw_aff
|
@TobiG Fair enough. Are those an example against using constraints/affs as basic building blocks though? This code seems to be a perfectly elegant solution to computing |
We discussed this topic again on the Polly phone call (Michael & Philip & Tobias & others). Between ourselves we felt that we should:
I will update the document, here the pull requests that complete the proposal's initial interface: |
f635c2f
to
5646a53
Compare
5646a53
to
74a7767
Compare
I just updated the proposal. Please feel invited to give feedback on proposal and patches. |
On today's Polly Labs call, we discussed the idea of creating better documentation how to use the new isl interfaces. This is a first proposal of how to create an isl::set.