# Keyword arguments preventing eliding boundscheck

So I’ve got a MWE here with a weird behavior.
Note: this seems to be actually fixed in v1.9. But I couldn’t track any relevant issues to PR.

I’m trying to elide some checking code with @boundscheck. (yes, the real use case is a numerical hot loop)

At first I was experimenting with positional arguments:

``````f2(x, a=0) = @boundscheck "not elided!"
Base.@propagate_inbounds f3(x, a=0) = @boundscheck "not elided!"

function caller(x)
@inbounds @show f2(x)
@inbounds @show f2(x, 2)

@inbounds @show f3(x)
@inbounds @show f3(x, 2)
nothing
end
caller(0)

# f2(x) = "not elided!"
# f2(x, 2) = nothing
# f3(x) = nothing
# f3(x, 2) = nothing
``````

After I figured out how to work with `@inbounds` and `@boundscheck` properly, I faced some trouble with optional arguments (`f2`).
But I figured it was because `f2(x)` was calling `f2(x, a)` without the “inbound-dedness” so `f2(x)` boundschecks was not elided. This was fixed with `propagate_inbounds`.

But now when I also found out the hard way that a similar thing appears to happen with keyword arguments and is not easily fixed.

``````
f4(x; k=0) = @boundscheck "not elided!"
@propagate_inbounds f5(x; k=0) = @boundscheck "not elided!"

function caller(x)
@inbounds @show f4(x)
@inbounds @show f4(x, k=2)

@inbounds @show f5(x)
@inbounds @show f5(x, k=2)
nothing
end
caller(0)

# f4(x) = "not elided!"
# f4(x, k = 2) = "not elided!"
# f5(x) = nothing
# f5(x, k = 2) = "not elided!"
``````

Does anyone know of a possible cause for specifying keyword argument to prevent proper inlining or skipping boundchecks?
Or maybe there is a common workaround?
I’ve tried removing the default value of `2` and/or specifying type `k::Int64` but neither changes the behavior.

@code_typed does show very obviously the difference between v1.8.5 and v1.9
``````# V1.8.5
julia> @code_typed f5(1, k=2)
CodeInfo(
1 ─      nothing::Nothing
│        nothing::Nothing
│        nothing::Nothing
└──      goto #3 if not true
2 ─      goto #4
3 ─      goto #4
4 ┄ %7 = φ (#2 => "not ellided!!!", #3 => nothing)::Union{Nothing, String}
└──      return %7
) => Union{Nothing, String}

# v1.9
julia> @code_typed f5(1, k=2)
CodeInfo(
1 ─      nothing::Nothing
│        nothing::Nothing
│        nothing::Nothing
└──      goto #3 if not \$(Expr(:boundscheck))
2 ─      goto #4
3 ─      goto #4
4 ┄ %7 = φ (#2 => "not ellided!!!", #3 => nothing)::Union{Nothing, String}
└──      return %7
) => Union{Nothing, String}
``````

Keyword arguments get handled in an auto-generated keyword sorting function that translates a keyword-using function call to a purely positional function call.

``````julia> foo(;x) = stacktrace()
foo (generic function with 1 method)

julia> foo(;x=3)
15-element Vector{Base.StackTraces.StackFrame}:
#foo#12 at REPL[25]:1 [inlined]
(::var"#foo##kw")(::NamedTuple{(:x,), Tuple{Int64}}, ::typeof(foo)) at REPL[25]:1
top-level scope at REPL[26]:1
...
``````

In this example, `(::var"#foo##kw")` is the keyword sorter and `#foo#12` is the keyword-less internal function that it maps to.

I’m guessing that the keyword sorter was not propagating the `@inbounds`. Unfortunately, I have no idea how you might fix this locally. You might need to use `v1.9`, since you say it’s fixed there.

I see …
I’ve seen some mentions of that sorter function in the docs.

regarding the specific explanation tho: defining with @noinline shows foo as `foo(; x::Int64)`…which I think is just my normal method, right? not a keyword-less internal version.

``````julia> @noinline foo(;x) = stacktrace();

julia> foo(x=1)
15-element Vector{Base.StackTraces.StackFrame}:
foo(; x::Int64) at REPL[17]:1
(::var"#foo##kw")(::NamedTuple{(:x,), Tuple{Int64}}, ::typeof(foo)) at REPL[17]:1
top-level scope at REPL[18]:1
eval at boot.jl:373 [inlined]
...
``````

Unfortunately I won’t be able to use 1.9 for quite some time for IT bureaucratic reasons.

Your explanation makes sense though, I’ll have to live with it.
I guess that is one of the remaining reasons keyword args aren’t commonly used in numerical code.