<p dir="auto">While trying to benchmark JuMP solvers on M1, we found that JuMP requires a compiler feature that is apparently not available on aarch64:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$ julia --project=. bench.jl 
ERROR: LoadError: cfunction: closures are not supported on this platform
Stacktrace:
  [1] _set_callback
    @ ~/.julia/dev/GLPK/src/MOI_wrapper/MOI_wrapper.jl:259 [inlined]
  [2] empty!(model::GLPK.Optimizer)
    @ GLPK ~/.julia/dev/GLPK/src/MOI_wrapper/MOI_wrapper.jl:290
  [3] GLPK.Optimizer(; want_infeasibility_certificates::Bool, method::GLPK.MethodEnum, kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ GLPK ~/.julia/dev/GLPK/src/MOI_wrapper/MOI_wrapper.jl:206
  [4] Optimizer
    @ ~/.julia/dev/GLPK/src/MOI_wrapper/MOI_wrapper.jl:171 [inlined]
  [5] _instantiate_and_check(optimizer_constructor::Type{GLPK.Optimizer})
    @ MathOptInterface ~/.julia/packages/MathOptInterface/YDdD3/src/instantiate.jl:62
  [6] instantiate(optimizer_constructor::Type; with_bridge_type::Type{Float64}, with_names::Bool)
    @ MathOptInterface ~/.julia/packages/MathOptInterface/YDdD3/src/instantiate.jl:120
  [7] set_optimizer(model::Model, optimizer_constructor::Type; bridge_constraints::Bool)
    @ JuMP ~/.julia/packages/JuMP/klrjG/src/optimizer_interface.jl:109
  [8] Model(optimizer_factory::Type; bridge_constraints::Bool, kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ JuMP ~/.julia/packages/JuMP/klrjG/src/JuMP.jl:287
  [9] Model
    @ ~/.julia/packages/JuMP/klrjG/src/JuMP.jl:286 [inlined]
 [10] example_factory_schedule(factory::Type)
    @ Main ~/mlp_bench/bench.jl:557
 [11] top-level scope
    @ ~/mlp_bench/bench.jl:605
in expression starting at /Users/sabae/mlp_bench/bench.jl:602"><pre class="notranslate"><code class="notranslate">$ julia --project=. bench.jl 
ERROR: LoadError: cfunction: closures are not supported on this platform
Stacktrace:
  [1] _set_callback
    @ ~/.julia/dev/GLPK/src/MOI_wrapper/MOI_wrapper.jl:259 [inlined]
  [2] empty!(model::GLPK.Optimizer)
    @ GLPK ~/.julia/dev/GLPK/src/MOI_wrapper/MOI_wrapper.jl:290
  [3] GLPK.Optimizer(; want_infeasibility_certificates::Bool, method::GLPK.MethodEnum, kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ GLPK ~/.julia/dev/GLPK/src/MOI_wrapper/MOI_wrapper.jl:206
  [4] Optimizer
    @ ~/.julia/dev/GLPK/src/MOI_wrapper/MOI_wrapper.jl:171 [inlined]
  [5] _instantiate_and_check(optimizer_constructor::Type{GLPK.Optimizer})
    @ MathOptInterface ~/.julia/packages/MathOptInterface/YDdD3/src/instantiate.jl:62
  [6] instantiate(optimizer_constructor::Type; with_bridge_type::Type{Float64}, with_names::Bool)
    @ MathOptInterface ~/.julia/packages/MathOptInterface/YDdD3/src/instantiate.jl:120
  [7] set_optimizer(model::Model, optimizer_constructor::Type; bridge_constraints::Bool)
    @ JuMP ~/.julia/packages/JuMP/klrjG/src/optimizer_interface.jl:109
  [8] Model(optimizer_factory::Type; bridge_constraints::Bool, kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ JuMP ~/.julia/packages/JuMP/klrjG/src/JuMP.jl:287
  [9] Model
    @ ~/.julia/packages/JuMP/klrjG/src/JuMP.jl:286 [inlined]
 [10] example_factory_schedule(factory::Type)
    @ Main ~/mlp_bench/bench.jl:557
 [11] top-level scope
    @ ~/mlp_bench/bench.jl:605
in expression starting at /Users/sabae/mlp_bench/bench.jl:602
</code></pre></div>
<p dir="auto">The relevant code can be found here: <a href="https://github.com/jump-dev/GLPK.jl/blob/ebc73abbf0c8352c0509932aa1ad57a69716a2e6/src/MOI_wrapper/MOI_wrapper.jl#L242-L259">https://github.com/jump-dev/GLPK.jl/blob/ebc73abbf0c8352c0509932aa1ad57a69716a2e6/src/MOI_wrapper/MOI_wrapper.jl#L242-L259</a></p>
<p dir="auto">Reading through the docs, it appears <a href="https://docs.julialang.org/en/v1/manual/calling-c-and-fortran-code/#Closure-cfunctions" rel="nofollow">this is advertised</a>, but since such a prominent package uses them internally, it seems like we might want to see if there's a workaround we can use.</p>
<p dir="auto">Hi!</p>
<p dir="auto">I've discovered that many (runtime) performance issues with broadcasting are caused by inlining not working with the highly recursive broadcasting code. It turns out that defining more methods can actually help here. Here is a piece of code you can evaluate in REPL to see that:</p>
<div class="highlight highlight-source-julia notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="
struct RecursiveInliningEnforcerA{T}
    makeargs::T
end

struct RecursiveInliningEnforcerB{TMT,TMH,TT,TH,TF}
    makeargs_tail::TMT
    makeargs_head::TMH
    headargs::TH
    tailargs::TT
    f::TF
end
for UB in [Any, RecursiveInliningEnforcerA]
    @eval @inline function (bb::RecursiveInliningEnforcerB{TMT,TMH,TT,TH,TF})(args::Vararg{Any,N}) where {N,TMT,TMH&lt;:$UB,TT,TH,TF}
        args1 = bb.makeargs_head(args...)
        a = bb.headargs(args1...)
        b = bb.makeargs_tail(bb.tailargs(args1...)...)
        return (bb.f(a...), b...)
    end
end

for UB in [Any, RecursiveInliningEnforcerB]
    @eval @inline (a::RecursiveInliningEnforcerA{TTA})(head::TH, tail::Vararg{Any,N}) where {TTA&lt;:$UB,TH,N} = (head, a.makeargs(tail...)...)
end

@inline function Broadcast.make_makeargs(makeargs_tail::TT, t::Tuple) where TT
    return RecursiveInliningEnforcerA(Broadcast.make_makeargs(makeargs_tail, Base.tail(t)))
end

function Broadcast.make_makeargs(makeargs_tail, t::Tuple{&lt;:Broadcast.Broadcasted, Vararg{Any}})
    bc = t[1]
    # c.f. the same expression in the function on leaf nodes above. Here
    # we recurse into siblings in the broadcast tree.
    let makeargs_tail = Broadcast.make_makeargs(makeargs_tail, Base.tail(t)),
            # Here we recurse into children. It would be valid to pass in makeargs_tail
            # here, and not use it below. However, in that case, our recursion is no
            # longer purely structural because we're building up one argument (the closure)
            # while destructuing another.
            makeargs_head = Broadcast.make_makeargs((args...)-&gt;args, bc.args),
            f = bc.f
        # Create two functions, one that splits of the first length(bc.args)
        # elements from the tuple and one that yields the remaining arguments.
        # N.B. We can't call headargs on `args...` directly because
        # args is flattened (i.e. our children have not been evaluated
        # yet).
        headargs, tailargs = Broadcast.make_headargs(bc.args), Broadcast.make_tailargs(bc.args)
        return RecursiveInliningEnforcerB(makeargs_tail, makeargs_head, headargs, tailargs, f)
    end
end"><pre class="notranslate"><span class="pl-k">struct</span> RecursiveInliningEnforcerA{T}
    makeargs<span class="pl-k">::</span><span class="pl-c1">T</span>
<span class="pl-k">end</span>

<span class="pl-k">struct</span> RecursiveInliningEnforcerB{TMT,TMH,TT,TH,TF}
    makeargs_tail<span class="pl-k">::</span><span class="pl-c1">TMT</span>
    makeargs_head<span class="pl-k">::</span><span class="pl-c1">TMH</span>
    headargs<span class="pl-k">::</span><span class="pl-c1">TH</span>
    tailargs<span class="pl-k">::</span><span class="pl-c1">TT</span>
    f<span class="pl-k">::</span><span class="pl-c1">TF</span>
<span class="pl-k">end</span>
<span class="pl-k">for</span> UB <span class="pl-k">in</span> [Any, RecursiveInliningEnforcerA]
    <span class="pl-c1">@eval</span> <span class="pl-c1">@inline</span> <span class="pl-k">function</span> (bb<span class="pl-k">::</span><span class="pl-c1">RecursiveInliningEnforcerB{TMT,TMH,TT,TH,TF}</span>)(args<span class="pl-k">::</span><span class="pl-c1">Vararg{Any,N}</span>) <span class="pl-k">where</span> {N,TMT,TMH<span class="pl-k">&lt;:</span><span class="pl-c1">$UB</span>,TT,TH,TF}
        args1 <span class="pl-k">=</span> bb<span class="pl-k">.</span><span class="pl-c1">makeargs_head</span>(args<span class="pl-k">...</span>)
        a <span class="pl-k">=</span> bb<span class="pl-k">.</span><span class="pl-c1">headargs</span>(args1<span class="pl-k">...</span>)
        b <span class="pl-k">=</span> bb<span class="pl-k">.</span><span class="pl-c1">makeargs_tail</span>(bb<span class="pl-k">.</span><span class="pl-c1">tailargs</span>(args1<span class="pl-k">...</span>)<span class="pl-k">...</span>)
        <span class="pl-k">return</span> (bb<span class="pl-k">.</span><span class="pl-c1">f</span>(a<span class="pl-k">...</span>), b<span class="pl-k">...</span>)
    <span class="pl-k">end</span>
<span class="pl-k">end</span>

<span class="pl-k">for</span> UB <span class="pl-k">in</span> [Any, RecursiveInliningEnforcerB]
    <span class="pl-c1">@eval</span> <span class="pl-c1">@inline</span> (a<span class="pl-k">::</span><span class="pl-c1">RecursiveInliningEnforcerA{TTA}</span>)(head<span class="pl-k">::</span><span class="pl-c1">TH</span>, tail<span class="pl-k">::</span><span class="pl-c1">Vararg{Any,N}</span>) <span class="pl-k">where</span> {TTA<span class="pl-k">&lt;:</span><span class="pl-c1">$UB</span>,TH,N} <span class="pl-k">=</span> (head, a<span class="pl-k">.</span><span class="pl-c1">makeargs</span>(tail<span class="pl-k">...</span>)<span class="pl-k">...</span>)
<span class="pl-k">end</span>

<span class="pl-c1">@inline</span> <span class="pl-k">function</span> Broadcast<span class="pl-k">.</span><span class="pl-en">make_makeargs</span>(makeargs_tail<span class="pl-k">::</span><span class="pl-c1">TT</span>, t<span class="pl-k">::</span><span class="pl-c1">Tuple</span>) <span class="pl-k">where</span> TT
    <span class="pl-k">return</span> <span class="pl-c1">RecursiveInliningEnforcerA</span>(Broadcast<span class="pl-k">.</span><span class="pl-c1">make_makeargs</span>(makeargs_tail, Base<span class="pl-k">.</span><span class="pl-c1">tail</span>(t)))
<span class="pl-k">end</span>

<span class="pl-k">function</span> Broadcast<span class="pl-k">.</span><span class="pl-en">make_makeargs</span>(makeargs_tail, t<span class="pl-k">::</span><span class="pl-c1">Tuple{&lt;:Broadcast.Broadcasted, Vararg{Any}}</span>)
    bc <span class="pl-k">=</span> t[<span class="pl-c1">1</span>]
    <span class="pl-c"><span class="pl-c">#</span> c.f. the same expression in the function on leaf nodes above. Here</span>
    <span class="pl-c"><span class="pl-c">#</span> we recurse into siblings in the broadcast tree.</span>
    <span class="pl-k">let</span> makeargs_tail <span class="pl-k">=</span> Broadcast<span class="pl-k">.</span><span class="pl-c1">make_makeargs</span>(makeargs_tail, Base<span class="pl-k">.</span><span class="pl-c1">tail</span>(t)),
            <span class="pl-c"><span class="pl-c">#</span> Here we recurse into children. It would be valid to pass in makeargs_tail</span>
            <span class="pl-c"><span class="pl-c">#</span> here, and not use it below. However, in that case, our recursion is no</span>
            <span class="pl-c"><span class="pl-c">#</span> longer purely structural because we're building up one argument (the closure)</span>
            <span class="pl-c"><span class="pl-c">#</span> while destructuing another.</span>
            makeargs_head <span class="pl-k">=</span> Broadcast<span class="pl-k">.</span><span class="pl-c1">make_makeargs</span>((args<span class="pl-k">...</span>)<span class="pl-k">-&gt;</span>args, bc<span class="pl-k">.</span>args),
            f <span class="pl-k">=</span> bc<span class="pl-k">.</span>f
        <span class="pl-c"><span class="pl-c">#</span> Create two functions, one that splits of the first length(bc.args)</span>
        <span class="pl-c"><span class="pl-c">#</span> elements from the tuple and one that yields the remaining arguments.</span>
        <span class="pl-c"><span class="pl-c">#</span> N.B. We can't call headargs on `args...` directly because</span>
        <span class="pl-c"><span class="pl-c">#</span> args is flattened (i.e. our children have not been evaluated</span>
        <span class="pl-c"><span class="pl-c">#</span> yet).</span>
        headargs, tailargs <span class="pl-k">=</span> Broadcast<span class="pl-k">.</span><span class="pl-c1">make_headargs</span>(bc<span class="pl-k">.</span>args), Broadcast<span class="pl-k">.</span><span class="pl-c1">make_tailargs</span>(bc<span class="pl-k">.</span>args)
        <span class="pl-k">return</span> <span class="pl-c1">RecursiveInliningEnforcerB</span>(makeargs_tail, makeargs_head, headargs, tailargs, f)
    <span class="pl-k">end</span>
<span class="pl-k">end</span></pre></div>
<p dir="auto">This effectively duplicates these two functions: </p><div class="Box Box--condensed my-2">
  <div class="Box-header f6">
    <p class="mb-0 text-bold">
      <a href="https://github.com/JuliaLang/julia/blob/abbb220b89ebcec87efd9fbf6c0ccae4f2a3ef4a/base/broadcast.jl#L380-L384">julia/base/broadcast.jl</a>
    </p>
    <p class="mb-0 color-fg-muted">
        Lines 380 to 384
      in
      <a data-pjax="true" class="commit-tease-sha" href="/JuliaLang/julia/commit/abbb220b89ebcec87efd9fbf6c0ccae4f2a3ef4a">abbb220</a>
    </p>
  </div>
  <div itemprop="text" class="Box-body p-0 blob-wrapper blob-wrapper-embedded data">
    <table class="highlight tab-size mb-0 js-file-line-container" data-tab-size="8" data-paste-markdown-skip="">

        <tbody><tr class="border-0">
          <td id="L380" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="380"></td>
          <td id="LC380" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> <span class="pl-k">return</span> <span class="pl-c1">@inline</span> <span class="pl-k">function</span>(args<span class="pl-k">::</span><span class="pl-c1">Vararg{Any,N}</span>) <span class="pl-k">where</span> N </td>
        </tr>

        <tr class="border-0">
          <td id="L381" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="381"></td>
          <td id="LC381" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line">     args1 <span class="pl-k">=</span> <span class="pl-c1">makeargs_head</span>(args<span class="pl-k">...</span>) </td>
        </tr>

        <tr class="border-0">
          <td id="L382" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="382"></td>
          <td id="LC382" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line">     a, b <span class="pl-k">=</span> <span class="pl-c1">headargs</span>(args1<span class="pl-k">...</span>), <span class="pl-c1">makeargs_tail</span>(<span class="pl-c1">tailargs</span>(args1<span class="pl-k">...</span>)<span class="pl-k">...</span>) </td>
        </tr>

        <tr class="border-0">
          <td id="L383" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="383"></td>
          <td id="LC383" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line">     (<span class="pl-c1">f</span>(a<span class="pl-k">...</span>), b<span class="pl-k">...</span>) </td>
        </tr>

        <tr class="border-0">
          <td id="L384" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="384"></td>
          <td id="LC384" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> <span class="pl-k">end</span> </td>
        </tr>
    </tbody></table>
  </div>
</div>
 and <div class="Box Box--condensed my-2">
  <div class="Box-header f6">
    <p class="mb-0 text-bold">
      <a href="https://github.com/JuliaLang/julia/blob/abbb220b89ebcec87efd9fbf6c0ccae4f2a3ef4a/base/broadcast.jl#L361">julia/base/broadcast.jl</a>
    </p>
    <p class="mb-0 color-fg-muted">
         Line 361
      in
      <a data-pjax="true" class="commit-tease-sha" href="/JuliaLang/julia/commit/abbb220b89ebcec87efd9fbf6c0ccae4f2a3ef4a">abbb220</a>
    </p>
  </div>
  <div itemprop="text" class="Box-body p-0 blob-wrapper blob-wrapper-embedded data">
    <table class="highlight tab-size mb-0 js-file-line-container" data-tab-size="8" data-paste-markdown-skip="">

        <tbody><tr class="border-0">
          <td id="L361" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="361"></td>
          <td id="LC361" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> (head, tail<span class="pl-k">...</span>)<span class="pl-k">-&gt;</span>(head, <span class="pl-c1">makeargs</span>(tail<span class="pl-k">...</span>)<span class="pl-k">...</span>) </td>
        </tr>
    </tbody></table>
  </div>
</div>
 for different argument types.<p></p>
<p dir="auto">It turns out that it's sufficient to fix the following issues:<br>
<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="388288356" data-permission-text="Title is private" data-url="https://github.com/JuliaArrays/StaticArrays.jl/issues/560" data-hovercard-type="issue" data-hovercard-url="/JuliaArrays/StaticArrays.jl/issues/560/hovercard" href="https://github.com/JuliaArrays/StaticArrays.jl/issues/560">JuliaArrays/StaticArrays.jl#560</a><br>
<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="514047184" data-permission-text="Title is private" data-url="https://github.com/JuliaArrays/StaticArrays.jl/issues/682" data-hovercard-type="issue" data-hovercard-url="/JuliaArrays/StaticArrays.jl/issues/682/hovercard" href="https://github.com/JuliaArrays/StaticArrays.jl/issues/682">JuliaArrays/StaticArrays.jl#682</a><br>
<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="449690176" data-permission-text="Title is private" data-url="https://github.com/JuliaArrays/StaticArrays.jl/issues/609" data-hovercard-type="issue" data-hovercard-url="/JuliaArrays/StaticArrays.jl/issues/609/hovercard" href="https://github.com/JuliaArrays/StaticArrays.jl/issues/609">JuliaArrays/StaticArrays.jl#609</a><br>
<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="631076890" data-permission-text="Title is private" data-url="https://github.com/JuliaArrays/StaticArrays.jl/issues/797" data-hovercard-type="issue" data-hovercard-url="/JuliaArrays/StaticArrays.jl/issues/797/hovercard" href="https://github.com/JuliaArrays/StaticArrays.jl/issues/797">JuliaArrays/StaticArrays.jl#797</a></p>
<p dir="auto">What do you think about it?</p>