Skip to content

Commit

Permalink
Manually manage inlining/noinlining of _checkbounds/BoundsError
Browse files Browse the repository at this point in the history
this should help avoiding unnecessary tuple allocations, i.e.
delaying allocation until an error actually occurs
  • Loading branch information
carlobaldassi committed Jan 6, 2015
1 parent 14ea3b6 commit 359d5d3
Show file tree
Hide file tree
Showing 2 changed files with 11 additions and 6 deletions.
14 changes: 9 additions & 5 deletions base/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,17 @@ linearindexing{A<:Array}(::Type{A}) = LinearFast()
linearindexing{A<:Range}(::Type{A}) = LinearFast()

## Bounds checking ##
_checkbounds(sz::Int, i::Int, A::AbstractArray, J...) = 1 <= i <= sz || throw(BoundsError(A, J))
_checkbounds(sz::Int, i::Real, A::AbstractArray, J...) = _checkbounds(sz, to_index(i), A, J...)
_checkbounds(sz::Int, I::AbstractVector{Bool}, A::AbstractArray, J...) = length(I) == sz || throw(BoundsError(A, J))
_checkbounds(sz::Int, r::Range{Int}, A::AbstractArray, J...) = isempty(r) || (minimum(r) >= 1 && maximum(r) <= sz) || throw(BoundsError(A, J))
_checkbounds{T<:Real}(sz::Int, r::Range{T}, A::AbstractArray, J...) = _checkbounds(sz, to_index(r), A, J...)
# note: we force inlining of all _checkbounds bethods in order to force splatting
# of the arguments and avoid allocating tuples (unless a BoundsError is raised).
# we can't use @inline at this stage in the build process, so we manually add :meta expressions
_checkbounds(sz::Int, i::Int, A::AbstractArray, J...) = (Expr(:meta, :inline); 1 <= i <= sz || throw(BoundsError(A, J)))
_checkbounds(sz::Int, i::Real, A::AbstractArray, J...) = (Expr(:meta, :inline); _checkbounds(sz, to_index(i), A, J...))
_checkbounds(sz::Int, I::AbstractVector{Bool}, A::AbstractArray, J...) = (Expr(:meta, :inline); length(I) == sz || throw(BoundsError(A, J)))
_checkbounds(sz::Int, r::Range{Int}, A::AbstractArray, J...) = (Expr(:meta, :inline); isempty(r) || (minimum(r) >= 1 && maximum(r) <= sz) || throw(BoundsError(A, J)))
_checkbounds{T<:Real}(sz::Int, r::Range{T}, A::AbstractArray, J...) = (Expr(:meta, :inline); _checkbounds(sz, to_index(r), A, J...))

function _checkbounds{T <: Real}(sz::Int, I::AbstractArray{T}, A::AbstractArray, J...)
Expr(:meta, :inline)
for i in I
_checkbounds(sz, i, A, J...)
end
Expand Down
3 changes: 2 additions & 1 deletion base/base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ const Bottom = Union()

# constructors for Core types in boot.jl
call(T::Type{BoundsError}) = Core.call(T)
call(T::Type{BoundsError}, args...) = Core.call(T, args...)
# note: manual @noinline to avoid tuple allocations unless an error actually occurs
call(T::Type{BoundsError}, args...) = (Expr(:meta, :noinline); Core.call(T, args...))
call(T::Type{DivideError}) = Core.call(T)
call(T::Type{DomainError}) = Core.call(T)
call(T::Type{OverflowError}) = Core.call(T)
Expand Down

4 comments on commit 359d5d3

@vtjnash
Copy link
Member

@vtjnash vtjnash commented on 359d5d3 Jan 6, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this actually work? I would not expect this to generate the same AST as actually splicing in an Expr(:meta)

@JeffBezanson
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not expect this to work. This code creates a meta Expr at run time, when _checkbounds is called, and then throws it away.

In abstractarray.jl, @inline should work. Did it not?

We could add a @meta macro for writing meta exprs into ASTs. However the compiler might currently be too sensitive to the placement of meta exprs for this to work well.

@carlobaldassi
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this actually work?

No it doesn't :|
Sorry for the noise.
However I'm not sure how to fix it. This kind of thing works in the REPL:

# manual @noinline g(x) = x
eval(:(function g(x)
        $(Expr(:meta, :noinline))
        begin  # none, line 1:
            x
        end
    end))

But when I try to use it in base.jl it segfaults during make.

In abstractarray.jl, @inline should work. Did it not?

No it doesn't:

abstractarray.jl
error during bootstrap: LoadError(at "sysimg.jl" line 58: LoadError(at "abstractarray.jl" line 82: Base.MethodError(f=getindex, args=(Array{Any, 1}[Expr(:call, :_checkbounds, Expr(:::, :sz, :Int)::Any, Expr(:::, :i, :Int)::Any, Expr(:::, :A, :AbstractArray)::Any, Expr(:..., :J)::Any)::Any, Expr(:block, Expr(:line, 81, :abstractarray.jl)::Any, Expr(:||, Expr(:comparison, 1, :<=, :i, :<=, :sz)::Any, Expr(:call, :throw, Expr(:call, :BoundsError, :A, :J)::Any)::Any)::Any)::Any], 1))))
Makefile:142: recipe for target '/home/carlo/Programs/julia/usr/lib/julia/sys0.o' failed

@JeffBezanson
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that makes sense: the array operations used to implement @inline have not actually been defined yet.

Please sign in to comment.