I'm not going to copy the whole of `@code_lowered optimize=true foo!(...)` here, but it does seem to work to a certain extend.
Even with optimize=false
you can see that it figured out calls that do nothing:
julia> o1 = Vector{Float64}(undef,2); o2 = NotWanted(); o3 = NotWanted()
NotWanted()
julia> @code_typed optimize=false foo!(o1,o2,o3,i1,i2)
CodeInfo(
1 β %1 = (2 * i1)::Vector{Float64}
β Base.setindex!(o1, %1, Main.:(:))::Any
β %3 = Base.broadcasted(Main.:+, i1, i2)::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1}, Nothing, typeof(+), Tuple{Vector{Float64}, Vector{Float64}}}
β %4 = Base.materialize(%3)::Vector{Float64}
β %5 = (3 * %4)::Vector{Float64}
β Base.setindex!(o2, %5, Main.:(:))::Union{}
β Core.Const(:(Base.broadcasted(Main.:+, i1, i2)))::Union{}
β Core.Const(:(tmp = Base.materialize(%7)))::Union{}
β Core.Const(:(3 * tmp))::Union{}
β Core.Const(:(Base.setindex!(o3, %9, Main.:(:))))::Union{}
βββ Core.Const(:(return %9))::Union{}
) => Union{}
julia> o1 = Vector{Float64}(undef,2); o2 = Vector{Int}(undef, 2); o3 = Vector{Int}(undef, 2)
2-element Vector{Int64}:
209904464
209904896
julia> @code_typed optimize=false foo!(o1,o2,o3,i1,i2)
CodeInfo(
1 β %1 = (2 * i1)::Vector{Float64}
β Base.setindex!(o1, %1, Main.:(:))::Any
β %3 = Base.broadcasted(Main.:+, i1, i2)::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1}, Nothing, typeof(+), Tuple{Vector{Float64}, Vector{Float64}}}
β %4 = Base.materialize(%3)::Vector{Float64}
β %5 = (3 * %4)::Vector{Float64}
β Base.setindex!(o2, %5, Main.:(:))::Any
β %7 = Base.broadcasted(Main.:+, i1, i2)::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1}, Nothing, typeof(+), Tuple{Vector{Float64}, Vector{Float64}}}
β (tmp = Base.materialize(%7))::Vector{Float64}
β %9 = (3 * tmp)::Vector{Float64}
β Base.setindex!(o3, %9, Main.:(:))::Any
βββ return %9
) => Vector{Float64}
The same with optimize=true
is way too unwieldy to fit here, but if you look at the length and certain key lines, it does seem to do quite well:
julia> o1 = Vector{Float64}(undef,2); o2 = NotWanted(); o3 = NotWanted();
julia> @code_typed optimize=true foo!(o1,o2,o3,i1,i2)
CodeInfo(
1 βββ %1 = invoke Main.:*(2::Int64, i1::Vector{Float64})::Vector{Float64}
β %2 = Base.arraylen(o1)::Int64
βββββ goto #6 if not true
2 βββ %4 = Base.arraylen(%1)::Int64
β %5 = (%4 === %2)::Bool
βββββ goto #4 if not %5
3 βββ goto #5
4 βββ %8 = Core.tuple(%2)::Tuple{Int64}
β invoke Base.throw_setindex_mismatch(%1::Vector{Float64}, %8::Tuple{Int64})::Union{}
βββββ unreachable
5 βββ nothing::Nothing
6 βββ %12 = Base.slt_int(0, %2)::Bool
βββββ goto #16 if not %12
7 βββ %14 = $(Expr(:gc_preserve_begin, Core.Argument(2)))
β %15 = $(Expr(:gc_preserve_begin, :(%1)))
β %16 = $(Expr(:foreigncall, :(:jl_array_ptr), Ptr{Float64}, svec(Any), 0, :(:ccall), Core.Argument(2)))::Ptr{Float64}
β Base.arraysize(o1, 1)::Int64
β %18 = Core.bitcast(Core.UInt, %16)::UInt64
β %19 = Base.add_ptr(%18, 0x0000000000000000)::UInt64
β %20 = Core.bitcast(Ptr{Float64}, %19)::Ptr{Float64}
β %21 = $(Expr(:foreigncall, :(:jl_array_ptr), Ptr{Float64}, svec(Any), 0, :(:ccall), :(%1)))::Ptr{Float64}
β Base.arraysize(%1, 1)::Int64
β %23 = Core.bitcast(Core.UInt, %21)::UInt64
β %24 = Base.add_ptr(%23, 0x0000000000000000)::UInt64
β %25 = Core.bitcast(Ptr{Float64}, %24)::Ptr{Float64}
β %26 = Base.mul_int(%2, 8)::Int64
β %27 = Core.lshr_int(%26, 63)::Int64
β %28 = Core.trunc_int(Core.UInt8, %27)::UInt8
β %29 = Core.eq_int(%28, 0x01)::Bool
βββββ goto #9 if not %29
8 βββ invoke Core.throw_inexacterror(:check_top_bit::Symbol, UInt64::Type{UInt64}, %26::Int64)::Union{}
βββββ unreachable
9 βββ goto #10
10 ββ %34 = Core.bitcast(Core.UInt64, %26)::UInt64
βββββ goto #11
11 ββ goto #12
12 ββ goto #13
13 ββ goto #14
.
.
.
%109::Tuple{Base.OneTo{Int64}})::Union{}
βββββ unreachable
99 ββ goto #100
100 β goto #101
101 β goto #102
102 β %273 = invoke Main.:*(3::Int64, %114::Vector{Float64})::Vector{Float64}
β Base.setindex!(o2, %273, Main.:(:))::Union{}
βββββ unreachable
) => Union{}
And I found no remaining mentions of o2
aside from the unreachable at the end or of o3
.
Even better:
julia> o1 = NotWanted(); o2 = NotWanted(); o3 = NotWanted();
julia> @code_typed optimize=true foo!(o1,o2,o3,i1,i2)
CodeInfo(
1 β %1 = invoke Main.:*(2::Int64, i1::Vector{Float64})::Vector{Float64}
β Base.setindex!(o1, %1, Main.:(:))::Union{}
βββ unreachable
) => Union{}
Without NotWanted
s:
julia> o1 = Vector{Float64}(undef,2); o2 = Vector{Int}(undef, 2); o3 = Vector{Int}(undef, 2);
julia> @code_typed optimize=true foo!(o1,o2,o3,i1,i2)
CodeInfo(
1 βββ %1 = invoke Main.:*(2::Int64, i1::Vector{Float64})::Vector{Float64}
β %2 = Base.arraylen(o1)::Int64
βββββ goto #6 if not true
2 βββ %4 = Base.arraylen(%1)::Int64
β %5 = (%4 === %2)::Bool
βββββ goto #4 if not %5
3 βββ goto #5
4 βββ %8 = Core.tuple(%2)::Tuple{Int64}
β invoke Base.throw_setindex_mismatch(%1::Vector{Float64}, %8::Tuple{Int64})::Union{}
βββββ unreachable
5 βββ nothing::Nothing
6 βββ %12 = Base.slt_int(0, %2)::Bool
βββββ goto #16 if not %12
7 βββ %14 = $(Expr(:gc_preserve_begin, Core.Argument(2)))
β %15 = $(Expr(:gc_preserve_begin, :(%1)))
β %16 = $(Expr(:foreigncall, :(:jl_array_ptr), Ptr{Float64}, svec(Any), 0, :(:ccall), Core.Argument(2)))::Ptr{Float64}
β Base.arraysize(o1, 1)::Int64
β %18 = Core.bitcast(Core.UInt, %16)::UInt64
β %19 = Base.add_ptr(%18, 0x0000000000000000)::UInt64
β %20 = Core.bitcast(Ptr{Float64}, %19)::Ptr{Float64}
β %21 = $(Expr(:foreigncall, :(:jl_array_ptr), Ptr{Float64}, svec(Any), 0, :(:ccall), :(%1)))::Ptr{Float64}
β Base.arraysize(%1, 1)::Int64
β %23 = Core.bitcast(Core.UInt, %21)::UInt64
β %24 = Base.add_ptr(%23, 0x0000000000000000)::UInt64
β %25 = Core.bitcast(Ptr{Float64}, %24)::Ptr{Float64}
β %26 = Base.mul_int(%2, 8)::Int64
β %27 = Core.lshr_int(%26, 63)::Int64
β %28 = Core.trunc_int(Core.UInt8, %27)::UInt8
β %29 = Core.eq_int(%28, 0x01)::Bool
βββββ goto #9 if not %29
8 βββ invoke Core.throw_inexacterror(:check_top_bit::Symbol, UInt64::Type{UInt64}, %26::Int64)::Union{}
βββββ unreachable
9 βββ goto #10
10 ββ %34 = Core.bitcast(Core.UInt64, %26)::UInt64
βββββ goto #11
11 ββ goto #12
12 ββ goto #13
13 ββ goto #14
.
.
.
102 β %273 = invoke Main.:*(3::Int64, %114::Vector{Float64})::Vector{Float64}
β %274 = Base.arraysize(o2, 1)::Int64
β %275 = Base.slt_int(%274, 0)::Bool
β %276 = Base.ifelse(%275, 0, %274)::Int64
β %277 = %new(Base.OneTo{Int64}, %276)::Base.OneTo{Int64}
β %278 = %new(Base.Slice{Base.OneTo{Int64}}, %277)::Base.Slice{Base.OneTo{Int64}}
βββββ goto #104 if not true
103 β Base.arraysize(o2, 1)::Int64
βββββ Base.nothing::Nothing
104 β invoke Base._unsafe_setindex!($(QuoteNode(IndexLinear()))::IndexLinear, o2::Vector{Int64}, %273::Vector{Float64}, %278::Base.Slice{Base.OneTo{Int64}})::Any
.
.
.
191 β %511 = invoke Main.:*(3::Int64, %352::Vector{Float64})::Vector{Float64}
β %512 = Base.arraysize(o3, 1)::Int64
β %513 = Base.slt_int(%512, 0)::Bool
β %514 = Base.ifelse(%513, 0, %512)::Int64
β %515 = %new(Base.OneTo{Int64}, %514)::Base.OneTo{Int64}
β %516 = %new(Base.Slice{Base.OneTo{Int64}}, %515)::Base.Slice{Base.OneTo{Int64}}
βββββ goto #193 if not true
192 β Base.arraysize(o3, 1)::Int64
βββββ Base.nothing::Nothing
193 β invoke Base._unsafe_setindex!($(QuoteNode(IndexLinear()))::IndexLinear, o3::Vector{Int64}, %511::Vector{Float64}, %516::Base.Slice{Base.OneTo{Int64}})::Any
βββββ goto #194
194 β goto #195
195 β return %511
) => Vector{Float64}
And am I the only one who would be curious to see how well the compilers code elimination fares in such an application? I do have to say I support Imiqβs suggestion - itβll probably be a lot less headachy to maintain in the long run. But I find your idea interesting and it would be cool to see how well it performs. Have a nice day!