diff --git a/base/essentials.jl b/base/essentials.jl index 32c44a9571f23..a169d224bc59b 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -846,13 +846,14 @@ macro boundscheck(blk) end """ - @inbounds(blk) + @inbounds block -Eliminates array bounds checking within expressions. - -In the example below the in-range check for referencing -element `i` of array `A` is skipped to improve performance. +Eliminates bounds checking within the block. +This macro can be used to improve performance by informing the compiler that accesses to +array elements or object fields are assuredly within bounds. +In the example below the in-range check for referencing element `i` of array `A` is skipped +to improve performance. ```julia function sum(A::AbstractArray) r = zero(eltype(A)) @@ -864,7 +865,6 @@ end ``` !!! warning - Using `@inbounds` may return incorrect results/crashes/corruption for out-of-bounds indices. The user is responsible for checking it manually. Only use `@inbounds` when you are certain that all accesses are in bounds (as @@ -872,6 +872,37 @@ end example, using `1:length(A)` instead of `eachindex(A)` in a function like the one above is _not_ safely inbounds because the first index of `A` may not be `1` for all user defined types that subtype `AbstractArray`. + +!!! note + `@inbounds` eliminates bounds checks in methods called within a given block, but not + those that are syntactically within the given block. + However, keep in mind that the `@inbounds` context propagates only one function call + layer deep. For example, if an `@inbounds` block includes a call to `f()`, which in turn + calls `g()`, bounds checks that are syntactically within `f()` will be eliminated, + while ones within `g()` will not. + If you want to eliminates bounds checks within `g()` also, + you need to annotate [`Base.@propagate_inbounds`](@ref) on `f()`. + +# Example + +```julia-repl +julia> code_typed((Vector{Any},Int)) do a, i + a[i] + end |> only +CodeInfo( +1 ─ %1 = Base.arrayref(true, a, i)::Any +└── return %1 +) => Any + +julia> code_typed((Vector{Any},Int)) do a, i + @inbounds a[i] # The bounds check for `arrayref` is turned off. + end |> only +CodeInfo( +1 ─ %1 = Base.arrayref(false, a, i)::Any +└── return %1 +) => Any +``` + """ macro inbounds(blk) return Expr(:block, diff --git a/base/expr.jl b/base/expr.jl index c4f64b89de8b6..1cd3bd6e8c61e 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -876,9 +876,43 @@ macro nospecializeinfer(ex) end """ - @propagate_inbounds + Base.@propagate_inbounds function f(args...) + ... + end + Base.@propagate_inbounds f(args...) = ... + +Tells the compiler to inline `f` while retaining the caller's `@inbounds` context. +This macro can be used to pass along the `@inbounds` context within the caller of `f` to +callee methods that are invoked within `f`. Without the `@propagate_inbounds` annotation, +the caller's `@inbounds` context propagates only one function call layer deep. -Tells the compiler to inline a function while retaining the caller's inbounds context. +# Example + +```julia-repl +julia> call_func(func, args...) = func(args...); + +julia> code_typed((Vector{Any},Int)) do a, i + # This `@inbounds` context does not propagate to `getindex`, + # and thus the bounds check for `arrayref` is not turned off. + @inbounds call_func(getindex, a, i) + end |> only +CodeInfo( +1 ─ %1 = Base.arrayref(true, a, i)::Any +└── return %1 +) => Any + +julia> Base.@propagate_inbounds call_func(func, args...) = func(args...); + +julia> code_typed((Vector{Any},Int)) do a, i + # Now this `@inbounds` context propagates to `getindex`, + # and the bounds check for `arrayref` is turned off. + @inbounds call_func(getindex, a, i) + end |> only +CodeInfo( +1 ─ %1 = Base.arrayref(false, a, i)::Any +└── return %1 +) => Any +``` """ macro propagate_inbounds(ex) if isa(ex, Expr)