-
Notifications
You must be signed in to change notification settings - Fork 31
(Towards #3060) intrinsic return types #3119
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: master
Are you sure you want to change the base?
Conversation
|
One other coding style question - are we happy with statements like: Or would you prefer to pull out the if statement? (This was required when this was a lambda, but its turning into a function as its getting a lot of reuse so I'm happy to rewrite it if it is preferred). |
|
Also one thing to note is PSyclone appears to support more Also a question for @sergisiso - can you always refer to arguments with their names? I see for example |
|
I would also say - there are some cases of reusing precision througout this code. I'm not sure if this is a good idea with the new "precision can be DataNodes" - if not then the review might need to request me to fix that by copying if they're a |
5911f97 to
9f0799f
Compare
|
I applied the black formatter to these files as well - I couldn't work out how to make formatting happy myself for a couple of the lambdas so I had to make black do it for me. |
| False, | ||
| ArgDesc(1, 1, DataNode), | ||
| {}, | ||
| None, |
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.
Strictly speaking, as this is a call to a routine, the type is lambda: NoType(). I was going to say we need to make sure this is consistent with Call.datatype but that method isn't implemented yet :-)
| 'ANINT', True, True, False, | ||
| ArgDesc(1, 1, DataNode), {"kind": DataNode}) | ||
| "ANINT", | ||
| True, |
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 yours but to make this code easier to understand, I think it would be good to use keyword arguments when we construct the IAttr objects, e.g.:
ANINT = IAttr(
name = "ANINT",
is_pure = True,
...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 can work on this after I reach the end of return types yeah.
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 have previously felt it is hard to work out what is being referred to so would be a good change, especially as their sizes grow.
| return ArrayType(dtype, new_shape) | ||
|
|
||
|
|
||
| def _get_first_argument_logical_kind_with_optional_dim(node) -> DataType: |
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 possible to replace the various _get_first_argument_{int,real,logical...}_kind_with... with a version that just takes the intrinsic-type as an argument?
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 think I'd need to see an example of how you mean - I'm not sure I have many of these functions that only vary by the Intrinsic type - I think most have some other variation (either have an optional dim or kind or work on scalars vs arrays etc.). Some that take the same named arguments don't behave the same either - some have kind of first argument if no kind whilst others have default kind if no kind.
I did consider a decorator to try to make a single one that was cleaner, but I didn't like the design of that due to the level of disparity between the functions - especially now I've reached some of the more complex return types that really needed to be a function and not a lambda to be readable.
I think I'd prefer a separate if for this - it's quite hard to parse :-) |
I'm a bit confused by the check on whether it is |
|
Thanks Aidan, I think it's looking mostly as I'd expect although, as commented above, I was anticipating always having a Callable - whether a lambda or a separate routine if it's complicated enough. EDIT: scrub that - I was getting confused between the definition of an IntrinsicCall and an Intrinsic. I think what you're suggesting is fine actually. |
I could always have a lambda - it just felt overkill for cases where the return type is just an |
|
I will say I semi-lost the will to carry on for specifically I'll clean up the remaining test suite issues before I have "finished" return_type and probably it would be good to have a closer look before I move on to implementing reference_accesses. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #3119 +/- ##
==========================================
- Coverage 99.90% 99.89% -0.01%
==========================================
Files 374 374
Lines 52383 52624 +241
==========================================
+ Hits 52332 52569 +237
- Misses 51 55 +4 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
I fixed the remaining failing test and added the keywords for |
|
Fixed up the remaining coverage I can do with |
|
@arporter @sergisiso ready for a look now - Andy suggested doing reference accesses as its own PR separately. There is coverage missing, but PSyclone/fparser doesn't support the inputs that could result in those - I want to leave the correct results in the code for when we do, but I'll leave it to the reviewer to decide. Edit: Note that this PR incorporates the kind stuff from #3110 - so ignore anything that looks like it comes from changes to kinds. Edit2: The other thing I'm unsure about for both this PR and the following PR is if we have optional arguments declared on an IntrinsicCall do they HAVE to be named in Fortran? I.e. is only |
|
Also one note - I think TEAM_IMAGE is a typo/made up intrinsic we have? I think it should be |
|
One note - this probably needs a todo w.r.t #2302 - I am rewriting the reference_accesses code to handle that, but this does not handle unexpected naming of arguments (that would cause IntrinsicCall.create to fail). |
arporter
left a comment
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 realised that it's a bit early to review this as it's branched off the PR that makes precision a DataNode. Therefore, I've only done a limited look, mainly focused on intrinsic_call.py (once I realised about the branching).
I like the way it's going and thanks for adding the keywords to the arguments to the many IAttr constructors. Mainly it's the usual request for comments plus it would be really helpful to write down the rules that are implemented by the various help methods - if you could do that for all of them (in their docstrings) then that would be great. I think there's also some scope to reduce duplication.
|
|
||
| # Shorthand for a scalar type with REAL_KIND precision | ||
| SCALAR_TYPE = ScalarType(ScalarType.Intrinsic.REAL, REAL_KIND) | ||
| SCALAR_TYPE = ScalarType(ScalarType.Intrinsic.REAL, Reference(REAL_KIND)) |
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 see what you mean about precisions and types now. Maybe SCALAR_TYPE needs to become a routine that returns a new object? Similarly for all the other shorthands we have here.
| :raises InternalError: if the variable does not have READ acccess. | ||
| ''' | ||
| if self._access_type != AccessType.READ: | ||
| raise InternalError("Trying to change variable to 'TYPE_INFO' " |
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 can see you've just adapted this from the existing one but I think the text could be better. Perhaps "...change variable access from 'READ' to 'TYPE_INFO' but access type is '{self._access_type}'"
| # pylint: disable=import-outside-toplevel | ||
| from psyclone.psyir.backend.debug_writer import DebugWriter | ||
| from psyclone.psyir.nodes import Literal, Node, Reference | ||
| from psyclone.psyir.symbols import DataSymbol, INTEGER_TYPE |
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.
Presumably you hit a circular dependence? Did you try making the imports more specific (e.g. from psyclone.psyir.symbols.datasymbol import DataSymbol)?
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.
Yeah - these were the circular dependencies I mentioned w.r.t. what Joerg was saying in Teams that I couldn't resolve any other way.
| # Compare the routine to be inlined with the one that | ||
| # is already present. | ||
| new_rts = self._prepare_code_to_inline([kernel_schedule]) | ||
| print(new_rts[0] == routine, len(new_rts)) |
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.
Please rm.
| end module my_mod | ||
| ''') | ||
|
|
||
| print("----------------------------------------------") |
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.
Please rm these prints.
| argtype2 = node.arguments[1].datatype | ||
| shape1 = argtype1.shape | ||
| shape2 = argtype2.shape | ||
| stype1 = ScalarType(argtype1.intrinsic, argtype1.precision) |
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.
mea culpa on lack of comments here. I presume I do all this so we can re-use the existing machinery for deciding on the type of the result of a BinaryOperation (e.g. real*integer => real). If you agree, please add a comment.
| arg2 = Reference(DataSymbol("b", stype2)) | ||
| binop = BinaryOperation.create(BinaryOperation.Operator.MUL, | ||
| arg1, arg2) | ||
| # TODO - make this a public method? |
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.
Probably we do want to make it (BinaryOperation._get_result_scalar_type) a public method.
| # a31 a32 a31*b1 + a32*b2 | ||
| # 3 x 2 * 2 x 1 = 3 x 1 | ||
| # rank 2 rank 1 rank 1 | ||
| if len(shape1) == 1: |
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.
mea culpa again but comments please, e.g. I think this first case is "vector-matrix" and the second is "matrix-vector".
| def _maxval_return_type(node) -> DataType: | ||
| """ Helper function for the MAXVAL (and similar) intrinsic return | ||
| types. | ||
|
|
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.
Please document the rules.
| required_args=ArgDesc(1, 1, DataNode), | ||
| optional_args={"kind": DataNode}, | ||
| return_type=lambda node: ( | ||
| ScalarType( |
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 think you may have a utility that does this now?
…one into 2302_intrinsic_argument_names
This reverts commit 18ab68e.
…one into 2302_intrinsic_argument_names
d108c89 to
4285bdb
Compare
|
I've rebased this now to branch off #3150, I'm not sure whether I'll do the same for the reference_accesses or just restart that at the point where its being worked on, it will depend how hard it is to rebase that since this has already been done. I can potentially restart looking at this with the argument names update now which means a more sensible way of implementing these is possible. |
I'm putting this up early so if anyone has time to have a quick look at the implementation before I go too far down the rabbit hole with how I'm implementing the return type and tests.
I have added two more members to the
IAttrnamedtuple,return_typeandreference_accesses, which can either be aCallableor specific value (depending on whats required).To get the return_type of an
IntrinsicCall, my plan is to do something like:The return type implementations are started - there are 3 helper functions at the moment (for cases I expect to be used a lot), e.g.
_get_first_argument_type, wheras other's have their own lambda (for example seeAINT).I'm unsure how much to avoid code duplication here, for example AINT and ANINT have the same lambda for their return_type, so I'm not sure whether its worth moving this out (and whether the result should be a lambda or function) every time I have any 2 intrinsic calls with the same return type? Feedback on this specific question would be appreciated as early as possible (probably one for @arporter to answer perhaps).
To test the return types, my plan was to have standalone test for every "helper" function (or even helper lambda later).
I was then planning to create a parametrize test for all other intrinsics who have their own specific lambda. My one concern is this parametrize would become very large - again feedback/thoughts on this approach would be helpful. You can see an initial versoin of how this parametrize might look at
intrinsic_call_test::650.NB. This is dependent on #3110 and I think I will rebase onto that branch for now so I can have passing tests.