-
-
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
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
1fd50a0
implement @pure annotation (recorded in the linfo) and support in inf…
vtjnash 0cd98aa
detect @pure in src, without requiring inference to have been run on …
vtjnash c416e9f
add @pure annotation to promotion functions
vtjnash 8ce7a02
mark some more key functions are @pure
vtjnash b7278d1
add support for calling `@pure` functions with args that are potentia…
vtjnash File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,7 +6,7 @@ const MAX_TYPE_DEPTH = 5 | |
const MAX_TUPLETYPE_LEN = 8 | ||
const MAX_TUPLE_DEPTH = 4 | ||
|
||
type NotFound | ||
immutable NotFound | ||
end | ||
|
||
const NF = NotFound() | ||
|
@@ -534,7 +534,7 @@ function builtin_tfunction(f::ANY, args::ANY, argtype::ANY, vtypes::ObjectIdDict | |
return tf[3](argtypes...) | ||
end | ||
|
||
function isconstantfunc(f::ANY, sv::StaticVarInfo) | ||
function isconstantref(f::ANY, sv::StaticVarInfo) | ||
if isa(f,TopNode) | ||
m = _topmod() | ||
return isconst(m, f.name) && isdefined(m, f.name) && f | ||
|
@@ -543,39 +543,46 @@ function isconstantfunc(f::ANY, sv::StaticVarInfo) | |
M = f.mod; s = f.name | ||
return isdefined(M,s) && isconst(M,s) && f | ||
end | ||
if isa(f,Expr) && is(f.head,:call) | ||
if length(f.args) == 3 && isa(f.args[1], TopNode) && | ||
is(f.args[1].name,:getfield) && isa(f.args[3],QuoteNode) | ||
s = f.args[3].value | ||
if isa(f.args[2],Module) | ||
M = f.args[2] | ||
else | ||
M = isconstantfunc(f.args[2], sv) | ||
if M === false | ||
return false | ||
end | ||
M = _ieval(M) | ||
if !isa(M,Module) | ||
return false | ||
if isa(f,Expr) | ||
if is(f.head,:call) | ||
if length(f.args) == 3 && isa(f.args[1], TopNode) && | ||
is(f.args[1].name,:getfield) && isa(f.args[3],QuoteNode) | ||
s = f.args[3].value | ||
if isa(f.args[2],Module) | ||
M = f.args[2] | ||
else | ||
M = isconstantref(f.args[2], sv) | ||
if M === false | ||
return false | ||
end | ||
M = _ieval(M) | ||
if !isa(M,Module) | ||
return false | ||
end | ||
end | ||
return isdefined(M,s) && isconst(M,s) && f | ||
end | ||
return isdefined(M,s) && isconst(M,s) && f | ||
elseif is(f.head,:inert) | ||
return f | ||
end | ||
return false | ||
end | ||
|
||
if isa(f,QuoteNode) && (isa(f.value, Function) || isa(f.value, IntrinsicFunction)) | ||
return f.value | ||
end | ||
if isa(f,Function) || isa(f,IntrinsicFunction) | ||
if isa(f,QuoteNode) | ||
return f | ||
end | ||
if isa(f,SymbolNode) | ||
f = f.name | ||
end | ||
return isa(f,Symbol) && is_global(sv, f) && _iisconst(f) && f | ||
if isa(f,Symbol) | ||
return is_global(sv, f) && _iisconst(f) && f | ||
end | ||
if isa(f,GenSym) || isa(f,LambdaStaticData) | ||
return false | ||
end | ||
return f | ||
end | ||
|
||
const isconstantref = isconstantfunc | ||
const isconstantfunc = isconstantref | ||
|
||
const limit_tuple_depth = t->limit_tuple_depth_(t,0) | ||
|
||
|
@@ -650,25 +657,7 @@ function abstract_call_gf(f, fargs, argtype, e) | |
end | ||
end | ||
if istopfunction(tm, f, :promote_type) || istopfunction(tm, f, :typejoin) | ||
la = length(argtypes) | ||
c = cell(la) | ||
for i = 1:la | ||
t = argtypes[i] | ||
if isType(t) && !isa(t.parameters[1],TypeVar) | ||
c[i] = t.parameters[1] | ||
else | ||
return Type | ||
end | ||
end | ||
if istopfunction(tm, f, :promote_type) | ||
try | ||
RT = Type{f(c...)} | ||
return RT | ||
catch | ||
end | ||
else | ||
return Type{f(c...)} | ||
end | ||
return Type | ||
end | ||
# don't consider more than N methods. this trades off between | ||
# compiler performance and generated code performance. | ||
|
@@ -821,7 +810,109 @@ function abstract_apply(af, fargs, aargtypes::Vector{Any}, vtypes, sv, e) | |
return abstract_call(af, (), Any[Vararg{Any}], vtypes, sv, ()) | ||
end | ||
|
||
function isconstantargs(args, argtypes::Vector{Any}, sv::StaticVarInfo) | ||
if isempty(argtypes) | ||
return true | ||
end | ||
if is(args,()) || isa(argtypes[end], Vararg) | ||
return false | ||
end | ||
for i = 1:length(args) | ||
arg = args[i] | ||
t = argtypes[i] | ||
if !isType(t) || has_typevars(t.parameters[1]) | ||
if isconstantref(arg, sv) === false | ||
return false | ||
end | ||
end | ||
end | ||
return true | ||
end | ||
|
||
function _ieval_args(args, argtypes::Vector{Any}, sv::StaticVarInfo) | ||
c = cell(length(args)) | ||
for i = 1:length(argtypes) | ||
t = argtypes[i] | ||
if isType(t) && !has_typevars(t.parameters[1]) | ||
c[i] = t.parameters[1] | ||
else | ||
c[i] = _ieval(isconstantref(args[i], sv)) | ||
end | ||
end | ||
return c | ||
end | ||
|
||
@pure function type_typeof(v::ANY) | ||
if isa(v, Type) | ||
return Type{v} | ||
end | ||
return typeof(v) | ||
end | ||
|
||
function pure_eval_call(f, fargs, argtypes, sv, e) | ||
if !isa(f, Function) # TODO: maybe replace with :call? | ||
return false | ||
end | ||
if !isconstantargs(fargs, argtypes, sv) | ||
return false | ||
end | ||
|
||
args = _ieval_args(fargs, argtypes, sv) | ||
tm = _topmod() | ||
if isgeneric(f) | ||
atype = Tuple{Any[type_typeof(a) for a in args]...} | ||
meth = _methods(f, atype, 1) | ||
if meth === false || length(meth) != 1 | ||
return false | ||
end | ||
meth = meth[1]::SimpleVector | ||
linfo = try | ||
func_for_method(meth[3], atype, meth[2]) | ||
catch | ||
NF | ||
end | ||
if linfo === NF | ||
return false | ||
end | ||
if !linfo.pure | ||
typeinf(linfo, meth[1], meth[2], linfo) | ||
if !linfo.pure | ||
return false | ||
end | ||
end | ||
elseif !isdefined(f, :code) || !f.code.pure | ||
return false | ||
end | ||
|
||
local v | ||
try | ||
v = f(args...) | ||
catch | ||
return false | ||
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. it should be ok to return bottom type here ? |
||
end | ||
if isa(e, Expr) # replace Expr with a constant | ||
stmts = Any[] # check if any arguments aren't effect_free and need to be kept around | ||
for i = 1:length(fargs) | ||
arg = fargs[i] | ||
if !effect_free(arg, sv, false) | ||
push!(stmts, arg) | ||
end | ||
end | ||
if isempty(stmts) | ||
e.head = :inert # eval_annotate will turn this into a QuoteNode | ||
e.args = Any[v] | ||
else | ||
e.head = :call # should get cleaned up by tuple elimination | ||
e.args = Any[top_getfield, Expr(:call, top_tuple, stmts..., v), length(stmts) + 1] | ||
end | ||
end | ||
return type_typeof(v) | ||
end | ||
|
||
|
||
function abstract_call(f, fargs, argtypes::Vector{Any}, vtypes, sv::StaticVarInfo, e) | ||
t = pure_eval_call(f, fargs, argtypes, sv, e) | ||
t !== false && return t | ||
if is(f,_apply) && length(fargs)>1 | ||
af = isconstantfunc(fargs[2], sv) | ||
if !is(af,false) | ||
|
@@ -925,7 +1016,8 @@ function abstract_eval_call(e, vtypes, sv::StaticVarInfo) | |
if !(Function <: ft) && _iisdefined(:call) | ||
call_func = _ieval(:call) | ||
if isa(call_func,Function) | ||
return abstract_call(call_func, e.args, Any[ft,argtypes...], vtypes, sv, e) | ||
unshift!(argtypes, ft) | ||
return abstract_call(call_func, e.args, argtypes, vtypes, sv, e) | ||
end | ||
end | ||
return Any | ||
|
@@ -942,7 +1034,8 @@ end | |
|
||
function abstract_eval(e::ANY, vtypes, sv::StaticVarInfo) | ||
if isa(e,QuoteNode) | ||
return typeof((e::QuoteNode).value) | ||
v = (e::QuoteNode).value | ||
return type_typeof(v) | ||
elseif isa(e,TopNode) | ||
return abstract_eval_global(_topmod(), (e::TopNode).name) | ||
elseif isa(e,Symbol) | ||
|
@@ -1018,6 +1111,9 @@ function abstract_eval(e::ANY, vtypes, sv::StaticVarInfo) | |
t = Function | ||
elseif is(e.head,:copyast) | ||
t = abstract_eval(e.args[1], vtypes, sv) | ||
elseif is(e.head,:inert) | ||
v = e.args[1] | ||
return type_typeof(v) | ||
else | ||
t = Any | ||
end | ||
|
@@ -1712,6 +1808,9 @@ function typeinf_uncached(linfo::LambdaStaticData, atypes::ANY, sparams::SimpleV | |
getfield_elim_pass(fulltree.args[3], sv) | ||
end | ||
linfo.inferred = true | ||
body = Expr(:block) | ||
body.args = fulltree.args[3].args::Array{Any,1} | ||
linfo.pure = popmeta!(body, :pure)[1] | ||
fulltree = ccall(:jl_compress_ast, Any, (Any,Any), def, fulltree) | ||
end | ||
|
||
|
@@ -1778,6 +1877,8 @@ function eval_annotate(e::ANY, vtypes::ANY, sv::StaticVarInfo, decls, clo, undef | |
return e | ||
#elseif is(head,:gotoifnot) || is(head,:return) | ||
# e.typ = Any | ||
elseif is(head,:inert) | ||
return QuoteNode(e.args[1]) | ||
elseif is(head,:(=)) | ||
# e.typ = Any | ||
s = e.args[1] | ||
|
@@ -1967,10 +2068,7 @@ function exprtype(x::ANY, sv::StaticVarInfo) | |
return abstract_eval(x::Symbol, emptydict, sv) | ||
elseif isa(x,QuoteNode) | ||
v = (x::QuoteNode).value | ||
if isa(v,Type) | ||
return Type{v} | ||
end | ||
return typeof(v) | ||
return type_typeof(v) | ||
elseif isa(x,Type) | ||
return Type{x} | ||
elseif isa(x,LambdaStaticData) | ||
|
@@ -2032,7 +2130,7 @@ function effect_free(e::ANY, sv, allow_volatile::Bool) | |
allow_volatile && return true | ||
return isconst(e.mod, e.name) | ||
end | ||
if isconstantfunc(e, sv) !== false | ||
if isconstantref(e, sv) !== false | ||
return true | ||
end | ||
if isa(e,Expr) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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:
@pure
function only if the return type was not perfectly inferred.@pure
functions for arguments that subtypeType
more than for other argument typesagain, 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 notx = 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 withBase.@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.