-
Notifications
You must be signed in to change notification settings - Fork 6
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
add flow-insensitive, local field / alias analysis #54
Conversation
4212ad7
to
1b207be
Compare
The JET test failures are all attributed to Base definitions, and they should be fixed by JuliaLang/julia#43087. |
1645d64
to
85aa8be
Compare
Codecov Report
@@ Coverage Diff @@
## master #54 +/- ##
==========================================
+ Coverage 71.19% 75.99% +4.80%
==========================================
Files 2 3 +1
Lines 361 604 +243
==========================================
+ Hits 257 459 +202
- Misses 104 145 +41
Continue to review full report at Codecov.
|
b3b0e20
to
e0017a3
Compare
e27cf0b
to
d6d6d85
Compare
This comment has been minimized.
This comment has been minimized.
d1763f6
to
e2046b9
Compare
@@ -227,6 +274,14 @@ can_elide_finalizer(x::EscapeLattice, pc::Int) = | |||
# we need to make sure this `==` operator corresponds to lattice equality rather than object equality, | |||
# otherwise `propagate_changes` can't detect the convergence | |||
x::EscapeLattice == y::EscapeLattice = begin | |||
xf, yf = x.FieldSets, y.FieldSets | |||
if isa(xf, Bool) | |||
isa(yf, Bool) || return false |
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.
isa(yf, Bool) || return false |
e2046b9
to
31f9725
Compare
71e2373
to
abac396
Compare
abac396
to
31831b8
Compare
""" | ||
struct EscapeState | ||
arguments::Vector{EscapeLattice} | ||
ssavalues::Vector{EscapeLattice} | ||
aliasset::IntDisjointSet{Int} |
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 quite understand aliasset
, or rather, the difference between the information stored in aliasset
and that stored in FieldSets
. Do we use aliasset
for performance reasons? IUIC we could (albeit with probably slow performance) write get_aliases(state::EscapeState, x, ir)
by searching for x
's value within the FieldSets
of state.arguments
and/or state.ssavalues
?
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 introduced aliasset
so that we can track a case like below:
analyze_escapes((String,)) do a # => ReturnEscape
o1 = Ref(a) # => NoEscape
o2 = Ref(o1) # => NoEscape
o1′ = o2[] # (aliased to `o1`)
a′ = o1′[] # (needs to propagate ReturnEscape to `a`)
return a′
end
That is, FieldSets
tracks values of a field that are known from Expr(:new, ...)
or setfield!
call, but in order to handle a case where escape information propagates through values that are aliased by getfield
chain, we need some external set that holds all the possible values that can be aliased to the field.
Having said that, now I think the design of FieldSets
is wrong.
I made FieldSets
tracks actual values (i.e. SSAValue
/Argument
) and escape information is propagated via those values (and also via aliased values by looking at aliasset
), just because I had SROA in my mind as a downstream optimization so that it can directly use that information of field values.
But the important observation here is that field information is something like type information, and it is something better propagated in a forward direction. This PR tries to analyze field information within a backward analysis, but it leads a complex and inefficient (and likely incomplete) data flow.
It may be better not to include a design that is specific downstream optimization in mind (like SROA), and I think we can have a better implementation by making FieldSets
somewhat like aliasset
, and just trying to propagate escape information without holding field values.
7661c3a
to
c008837
Compare
Okay, I think I've finished this PR. I hope the updated README well describes the general algorithm of EA, and key ideas of newly introduced field and alias analyses. As for As @ianatol pointed, it is possible to propagate escape information to aliased values when encountering new aliasing, but it turns out less efficient than maintaining external aliasset-like data structure.
On the other hand, if we equalize escape information between aliased values (c008837) with an external
The key observation is that aliasing happens at "definition" site, and thus is somewhat better to be propagated in forwardly. |
8f1a6aa
to
ef58547
Compare
This commit implements a simple, flow-insensitive alias analysis using an approach inspired by the escape analysis algorithm explained in the old JVM paper [^JVM05]. `EscapeLattice` is extended so that it also keeps track of possible field values. In more detail, `x::EscapeLattice` has the new field called `x.FieldSet::Union{Vector{IdSet{Any}},Bool}`, where: - `x.FieldSets === false` indicates the fields of `x` isn't analyzed yet - `x.FieldSets === true` indicates the fields of `x` can't be analyzed, e.g. the type of `x` is not concrete and thus the number of its fields can't known precisely - otherwise `x.FieldSets::Vector{IdSet{Any}}` holds all the possible values of each field, where `x.FieldSets[i]` keeps all possibilities that the `i`th field can be And now, in addition to managing escape lattice elements, the analysis state also maintains an "alias set" `state.aliasset::IntDisjointSet{Int}`, which is implemented as a disjoint set of aliased arguments and SSA statements. When the fields of object `x` are known precisely (i.e. `x.FieldSets isa Vector{IdSet{Any}}` holds), the alias set is updated each time `z = getfield(x, y)` is encountered in a way that `z` is aliased to all values of `x.FieldSets[y]`, so that escape information imposed on `z` will be propagated to all the aliased values and `z` can be replaced with an aliased value later. Note that in a case when the fields of object `x` can't known precisely (i.e. `x.FieldSets` is `true`), when `z = getfield(x, y)` is analyzed, escape information of `z` is propagated to `x` rather than any of `x`'s fields, which is the most conservative propagation since escape information imposed on `x` will end up being propagated to all of its fields anyway at definitions of `x` (i.e. `:new` expression or `setfield!` call). [^JVM05]: Escape Analysis in the Context of Dynamic Compilation and Deoptimization. Thomas Kotzmann and Hanspeter Mössenböck, 2005, June. <https://dl.acm.org/doi/10.1145/1064979.1064996>. Now this alias analysis should allow us to implement a "stronger" SROA, which eliminates the allocation of `r` within the following code: ```julia julia> result = analyze_escapes((String,)) do s r = Ref(s) broadcast(identity, r) end \#3(_2::String *, _3::Base.RefValue{String} ◌) in Main at REPL[2]:2 2 ↓ 1 ─ %1 = %new(Base.RefValue{String}, _2)::Base.RefValue{String} │╻╷╷ Ref 3 ✓ │ %2 = Core.tuple(%1)::Tuple{Base.RefValue{String}} │╻ broadcast ↓ │ %3 = Core.getfield(%2, 1)::Base.RefValue{String} ││ ◌ └── goto #3 if not true ││╻╷ materialize ◌ 2 ─ nothing::Nothing │ * 3 ┄ %6 = Base.getfield(%3, :x)::String │││╻╷╷╷╷ copy ◌ └── goto #4 ││││┃ getindex ◌ 4 ─ goto #5 ││││ ◌ 5 ─ goto #6 │││ ◌ 6 ─ goto #7 ││ ◌ 7 ─ return %6 │ julia> EscapeAnalysis.get_aliases(result.state.aliasset, Core.SSAValue(6), result.ir) 2-element Vector{Union{Core.Argument, Core.SSAValue}}: Core.Argument(2) :(%6) ``` Note that the allocation `%1` isn't analyzed as `ReturnEscape`, still `_2` is analyzed so.
ef58547
to
030a758
Compare
# demonstrate the power of our field / alias analysis with a realistic end to end example | ||
abstract type AbstractPoint{T} end | ||
mutable struct MPoint{T} <: AbstractPoint{T} | ||
x::T | ||
y::T | ||
end | ||
add(a::P, b::P) where P<:AbstractPoint = P(a.x + b.x, a.y + b.y) | ||
function compute(T, ax, ay, bx, by) | ||
a = T(ax, ay) | ||
b = T(bx, by) | ||
for i in 0:(100000000-1) | ||
a = add(add(a, b), b) | ||
end | ||
a.x, a.y | ||
end | ||
function compute(a, b) | ||
for i in 0:(100000000-1) | ||
a = add(add(a, b), b) | ||
end | ||
a.x, a.y | ||
end | ||
function compute!(a, b) | ||
for i in 0:(100000000-1) | ||
a′ = add(add(a, b), b) | ||
a.x = a′.x | ||
a.y = a′.y | ||
end | ||
end | ||
let result = @analyze_escapes compute(MPoint, 1+.5im, 2+.5im, 2+.25im, 4+.75im) | ||
for i in findall(isnew, result.ir.stmts.inst) | ||
@test is_sroa_eligible(result.state.ssavalues[i]) | ||
end | ||
end | ||
let result = @analyze_escapes compute(MPoint(1+.5im, 2+.5im), MPoint(2+.25im, 4+.75im)) | ||
for i in findall(isnew, result.ir.stmts.inst) | ||
@test is_sroa_eligible(result.state.ssavalues[i]) | ||
end | ||
end | ||
let result = @analyze_escapes compute!(MPoint(1+.5im, 2+.5im), MPoint(2+.25im, 4+.75im)) | ||
for i in findall(isnew, result.ir.stmts.inst) | ||
@test is_sroa_eligible(result.state.ssavalues[i]) | ||
end | ||
end |
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 this PR will allow us to fully eliminate allocations involved within these examples (taken from LuaJIT's allocation sinking).
EDIT: rather than reading the description below, please see newly added README as well as #54 (comment).
This commit implements a simple, flow-insensitive alias analysis using an
approach inspired by the escape analysis algorithm explained in
the old JVM paper 1.
EscapeLattice
is extended so that it also keeps track of possible field values.In more detail,
x::EscapeLattice
has the new field calledx.FieldSet::Union{Vector{IdSet{Any}},Bool}
, where:x.FieldSets === false
indicates the fields ofx
isn't analyzed yetx.FieldSets === true
indicates the fields ofx
can't be analyzed,e.g. the type of
x
is not concrete and thus the number of its fieldscan't known precisely
x.FieldSets::Vector{IdSet{Any}}
holds all the possiblevalues of each field, where
x.FieldSets[i]
keeps all possibilitiesthat the
i
th field can beAnd now, in addition to managing escape lattice elements, the analysis
state also maintains an "alias set"
state.aliasset::IntDisjointSet{Int}
,which is implemented as a disjoint set of aliased arguments and SSA statements.
When the fields of object
x
are known precisely (i.e.x.FieldSets isa Vector{IdSet{Any}}
holds),the alias set is updated each time
z = getfield(x, y)
is encountered in a way thatz
isaliased to all values of
x.FieldSets[y]
, so that escape information imposed onz
will bepropagated to all the aliased values and
z
can be replaced with an aliased value later.Note that in a case when the fields of object
x
can't known precisely (i.e.x.FieldSets
istrue
),when
z = getfield(x, y)
is analyzed, escape information ofz
is propagated tox
ratherthan any of
x
's fields, which is the most conservative propagation since escape informationimposed on
x
will end up being propagated to all of its fields anyway at definitions ofx
(i.e.
:new
expression orsetfield!
call).Now this alias analysis should allow us to implement a "stronger" SROA,
which eliminates the allocation of
r
within the following code:Note that the allocation
%1
isn't analyzed asReturnEscape
, still_2
is analyzed so.Remaining TODOs:
Footnotes
Escape Analysis in the Context of Dynamic Compilation and Deoptimization.
Thomas Kotzmann and Hanspeter Mössenböck, 2005, June.
https://dl.acm.org/doi/10.1145/1064979.1064996. ↩