From b0718720316753578a6d4a98b0934ef43939eac9 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Wed, 14 Jun 2023 03:13:57 +0900 Subject: [PATCH 1/4] improve docs for `@inbounds` and `Base.@propagate_inbounds` Despite the widespread use of these macros, there's a lack of detailed explanation about their behaviors. In particular, it's not very clear how the latter macro can be employed. This commit aims to improve their documentation, supplementing it with examples. --- base/essentials.jl | 46 ++++++++++++++++++++++++++++++++++++++-------- base/expr.jl | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 74 insertions(+), 10 deletions(-) diff --git a/base/essentials.jl b/base/essentials.jl index 0477ccd5b172a..a3c3fb39a611b 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -682,13 +682,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 elemements 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)) @@ -700,14 +701,43 @@ 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 it is certain from the information locally available - that all accesses are in bounds. In particular, using `1:length(A)` instead of - `eachindex(A)` in a function like the one above is _not_ safely inbounds because + that all accesses are in-bounds. In particular, using `1:length(A)` instead of + `eachindex(A)` in a function like the one above is _not_ safely in-bounds because the first index of `A` may not be `1` for all user defined types that subtype `AbstractArray`. + +!!! note + `@inbounds` eleminates bounds checks that are syntactically within the given block, + as well as ones in methods that are called within the 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 e007306063db1..ef2dbd9fa4fe3 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -801,9 +801,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. + +# 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...); -Tells the compiler to inline a function while retaining the caller's inbounds context. +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) From fdb2f910ce41ad6c250e13a0cef4a5ac107decab Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" Date: Tue, 9 Jul 2024 15:54:26 +0200 Subject: [PATCH 2/4] Update base/essentials.jl Co-authored-by: Jameson Nash --- base/essentials.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/essentials.jl b/base/essentials.jl index a3c3fb39a611b..75210dc604df2 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -710,8 +710,8 @@ end `AbstractArray`. !!! note - `@inbounds` eleminates bounds checks that are syntactically within the given block, - as well as ones in methods that are called within the block. + `@inbounds` eleminates 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, From 2fc8bbedf2f784590cf59245c916484df0de9fd0 Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" Date: Tue, 10 Sep 2024 00:41:36 -0400 Subject: [PATCH 3/4] Update base/essentials.jl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mosè Giordano <765740+giordano@users.noreply.github.com> --- base/essentials.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/essentials.jl b/base/essentials.jl index 67370769b90ba..442f14b525cd5 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -874,7 +874,7 @@ end be `1` for all user defined types that subtype `AbstractArray`. !!! note - `@inbounds` eleminates bounds checks in methods called within a given block, but not + `@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 From 8f7ba3b2fa7691f2c84675ec700aa03eef9a6621 Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" Date: Tue, 10 Sep 2024 00:41:45 -0400 Subject: [PATCH 4/4] Update base/essentials.jl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mosè Giordano <765740+giordano@users.noreply.github.com> --- base/essentials.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/essentials.jl b/base/essentials.jl index 442f14b525cd5..a169d224bc59b 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -850,7 +850,7 @@ end Eliminates bounds checking within the block. This macro can be used to improve performance by informing the compiler that accesses to -array elemements or object fields are assuredly within bounds. +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.