inlining: relax `finalizer` inlining control-flow restriction (#46651)
Eager `finalizer` inlining (#45272) currently has a restriction that
requires all the def/uses to be in a same basic block.
This commit relaxes that restriction a bit by allowing def/uses to
involve control flow when all of them are dominated by a `finalizer`
call to be inlined, since in that case it is safe to insert the body of
`finalizer` at the end of all the def/uses, e.g.
```julia
const FINALIZATION_COUNT = Ref(0)
init_finalization_count!() = FINALIZATION_COUNT[] = 0
get_finalization_count() = FINALIZATION_COUNT[]
@noinline add_finalization_count!(x) = FINALIZATION_COUNT[] += x
@noinline Base.@assume_effects :nothrow safeprint(io::IO, x...) = (@nospecialize; print(io, x...))
mutable struct DoAllocWithFieldInter
x::Int
end
function register_finalizer!(obj::DoAllocWithFieldInter)
finalizer(obj) do this
add_finalization_count!(this.x)
end
end
function cfg_finalization3(io)
for i = -999:1000
o = DoAllocWithFieldInter(i)
register_finalizer!(o)
if i == 1000
safeprint(io, o.x, '\n')
elseif i > 0
safeprint(io, o.x)
end
end
end
let src = code_typed1(cfg_finalization3, (IO,))
@test count(isinvoke(:add_finalization_count!), src.code) == 1
end
let
init_finalization_count!()
cfg_finalization3(IOBuffer())
@test get_finalization_count() == 1000
end
```
To support this transformation, the domtree code also gains the ability
to represent post-dominator trees, which is generally useful.