Skip to content

Commit 99e292a

Browse files
committed
Merge pull request #14474 from JuliaLang/brj/boundscheck
elide code marked with `@boundscheck(...)`.
2 parents 2d03a92 + b74533a commit 99e292a

17 files changed

+340
-61
lines changed

base/abstractarray.jl

+30-30
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ checkbounds(::Type{Bool}, sz::Integer, i) = throw(ArgumentError("unable to check
131131
checkbounds(::Type{Bool}, sz::Integer, i::Real) = 1 <= i <= sz
132132
checkbounds(::Type{Bool}, sz::Integer, ::Colon) = true
133133
function checkbounds(::Type{Bool}, sz::Integer, r::Range)
134-
@_inline_meta
134+
@_propagate_inbounds_meta
135135
isempty(r) || (checkbounds(Bool, sz, minimum(r)) && checkbounds(Bool, sz, maximum(r)))
136136
end
137137
checkbounds(::Type{Bool}, sz::Integer, I::AbstractArray{Bool}) = length(I) == sz
@@ -350,8 +350,8 @@ function copy!{R,S}(B::AbstractVecOrMat{R}, ir_dest::Range{Int}, jr_dest::Range{
350350
throw(ArgumentError(string("source and destination must have same size (got ",
351351
length(jr_src)," and ",length(jr_dest),")")))
352352
end
353-
checkbounds(B, ir_dest, jr_dest)
354-
checkbounds(A, ir_src, jr_src)
353+
@boundscheck checkbounds(B, ir_dest, jr_dest)
354+
@boundscheck checkbounds(A, ir_src, jr_src)
355355
jdest = first(jr_dest)
356356
for jsrc in jr_src
357357
idest = first(ir_dest)
@@ -374,8 +374,8 @@ function copy_transpose!{R,S}(B::AbstractVecOrMat{R}, ir_dest::Range{Int}, jr_de
374374
throw(ArgumentError(string("source and destination must have same size (got ",
375375
length(ir_src)," and ",length(jr_dest),")")))
376376
end
377-
checkbounds(B, ir_dest, jr_dest)
378-
checkbounds(A, ir_src, jr_src)
377+
@boundscheck checkbounds(B, ir_dest, jr_dest)
378+
@boundscheck checkbounds(A, ir_src, jr_src)
379379
idest = first(ir_dest)
380380
for jsrc in jr_src
381381
jdest = first(jr_dest)
@@ -476,35 +476,35 @@ pointer{T}(x::AbstractArray{T}, i::Integer) = (@_inline_meta; unsafe_convert(Ptr
476476
# unsafe method.
477477

478478
function getindex(A::AbstractArray, I...)
479-
@_inline_meta
479+
@_propagate_inbounds_meta
480480
_getindex(linearindexing(A), A, I...)
481481
end
482482
function unsafe_getindex(A::AbstractArray, I...)
483-
@_inline_meta
483+
@_propagate_inbounds_meta
484484
_unsafe_getindex(linearindexing(A), A, I...)
485485
end
486486
## Internal defitions
487487
# 0-dimensional indexing is defined to prevent ambiguities. LinearFast is easy:
488-
_getindex(::LinearFast, A::AbstractArray) = (@_inline_meta; getindex(A, 1))
488+
_getindex(::LinearFast, A::AbstractArray) = (@_propagate_inbounds_meta; getindex(A, 1))
489489
# But LinearSlow must take into account the dimensionality of the array:
490490
_getindex{T}(::LinearSlow, A::AbstractArray{T,0}) = error("indexing not defined for ", typeof(A))
491-
_getindex(::LinearSlow, A::AbstractVector) = (@_inline_meta; getindex(A, 1))
492-
_getindex(l::LinearSlow, A::AbstractArray) = (@_inline_meta; _getindex(l, A, 1))
491+
_getindex(::LinearSlow, A::AbstractVector) = (@_propagate_inbounds_meta; getindex(A, 1))
492+
_getindex(l::LinearSlow, A::AbstractArray) = (@_propagate_inbounds_meta; _getindex(l, A, 1))
493493
_unsafe_getindex(::LinearFast, A::AbstractArray) = (@_inline_meta; unsafe_getindex(A, 1))
494494
_unsafe_getindex{T}(::LinearSlow, A::AbstractArray{T,0}) = error("indexing not defined for ", typeof(A))
495495
_unsafe_getindex(::LinearSlow, A::AbstractVector) = (@_inline_meta; unsafe_getindex(A, 1))
496496
_unsafe_getindex(l::LinearSlow, A::AbstractArray) = (@_inline_meta; _unsafe_getindex(l, A, 1))
497497

498498
_getindex(::LinearIndexing, A::AbstractArray, I...) = error("indexing $(typeof(A)) with types $(typeof(I)) is not supported")
499-
_unsafe_getindex(::LinearIndexing, A::AbstractArray, I...) = (@_inline_meta; getindex(A, I...))
499+
_unsafe_getindex(::LinearIndexing, A::AbstractArray, I...) = (@_propagate_inbounds_meta; getindex(A, I...))
500500

501501
## LinearFast Scalar indexing
502502
_getindex(::LinearFast, A::AbstractArray, I::Int) = error("indexing not defined for ", typeof(A))
503503
function _getindex(::LinearFast, A::AbstractArray, I::Real...)
504504
@_inline_meta
505505
# We must check bounds for sub2ind; so we can then call unsafe_getindex
506506
J = to_indexes(I...)
507-
checkbounds(A, J...)
507+
@boundscheck checkbounds(A, J...)
508508
unsafe_getindex(A, sub2ind(size(A), J...))
509509
end
510510
_unsafe_getindex(::LinearFast, A::AbstractArray, I::Real) = (@_inline_meta; getindex(A, I))
@@ -520,14 +520,14 @@ end
520520
if all(x->x===Int, I)
521521
:(error("indexing not defined for ", typeof(A)))
522522
else
523-
:($(Expr(:meta, :inline)); getindex(A, to_indexes(I...)...))
523+
:($(Expr(:meta, :inline, :propagate_inbounds)); getindex(A, to_indexes(I...)...))
524524
end
525525
elseif N > AN
526526
# Drop trailing ones
527527
Isplat = Expr[:(I[$d]) for d = 1:AN]
528528
Osplat = Expr[:(to_index(I[$d]) == 1) for d = AN+1:N]
529529
quote
530-
$(Expr(:meta, :inline))
530+
$(Expr(:meta, :inline, :propagate_inbounds))
531531
(&)($(Osplat...)) || throw_boundserror(A, I)
532532
getindex(A, $(Isplat...))
533533
end
@@ -542,7 +542,7 @@ end
542542
sz.args = Expr[:(size(A, $d)) for d=N:AN]
543543
szcheck = Expr[:(size(A, $d) > 0) for d=N:AN]
544544
quote
545-
$(Expr(:meta, :inline))
545+
$(Expr(:meta, :inline, :propagate_inbounds))
546546
# ind2sub requires all dimensions to be > 0:
547547
(&)($(szcheck...)) || throw_boundserror(A, I)
548548
s = ind2sub($sz, to_index(I[$N]))
@@ -553,12 +553,12 @@ end
553553
@generated function _unsafe_getindex{T,AN}(::LinearSlow, A::AbstractArray{T,AN}, I::Real...)
554554
N = length(I)
555555
if N == AN
556-
:($(Expr(:meta, :inline)); getindex(A, I...))
556+
:($(Expr(:meta, :inline, :propagate_inbounds)); getindex(A, I...))
557557
elseif N > AN
558558
# Drop trailing dimensions (unchecked)
559559
Isplat = Expr[:(I[$d]) for d = 1:AN]
560560
quote
561-
$(Expr(:meta, :inline))
561+
$(Expr(:meta, :inline, :propagate_inbounds))
562562
unsafe_getindex(A, $(Isplat...))
563563
end
564564
else
@@ -570,7 +570,7 @@ end
570570
sz = Expr(:tuple)
571571
sz.args = Expr[:(size(A, $d)) for d=N:AN]
572572
quote
573-
$(Expr(:meta, :inline))
573+
$(Expr(:meta, :inline, :propagate_inbounds))
574574
s = ind2sub($sz, to_index(I[$N]))
575575
unsafe_getindex(A, $(Isplat...))
576576
end
@@ -580,33 +580,33 @@ end
580580
## Setindex! is defined similarly. We first dispatch to an internal _setindex!
581581
# function that allows dispatch on array storage
582582
function setindex!(A::AbstractArray, v, I...)
583-
@_inline_meta
583+
@_propagate_inbounds_meta
584584
_setindex!(linearindexing(A), A, v, I...)
585585
end
586586
function unsafe_setindex!(A::AbstractArray, v, I...)
587-
@_inline_meta
587+
@_propagate_inbounds_meta
588588
_unsafe_setindex!(linearindexing(A), A, v, I...)
589589
end
590590
## Internal defitions
591-
_setindex!(::LinearFast, A::AbstractArray, v) = (@_inline_meta; setindex!(A, v, 1))
591+
_setindex!(::LinearFast, A::AbstractArray, v) = (@_propagate_inbounds_meta; setindex!(A, v, 1))
592592
_setindex!{T}(::LinearSlow, A::AbstractArray{T,0}, v) = error("indexing not defined for ", typeof(A))
593-
_setindex!(::LinearSlow, A::AbstractVector, v) = (@_inline_meta; setindex!(A, v, 1))
594-
_setindex!(l::LinearSlow, A::AbstractArray, v) = (@_inline_meta; _setindex!(l, A, v, 1))
593+
_setindex!(::LinearSlow, A::AbstractVector, v) = (@_propagate_inbounds_meta; setindex!(A, v, 1))
594+
_setindex!(l::LinearSlow, A::AbstractArray, v) = (@_propagate_inbounds_meta; _setindex!(l, A, v, 1))
595595
_unsafe_setindex!(::LinearFast, A::AbstractArray, v) = (@_inline_meta; unsafe_setindex!(A, v, 1))
596596
_unsafe_setindex!{T}(::LinearSlow, A::AbstractArray{T,0}, v) = error("indexing not defined for ", typeof(A))
597597
_unsafe_setindex!(::LinearSlow, A::AbstractVector, v) = (@_inline_meta; unsafe_setindex!(A, v, 1))
598598
_unsafe_setindex!(l::LinearSlow, A::AbstractArray, v) = (@_inline_meta; _unsafe_setindex!(l, A, v, 1))
599599

600600
_setindex!(::LinearIndexing, A::AbstractArray, v, I...) = error("indexing $(typeof(A)) with types $(typeof(I)) is not supported")
601-
_unsafe_setindex!(::LinearIndexing, A::AbstractArray, v, I...) = (@_inline_meta; setindex!(A, v, I...))
601+
_unsafe_setindex!(::LinearIndexing, A::AbstractArray, v, I...) = (@_propagate_inbounds_meta; setindex!(A, v, I...))
602602

603603
## LinearFast Scalar indexing
604604
_setindex!(::LinearFast, A::AbstractArray, v, I::Int) = error("indexed assignment not defined for ", typeof(A))
605605
function _setindex!(::LinearFast, A::AbstractArray, v, I::Real...)
606606
@_inline_meta
607607
# We must check bounds for sub2ind; so we can then call unsafe_setindex!
608608
J = to_indexes(I...)
609-
checkbounds(A, J...)
609+
@boundscheck checkbounds(A, J...)
610610
unsafe_setindex!(A, v, sub2ind(size(A), J...))
611611
end
612612
_unsafe_setindex!(::LinearFast, A::AbstractArray, v, I::Real) = (@_inline_meta; setindex!(A, v, I))
@@ -622,14 +622,14 @@ end
622622
if all(x->x===Int, I)
623623
:(error("indexing not defined for ", typeof(A)))
624624
else
625-
:($(Expr(:meta, :inline)); setindex!(A, v, to_indexes(I...)...))
625+
:($(Expr(:meta, :inline, :propagate_inbounds)); setindex!(A, v, to_indexes(I...)...))
626626
end
627627
elseif N > AN
628628
# Drop trailing ones
629629
Isplat = Expr[:(I[$d]) for d = 1:AN]
630630
Osplat = Expr[:(to_index(I[$d]) == 1) for d = AN+1:N]
631631
quote
632-
$(Expr(:meta, :inline))
632+
$(Expr(:meta, :inline, :propagate_inbounds))
633633
(&)($(Osplat...)) || throw_boundserror(A, I)
634634
setindex!(A, v, $(Isplat...))
635635
end
@@ -644,7 +644,7 @@ end
644644
sz.args = Expr[:(size(A, $d)) for d=N:AN]
645645
szcheck = Expr[:(size(A, $d) > 0) for d=N:AN]
646646
quote
647-
$(Expr(:meta, :inline))
647+
$(Expr(:meta, :inline, :propagate_inbounds))
648648
# ind2sub requires all dimensions to be > 0:
649649
(&)($(szcheck...)) || throw_boundserror(A, I)
650650
s = ind2sub($sz, to_index(I[$N]))
@@ -660,7 +660,7 @@ end
660660
# Drop trailing dimensions (unchecked)
661661
Isplat = Expr[:(I[$d]) for d = 1:AN]
662662
quote
663-
$(Expr(:meta, :inline))
663+
$(Expr(:meta, :inline, :propagate_inbounds))
664664
unsafe_setindex!(A, v, $(Isplat...))
665665
end
666666
else
@@ -672,7 +672,7 @@ end
672672
sz = Expr(:tuple)
673673
sz.args = Expr[:(size(A, $d)) for d=N:AN]
674674
quote
675-
$(Expr(:meta, :inline))
675+
$(Expr(:meta, :inline, :propagate_inbounds))
676676
s = ind2sub($sz, to_index(I[$N]))
677677
unsafe_setindex!(A, v, $(Isplat...))
678678
end

base/deprecated.jl

+8
Original file line numberDiff line numberDiff line change
@@ -952,3 +952,11 @@ end
952952
#14555
953953
@deprecate_binding Coff_t Int64
954954
@deprecate_binding FileOffset Int64
955+
956+
#14474
957+
macro boundscheck(yesno,blk)
958+
depwarn("The meaning of `@boundscheck` has changed. It now indicates that the provided code block performs bounds checking, and may be elided when inbounds.", symbol("@boundscheck"))
959+
if yesno === true
960+
:(@inbounds $(esc(blk)))
961+
end
962+
end

base/essentials.jl

+9-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ end
1717
macro _pure_meta()
1818
Expr(:meta, :pure)
1919
end
20+
# another version of inlining that propagates an inbounds context
21+
macro _propagate_inbounds_meta()
22+
Expr(:meta, :inline, :propagate_inbounds)
23+
end
2024

2125

2226
# constructors for Core types in boot.jl
@@ -168,15 +172,17 @@ end
168172

169173
esc(e::ANY) = Expr(:escape, e)
170174

171-
macro boundscheck(yesno,blk)
175+
macro boundscheck(blk)
172176
# hack: use this syntax since it avoids introducing line numbers
173-
:($(Expr(:boundscheck,yesno));
177+
:($(Expr(:boundscheck,true));
174178
$(esc(blk));
175179
$(Expr(:boundscheck,:pop)))
176180
end
177181

178182
macro inbounds(blk)
179-
:(@boundscheck false $(esc(blk)))
183+
:($(Expr(:inbounds,true));
184+
$(esc(blk));
185+
$(Expr(:inbounds,:pop)))
180186
end
181187

182188
macro label(name::Symbol)

base/expr.jl

+15
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,21 @@ macro pure(ex)
6666
esc(isa(ex, Expr) ? pushmeta!(ex, :pure) : ex)
6767
end
6868

69+
"""
70+
@propagate_inbounds(ex)
71+
72+
Tells the compiler to inline a function while retaining the caller's inbounds context.
73+
"""
74+
macro propagate_inbounds(ex)
75+
if isa(ex, Expr)
76+
pushmeta!(ex, :inline)
77+
pushmeta!(ex, :propagate_inbounds)
78+
esc(ex)
79+
else
80+
esc(ex)
81+
end
82+
end
83+
6984
## some macro utilities ##
7085

7186
find_vars(e) = find_vars(e, [])

base/inference.jl

+19-1
Original file line numberDiff line numberDiff line change
@@ -1861,6 +1861,7 @@ function typeinf_uncached(linfo::LambdaStaticData, atypes::ANY, sparams::SimpleV
18611861
fulltree.args[3] = inlining_pass(fulltree.args[3], sv, fulltree)[1]
18621862
# inlining can add variables
18631863
sv.vars = append_any(f_argnames(fulltree), fulltree.args[2][1])
1864+
inbounds_meta_elim_pass(fulltree.args[3])
18641865
end
18651866
tuple_elim_pass(fulltree, sv)
18661867
getfield_elim_pass(fulltree.args[3], sv)
@@ -2414,6 +2415,7 @@ function inlineable(f::ANY, e::Expr, atype::ANY, sv::StaticVarInfo, enclosing_as
24142415

24152416
body = Expr(:block)
24162417
body.args = ast.args[3].args::Array{Any,1}
2418+
propagate_inbounds, _ = popmeta!(body, :propagate_inbounds)
24172419
cost::Int = 1000
24182420
if incompletematch
24192421
cost *= 4
@@ -2773,6 +2775,12 @@ function inlineable(f::ANY, e::Expr, atype::ANY, sv::StaticVarInfo, enclosing_as
27732775
end
27742776
end
27752777

2778+
if !isempty(stmts) && !propagate_inbounds
2779+
# inlined statements are out-of-bounds by default
2780+
unshift!(stmts, Expr(:inbounds, false))
2781+
push!(stmts, Expr(:inbounds, :pop))
2782+
end
2783+
27762784
if isa(expr,Expr)
27772785
old_t = e.typ
27782786
if old_t <: expr.typ
@@ -2985,7 +2993,7 @@ function inlining_pass(e::Expr, sv, ast)
29852993
end
29862994
res = inlineable(f, e, atype, sv, ast)
29872995
if isa(res,Tuple)
2988-
if isa(res[2],Array)
2996+
if isa(res[2],Array) && !isempty(res[2])
29892997
append!(stmts,res[2])
29902998
end
29912999
res = res[1]
@@ -3265,6 +3273,16 @@ function occurs_outside_getfield(e::ANY, sym::ANY, sv::StaticVarInfo, tuplen::In
32653273
return false
32663274
end
32673275

3276+
# removes inbounds metadata if we never encounter an inbounds=true or
3277+
# boundscheck context in the method body
3278+
function inbounds_meta_elim_pass(e::Expr)
3279+
if findfirst(x -> isa(x, Expr) &&
3280+
((x.head === :inbounds && x.args[1] == true) || x.head === :boundscheck),
3281+
e.args) == 0
3282+
filter!(x -> !(isa(x, Expr) && x.head === :inbounds), e.args)
3283+
end
3284+
end
3285+
32683286
# replace getfield(tuple(exprs...), i) with exprs[i]
32693287
function getfield_elim_pass(e::Expr, sv)
32703288
for i = 1:length(e.args)

doc/devdocs/ast.rst

+6-1
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,17 @@ These symbols appear in the ``head`` field of ``Expr``\s in lowered form.
139139
``leave``
140140
pop exception handlers. ``args[1]`` is the number of handlers to pop.
141141

142-
``boundscheck``
142+
``inbounds``
143143
controls turning bounds checks on or off. A stack is maintained; if the
144144
first argument of this expression is true or false (``true`` means bounds
145145
checks are enabled), it is pushed onto the stack. If the first argument is
146146
``:pop``, the stack is popped.
147147

148+
``boundscheck``
149+
indicates the beginning or end of a section of code that performs a bounds
150+
check. Like ``inbounds``, a stack is maintained, and the second argument
151+
can be one of: ``true``, ``false``, or ``:pop``.
152+
148153
``copyast``
149154
part of the implementation of quasi-quote. The argument is a surface syntax
150155
AST that is simply copied recursively and returned at run time.

0 commit comments

Comments
 (0)