Removing bounds checking for HPC - `--check-bounds=unsafe`?

Slightly tangential to the discussion, but I wanted to briefly mention Base.@propagate_inbounds in this context.

Combined with @inbounds I found this a very convenient way of declaring exactly what I can prove to be in-bounds without covering too much (as might be the case in a fully recursive @inbounds version), nor too little (just putting @inbounds around the top-level call). I agree with @jakobnissen that forcing code authors to chose a (slightly) more verbose path to improve correctness outweighs the convenience gain in some special use cases.

An example where I’ve used this quite happily are custom structs which support indexing or some kind of access to internal indexable objects. I cannot know if someone calls my own getindex method with a correct index, but if I generated the indices myself in another function and use them in the same place, I do know that they are correct (granted, the code becomes a bit verbose, but you can also write aliases to shorten the macros).

Something like this:

# Just shorten the name a bit
using Base: @propagate_inbounds
const var"@pi" = var"@propagate_inbounds"

# Can't use @inbounds here directly
# and don't want to put it around every single instance of `container[i]`
# Instead, `@propagate_inbounds` can cover more ground in the end
@pi getindex(container::MyContainer, i) = container.data[i]

# Slightly cumbersome: callers of my `getindex` method need all be annotated as well
# (if I understand this macro correctly)
@pi modify_containers(containers, indices) = ...

function do_the_thing()
    # I create the containers and know that the accesses will be in-bounds
    # This function is really the only place I can and want to put `@inbounds`
    containers = ...
    indices = ...
    @inbounds modify_containers!(containers, indices)
    ...
end

(I’m not so sure though how smart this approach is when it comes to very large functions and inlining?)

2 Likes