-
-
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
fix #20704, pure
annotation should not skip method errors
#20726
Conversation
base/inference.jl
Outdated
# and to possibly enable more optimization in the future | ||
me.const_api = true | ||
end | ||
if proven_pure || me.src.pure |
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 shouldn't inline me.src.pure
functions, some of them can be very large
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're right, but we seem to have been doing that before! I guess I should just use proven_pure
?
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.
Ooops! and yes (merge with previous if statement)
It wasn't causing issues in the past since we also set me.const_api
, so it should have managed to avoid actually inlining the whole function.
break | ||
end | ||
end | ||
end | ||
end | ||
me.src.pure = ispure | ||
if proven_pure | ||
me.src.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.
This doesn't seem quite right, but do we ever actually look at this field? I suspect we should acknowledge that source.pure
and inferred.pure
and reflecting slightly different attributes (the former meaning @pure
, the latter meaning proven_pure
). There's a bit of a mis-match in terminology here though too, since we're using me.src.pure
to mean the same as const_api / jlcall_api == 2
. However, it's perfectly reasonable to know that a function is effect-free / pure, but not know what it returns.
While I agree with this PR and think we should make both changes 1 and 2, I don't think this fixes #20704. To fix that issue, we need to fix the condition here (https://github.com/JuliaLang/julia/pull/20726/files#diff-c0d983631c937ee5acfd0137cd7332deR3784) to prove that we've called the function at least once. |
I wanted to remove that block of code actually, but it broke a test in test/inference.jl that checks the behavior of a |
229e87e
to
4f1f8f6
Compare
@nanosoldier |
Wouldn't that contradict the idea that |
But when you call the function affects which random number you get. If you call it three times, will you get the same number or different ones? Depends on what the compiler decided to do, hence undefined. |
I should clarify I only meant this in a very narrow sense related to the test in question --- the test was checking that repeated calls returned the same value, which should not be mandated. We can continue to call the function at run time if we want to. |
Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @jrevels |
If we can get rid of that, we can just get rid of The test with |
Yes, I see what you mean. I'll push a better version. |
4f1f8f6
to
a441a50
Compare
@nanosoldier |
base/inference.jl
Outdated
|
||
methsig = method.sig | ||
if !(atype <: metharg) | ||
return invoke_NF(argexprs, e.typ, atypes, sv, atype_unlimited, | ||
invoke_data) | ||
end | ||
|
||
# check whether call can be inlined to just a quoted constant value | ||
if isa(f, widenconst(ft)) && !method.isstaged && (method.source.pure || f === return_type) && | ||
are_args_const(atypes) |
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 not sufficient to check that the result could have come from calling the pure
function. We need to actually prove that it did come from calling the pure function. For example, we would be correct to infer that bar(1.0)::Const(1)
in the original issue, even though calling bar(1.0)
throws a method error.
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 ever actually do that though? I believe any time these conditions are met, we will have called the function. I agree that's a bit brittle, but it's not easy to know where a type came from.
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's a simple example of the failure scenario – it doesn't even need arguments :P – but it's just a minor rewrite of #20704:
julia> function method_error end
method_error (generic function with 0 methods)
julia> Base.@pure a() = (method_error(); 1)
a (generic function with 1 method)
julia> b() = a()
b (generic function with 1 method)
julia> b()
1
I think we either need to recompute the Const here, or add a bool field to const (was_computed_by_apply_pure
) that we can check here.
Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @jrevels |
a441a50
to
6540021
Compare
@nanosoldier |
Ok, here's another attempt. |
@@ -191,7 +193,7 @@ mutable struct InferenceState | |||
vararg_type = rewrap(vararg_type, linfo.specTypes) | |||
end | |||
s_types[1][la] = VarState(vararg_type, false) | |||
src.slottypes[la] = widenconst(vararg_type) | |||
src.slottypes[la] = vararg_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.
I think you can drop this part of the change. I don't foresee any need for inference or optimization to examine slottypes
(it should be strictly suboptimal information relative to the dataflow-sensative types) – I think this is strictly consumed by codegen.
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.
For slottypes in general that's true, but this was causing arguments to be given e.g. type Void
instead of Const(nothing)
. Ideally we'd treat those the same anyway, but this was an easier way to achieve that.
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.
Where are we using this info? Seems like this is a different bug that'll need tests if this is also causing issues.
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 weird thing is that just a few lines above, we explicitly do
if isa(atyp, DataType) && isdefined(atyp, :instance)
atyp = Const(atyp.instance)
and then call widenconst(atyp)
! I'll try to find a test case.
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 found it: exprtype
reads slottypes
, and inlining and some other passes call exprtype
. It takes a really obscure case to trigger it. For example f(x) = x()
, where f
is inferred on the type of a singleton pure function. x()
won't get inlined in that case because it's not a Const
.
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.
E.g.
Base.@pure function f(x)
rand()
42
end
julia> h(x) = x(nothing)
h (generic function with 1 method)
julia> code_typed(h, (typeof(f),))
1-element Array{Any,1}:
CodeInfo(:(begin
return $(Expr(:invoke, MethodInstance for f(::Void), :(x), :(Main.nothing)))
end))=>Int64
base/inference.jl
Outdated
@@ -1569,7 +1571,7 @@ function pure_eval_call(f::ANY, argtypes::ANY, atype::ANY, vtypes::VarTable, sv: | |||
try | |||
value = Core._apply_pure(f, args) | |||
# TODO: add some sort of edge(s) | |||
return abstract_eval_constant(value) | |||
return Const(value,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.
missing space after the comma
|
||
methsig = method.sig | ||
if !(atype <: metharg) | ||
return invoke_NF(argexprs, e.typ, atypes, sv, atype_unlimited, | ||
invoke_data) | ||
end | ||
|
||
# check whether call can be inlined to just a quoted constant value | ||
if isa(f, widenconst(ft)) && !method.isstaged |
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.
Any idea why this isa
check is here? AFAIK, we should be able to drop the f
argument, but this check gave me pause, from uncertainty.
Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @jrevels |
6540021
to
8fee77f
Compare
Were all those regressions just noise then? It would have been nice to address those with at least a comment before merging. |
those look too large and consistent to be noise |
Which set of results are you looking at? |
The last one run (#20726 (comment)) had a max 2.38x slowdown and max 0.30x speedup. It seems neither was expected? |
We've seen the sqrtm slowdown before --- did we ever figure out what it was? I'll look at the IR for some of the vector operations. |
daily benchmark did worse overall as well: (https://github.com/JuliaCI/BaseBenchmarkReports/blob/03d8b02711ecfc121c7935b5a20c66645fdd3f0a/daily_2017_2_28/report.md). Maybe this commit broke the inliner heuristics? |
JuliaCI/BaseBenchmarks.jl#65 is potentially relevant here. |
Found something: code for |
Ah, that makes sense. It looks like that one would be fairly hard for it to prove is pure, given our current lack of a framework for propagating the purity computation. How did it manage to get elided before? |
Ref. #20802 (comment) for what seems to be another related regression. Best! |
improve `effect_free` to fix regressions from #20726
There were two issues here:
@pure
function as a constant unless the inferred argument type is a subtype of the method signature, to be sure there is no MethodError.@pure
and is inferred to return a constant, we should not use the constant calling convention (CCC) for it unless we can prove iteffect_free
. That's because the CCC is equivalent to deleting the entire body of the function, which is not the same thing as calling the function at a different time.Item (1) is an uncontroversial bug fix. Item (2) is more of an interesting corner case. I think most people would agree that if you put
@pure
on a function that prints, you shouldn't be surprised to see the printed output more or fewer times than you expected (including zero times). Similarly, considerHere I'd even say it's ok to skip throwing the error, since who's to say whether
isdir("/")
is true in the imaginary land of pure functions. However I think method errors are different. We can only conclude that a function returnsx
if it actually calls a method that returnsx
. If the method that returnsx
is not called at all for a certain argument, it can't be the result even of a pure function.