effects: improve `:effect_free`-ness analysis for local mutability (#46200)
This commit improves the accuracy of the `:effect-free`-ness analysis,
that currently doesn't handle `setfield!` call on local mutable object
pretty well.
The existing analysis taints `:effect_free`-ness upon any `setfield!`
call on mutable object because we really don't have a knowledge about
the object lifetime and so we need to conservatively take into account a
possibility of the mutable object being a global variable.
However we can "recover" `:effect_free`-cness tainted by `setfield!` on
mutable object when the newly added `:noglobal` helper effect has been
proven because in that case we can conclude that all mutable objects
accessed within the method are purely local and `setfield!` on them
are `:effect_free` (more precisely we also need to confirm that all the
call arguments are known not to be mutable global objects to derive this
conclusion).
For example now we can prove `:effect_free`-ness of the function below
and it will be DCE-eligible (and it will even be concrete-evaluated
after #46184):
```julia
julia> makeref() = Ref{Any}()
makeref (generic function with 1 method)
julia> setref!(ref, @nospecialize v) = ref[] = v
setref! (generic function with 1 method)
julia> @noinline function mutable_effect_free(v)
x = makeref()
setref!(x, v)
x
end
mutable_effect_free (generic function with 1 method)
julia> Base.infer_effects(mutable_effect_free, (String,))
(!c,+e,+n,+t,+s,+g)
julia> code_typed() do
mutable_effect_free("foo") # will be DCE-ed
nothing
end
1-element Vector{Any}:
CodeInfo(
1 ─ return Main.nothing
) => Nothing
```