-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
implement pure function annotations #13555
Conversation
…erence such that pure functions might be run at that time
Hooray! This will close issue #414; see that issue for discussion. |
…lly not effect free (some of the linalg code expects this)
One of the things I was hoping to make pure was e.g. |
yes, that seems possible. as written, however, it's mostly intended for type-domain functions, where the added expense at compile-time should be completely offset by better type information. also, since we don't have a constant-propagation pass, except for types, it won't be able to handle anything too complex that isn't in the type domain. |
But if we had constant-propagation in addition to this, then it could work, no? I don't understand the insistence that this feature only be for doing type domain computations. Why limit it? |
it still wouldn't provide most of the runtime optimizations you would probably expect (such as auto-memoization and loop invariance), and it doesn't run during / after inlining as you would probably want. but yes, there's nothing inherently wrong about annotating other functions. (travis failure seems to have been a random OOM, so this is ready to merge) |
Presumably, those are all optimizations that can be hooked in later with this |
|
||
local v | ||
try | ||
v = f(args...) |
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.
even if we trust the user that the function is pure, this looks dangerous. pure usually does not imply terminating (or reasonably fast anyway) so we should at least not use that name.
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 do like that we can get rid of the ugly special cases in inference.jl, I'm just not sure this annotation should be user facing.
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 trust the user that @generated
functions are sane (which is how these particular functions used to be marked). what's wrong with trusting them that @pure
is marked correctly? i don't plan on exporting @pure
until it is more intelligent about those additional requirements (e.g. post-green-fairy, if ever)
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.
Pure functions, and the associated compiler optimizations, are very useful for writing efficient library functions... it would be sad if this feature were planned to be confined to Base for the foreseeable future.
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 fine with trusting the user, I just think the naming at least is a little too innocent. Pure is a factual assertion ("even if it may not look like so, this function can be considered side effect free"), whereas this annotation is an intent declaration ("blindly execute at compile time").
IMO a correct implementation of "pure" would at least terminate the evaluation after some constant number of steps. I am aware that this is basically infeasible today.
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.
@carnval i'm not convinced that those require different annotations. it's true that's how it is annotated today, but I think there are a number of heuristics that can be incorporated into inference to improve the off-nominal behavior of this annotation without actually changing its meaning. these include:
- execute an
@pure
function only if the return type was not perfectly inferred. - execute
@pure
functions for arguments that subtypeType
more than for other argument types - implement execution time step limits
again, this is in contrast to the @generated
functions and special-case functions that this PR replaces, for which the compiler has no flexibility except to blindly execute at compile time and hope it terminates / works.
but, this PR was intended to only address a couple of particular cases (and provide a slot to store the pure
attribute). i'm expecting the gf to provide a more general framework and make better use of this information. in light of that, does this need tweaking now to make it compatible with your plans for that?
@stevengj I agree. However, this can only handle completely trivial expressions like pow(1, 2)
, and not x = 1; pow(x, 2)
, and it doesn't propagate the information in such a way that it could hoist this out of a loop. I'm not saying you can't mark any function with Base.@pure
, but this just hasn't implement any of the associated compiler optimizations that make it useful for efficient library functions (outside of the type domain), or any of the safety measures that carnval is describing.
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 agree that until the associated compiler optimizations are implemented, there is no point in exporting it. But presumably those are orthogonal features that will get implemented eventually — they are well-known and well-understood optimizations that have been implemented to some degree in many other languages.
On the other hand safety checks on pure annotations seem more like an open-ended research project, and I don't think exporting @pure
need wait on these.
This feature seems a bit ripe for abuse. I think we should add a command line switch to ignore the |
how do you think it'll get abused? the system only looks for the attribute when the result is (likely) profitable. |
If you added the But I misread the above conversation, it doesn't seem like this will be user facing or exported. |
It's probably fine for types (i.e. the current use case) but I'm wondering if this is fine to do with other values in general since some of them might be pure or not depending on the type of the arguments. |
Of course random methods aren't When more compiler optimizations are implemented and |
In particular, see @JeffBezanson's suggestion in #414 that |
Thanks for the pointer. Seems that what I said (edit: or what I wanted to say at least....) was basically this #414 (comment) ... (Although I don't think there's a solution for this in that thread?) |
@yuyichao, I don't think there was a solution. (Has any language as complex as Julia, with side effects etc., ever successfully implemented automatic purity inference? Lots of languages have unchecked |
Unless there is reason to believe that bottom-up purity inference is not that difficult for the cases where we want to use it? At minimum, you would need the ability to mark certain |
there are cases (such as type-intersection), where the intermediates are not pure but the end result is pure. however, the computation of that would require tracking purity and escape with respect to each argument (i would guess that is a special case of the halting problem). as mentioned in the top post, the compiler could use effect_free to handle many of the trivial cases (e.g. so that it propagates up to a simple dispatch wrapper, or for a function that returns a constant, etc.). the annotation here is on a method, not a function, in part to address the observation that |
implement a pure function annotation
|
||
# Try promote_rule in both orders. Typically only one is defined, | ||
# and there is a fallback returning Bottom below, so the common case is | ||
# promote_type(T, S) => | ||
# promote_result(T, S, result, Bottom) => | ||
# typejoin(result, Bottom) => result | ||
promote_type{T,S}(::Type{T}, ::Type{S}) = | ||
function promote_type{T,S}(::Type{T}, ::Type{S}) | ||
@_pure_meta |
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.
So what happens if I define a promote rule that ensures my custom type only ever has a concrete type parameter… and throws an error otherwise? That would mean that this pure annotation is no longer correct, no?
@inline function Base.promote_rule{T,S<:Number}(::Type{Interval{T}}, ::Type{S})
R = promote_type(T,S)
isleaftype(R) || throw(ArgumentError("cannot promote $T and $S to a common leaf type"))
Interval{R}
end
On second thought, this is probably better to enforce in the constructor. But do we need to document that promote_rule
needs to be a pure function now? What happens if it's not?
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.
throwing errors is fine
this implements a
@pure
annotation for functions. if a pure function can be run at compile-time, it will be, so use sparingly (and don't try to make it solve the halting problem). however, it provides a less codegen-heavy alternative to several@generated
functions that were in base (since the optimization now becomes optional / compile-time, rather than mandatory / runtime).future work: the current implementation neglects to make use of this information to improve
effect_free
or to auto-compute this information usingeffect_free
. both of those optimizations should be simple and beneficial.