diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 286d56279a2ed..f76c4744529ef 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -131,7 +131,7 @@ checkbounds(::Type{Bool}, sz::Integer, i) = throw(ArgumentError("unable to check checkbounds(::Type{Bool}, sz::Integer, i::Real) = 1 <= i <= sz checkbounds(::Type{Bool}, sz::Integer, ::Colon) = true function checkbounds(::Type{Bool}, sz::Integer, r::Range) - @_inline_meta + @_propagate_inbounds_meta isempty(r) || (checkbounds(Bool, sz, minimum(r)) && checkbounds(Bool, sz, maximum(r))) end 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{ throw(ArgumentError(string("source and destination must have same size (got ", length(jr_src)," and ",length(jr_dest),")"))) end - checkbounds(B, ir_dest, jr_dest) - checkbounds(A, ir_src, jr_src) + @boundscheck checkbounds(B, ir_dest, jr_dest) + @boundscheck checkbounds(A, ir_src, jr_src) jdest = first(jr_dest) for jsrc in jr_src idest = first(ir_dest) @@ -374,8 +374,8 @@ function copy_transpose!{R,S}(B::AbstractVecOrMat{R}, ir_dest::Range{Int}, jr_de throw(ArgumentError(string("source and destination must have same size (got ", length(ir_src)," and ",length(jr_dest),")"))) end - checkbounds(B, ir_dest, jr_dest) - checkbounds(A, ir_src, jr_src) + @boundscheck checkbounds(B, ir_dest, jr_dest) + @boundscheck checkbounds(A, ir_src, jr_src) idest = first(ir_dest) for jsrc in jr_src jdest = first(jr_dest) @@ -476,27 +476,27 @@ pointer{T}(x::AbstractArray{T}, i::Integer) = (@_inline_meta; unsafe_convert(Ptr # unsafe method. function getindex(A::AbstractArray, I...) - @_inline_meta + @_propagate_inbounds_meta _getindex(linearindexing(A), A, I...) end function unsafe_getindex(A::AbstractArray, I...) - @_inline_meta + @_propagate_inbounds_meta _unsafe_getindex(linearindexing(A), A, I...) end ## Internal defitions # 0-dimensional indexing is defined to prevent ambiguities. LinearFast is easy: -_getindex(::LinearFast, A::AbstractArray) = (@_inline_meta; getindex(A, 1)) +_getindex(::LinearFast, A::AbstractArray) = (@_propagate_inbounds_meta; getindex(A, 1)) # But LinearSlow must take into account the dimensionality of the array: _getindex{T}(::LinearSlow, A::AbstractArray{T,0}) = error("indexing not defined for ", typeof(A)) -_getindex(::LinearSlow, A::AbstractVector) = (@_inline_meta; getindex(A, 1)) -_getindex(l::LinearSlow, A::AbstractArray) = (@_inline_meta; _getindex(l, A, 1)) +_getindex(::LinearSlow, A::AbstractVector) = (@_propagate_inbounds_meta; getindex(A, 1)) +_getindex(l::LinearSlow, A::AbstractArray) = (@_propagate_inbounds_meta; _getindex(l, A, 1)) _unsafe_getindex(::LinearFast, A::AbstractArray) = (@_inline_meta; unsafe_getindex(A, 1)) _unsafe_getindex{T}(::LinearSlow, A::AbstractArray{T,0}) = error("indexing not defined for ", typeof(A)) _unsafe_getindex(::LinearSlow, A::AbstractVector) = (@_inline_meta; unsafe_getindex(A, 1)) _unsafe_getindex(l::LinearSlow, A::AbstractArray) = (@_inline_meta; _unsafe_getindex(l, A, 1)) _getindex(::LinearIndexing, A::AbstractArray, I...) = error("indexing $(typeof(A)) with types $(typeof(I)) is not supported") -_unsafe_getindex(::LinearIndexing, A::AbstractArray, I...) = (@_inline_meta; getindex(A, I...)) +_unsafe_getindex(::LinearIndexing, A::AbstractArray, I...) = (@_propagate_inbounds_meta; getindex(A, I...)) ## LinearFast Scalar indexing _getindex(::LinearFast, A::AbstractArray, I::Int) = error("indexing not defined for ", typeof(A)) @@ -504,7 +504,7 @@ function _getindex(::LinearFast, A::AbstractArray, I::Real...) @_inline_meta # We must check bounds for sub2ind; so we can then call unsafe_getindex J = to_indexes(I...) - checkbounds(A, J...) + @boundscheck checkbounds(A, J...) unsafe_getindex(A, sub2ind(size(A), J...)) end _unsafe_getindex(::LinearFast, A::AbstractArray, I::Real) = (@_inline_meta; getindex(A, I)) @@ -520,14 +520,14 @@ end if all(x->x===Int, I) :(error("indexing not defined for ", typeof(A))) else - :($(Expr(:meta, :inline)); getindex(A, to_indexes(I...)...)) + :($(Expr(:meta, :inline, :propagate_inbounds)); getindex(A, to_indexes(I...)...)) end elseif N > AN # Drop trailing ones Isplat = Expr[:(I[$d]) for d = 1:AN] Osplat = Expr[:(to_index(I[$d]) == 1) for d = AN+1:N] quote - $(Expr(:meta, :inline)) + $(Expr(:meta, :inline, :propagate_inbounds)) (&)($(Osplat...)) || throw_boundserror(A, I) getindex(A, $(Isplat...)) end @@ -542,7 +542,7 @@ end sz.args = Expr[:(size(A, $d)) for d=N:AN] szcheck = Expr[:(size(A, $d) > 0) for d=N:AN] quote - $(Expr(:meta, :inline)) + $(Expr(:meta, :inline, :propagate_inbounds)) # ind2sub requires all dimensions to be > 0: (&)($(szcheck...)) || throw_boundserror(A, I) s = ind2sub($sz, to_index(I[$N])) @@ -553,12 +553,12 @@ end @generated function _unsafe_getindex{T,AN}(::LinearSlow, A::AbstractArray{T,AN}, I::Real...) N = length(I) if N == AN - :($(Expr(:meta, :inline)); getindex(A, I...)) + :($(Expr(:meta, :inline, :propagate_inbounds)); getindex(A, I...)) elseif N > AN # Drop trailing dimensions (unchecked) Isplat = Expr[:(I[$d]) for d = 1:AN] quote - $(Expr(:meta, :inline)) + $(Expr(:meta, :inline, :propagate_inbounds)) unsafe_getindex(A, $(Isplat...)) end else @@ -570,7 +570,7 @@ end sz = Expr(:tuple) sz.args = Expr[:(size(A, $d)) for d=N:AN] quote - $(Expr(:meta, :inline)) + $(Expr(:meta, :inline, :propagate_inbounds)) s = ind2sub($sz, to_index(I[$N])) unsafe_getindex(A, $(Isplat...)) end @@ -580,25 +580,25 @@ end ## Setindex! is defined similarly. We first dispatch to an internal _setindex! # function that allows dispatch on array storage function setindex!(A::AbstractArray, v, I...) - @_inline_meta + @_propagate_inbounds_meta _setindex!(linearindexing(A), A, v, I...) end function unsafe_setindex!(A::AbstractArray, v, I...) - @_inline_meta + @_propagate_inbounds_meta _unsafe_setindex!(linearindexing(A), A, v, I...) end ## Internal defitions -_setindex!(::LinearFast, A::AbstractArray, v) = (@_inline_meta; setindex!(A, v, 1)) +_setindex!(::LinearFast, A::AbstractArray, v) = (@_propagate_inbounds_meta; setindex!(A, v, 1)) _setindex!{T}(::LinearSlow, A::AbstractArray{T,0}, v) = error("indexing not defined for ", typeof(A)) -_setindex!(::LinearSlow, A::AbstractVector, v) = (@_inline_meta; setindex!(A, v, 1)) -_setindex!(l::LinearSlow, A::AbstractArray, v) = (@_inline_meta; _setindex!(l, A, v, 1)) +_setindex!(::LinearSlow, A::AbstractVector, v) = (@_propagate_inbounds_meta; setindex!(A, v, 1)) +_setindex!(l::LinearSlow, A::AbstractArray, v) = (@_propagate_inbounds_meta; _setindex!(l, A, v, 1)) _unsafe_setindex!(::LinearFast, A::AbstractArray, v) = (@_inline_meta; unsafe_setindex!(A, v, 1)) _unsafe_setindex!{T}(::LinearSlow, A::AbstractArray{T,0}, v) = error("indexing not defined for ", typeof(A)) _unsafe_setindex!(::LinearSlow, A::AbstractVector, v) = (@_inline_meta; unsafe_setindex!(A, v, 1)) _unsafe_setindex!(l::LinearSlow, A::AbstractArray, v) = (@_inline_meta; _unsafe_setindex!(l, A, v, 1)) _setindex!(::LinearIndexing, A::AbstractArray, v, I...) = error("indexing $(typeof(A)) with types $(typeof(I)) is not supported") -_unsafe_setindex!(::LinearIndexing, A::AbstractArray, v, I...) = (@_inline_meta; setindex!(A, v, I...)) +_unsafe_setindex!(::LinearIndexing, A::AbstractArray, v, I...) = (@_propagate_inbounds_meta; setindex!(A, v, I...)) ## LinearFast Scalar indexing _setindex!(::LinearFast, A::AbstractArray, v, I::Int) = error("indexed assignment not defined for ", typeof(A)) @@ -606,7 +606,7 @@ function _setindex!(::LinearFast, A::AbstractArray, v, I::Real...) @_inline_meta # We must check bounds for sub2ind; so we can then call unsafe_setindex! J = to_indexes(I...) - checkbounds(A, J...) + @boundscheck checkbounds(A, J...) unsafe_setindex!(A, v, sub2ind(size(A), J...)) end _unsafe_setindex!(::LinearFast, A::AbstractArray, v, I::Real) = (@_inline_meta; setindex!(A, v, I)) @@ -622,14 +622,14 @@ end if all(x->x===Int, I) :(error("indexing not defined for ", typeof(A))) else - :($(Expr(:meta, :inline)); setindex!(A, v, to_indexes(I...)...)) + :($(Expr(:meta, :inline, :propagate_inbounds)); setindex!(A, v, to_indexes(I...)...)) end elseif N > AN # Drop trailing ones Isplat = Expr[:(I[$d]) for d = 1:AN] Osplat = Expr[:(to_index(I[$d]) == 1) for d = AN+1:N] quote - $(Expr(:meta, :inline)) + $(Expr(:meta, :inline, :propagate_inbounds)) (&)($(Osplat...)) || throw_boundserror(A, I) setindex!(A, v, $(Isplat...)) end @@ -644,7 +644,7 @@ end sz.args = Expr[:(size(A, $d)) for d=N:AN] szcheck = Expr[:(size(A, $d) > 0) for d=N:AN] quote - $(Expr(:meta, :inline)) + $(Expr(:meta, :inline, :propagate_inbounds)) # ind2sub requires all dimensions to be > 0: (&)($(szcheck...)) || throw_boundserror(A, I) s = ind2sub($sz, to_index(I[$N])) @@ -660,7 +660,7 @@ end # Drop trailing dimensions (unchecked) Isplat = Expr[:(I[$d]) for d = 1:AN] quote - $(Expr(:meta, :inline)) + $(Expr(:meta, :inline, :propagate_inbounds)) unsafe_setindex!(A, v, $(Isplat...)) end else @@ -672,7 +672,7 @@ end sz = Expr(:tuple) sz.args = Expr[:(size(A, $d)) for d=N:AN] quote - $(Expr(:meta, :inline)) + $(Expr(:meta, :inline, :propagate_inbounds)) s = ind2sub($sz, to_index(I[$N])) unsafe_setindex!(A, v, $(Isplat...)) end diff --git a/base/deprecated.jl b/base/deprecated.jl index e510542a6ed63..40176d4ce5a2e 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -952,3 +952,11 @@ end #14555 @deprecate_binding Coff_t Int64 @deprecate_binding FileOffset Int64 + +#14474 +macro boundscheck(yesno,blk) + 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")) + if yesno === true + :(@inbounds $(esc(blk))) + end +end diff --git a/base/essentials.jl b/base/essentials.jl index a43d5cf13e225..be0ee7655ca94 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -17,6 +17,10 @@ end macro _pure_meta() Expr(:meta, :pure) end +# another version of inlining that propagates an inbounds context +macro _propagate_inbounds_meta() + Expr(:meta, :inline, :propagate_inbounds) +end # constructors for Core types in boot.jl @@ -168,15 +172,17 @@ end esc(e::ANY) = Expr(:escape, e) -macro boundscheck(yesno,blk) +macro boundscheck(blk) # hack: use this syntax since it avoids introducing line numbers - :($(Expr(:boundscheck,yesno)); + :($(Expr(:boundscheck,true)); $(esc(blk)); $(Expr(:boundscheck,:pop))) end macro inbounds(blk) - :(@boundscheck false $(esc(blk))) + :($(Expr(:inbounds,true)); + $(esc(blk)); + $(Expr(:inbounds,:pop))) end macro label(name::Symbol) diff --git a/base/expr.jl b/base/expr.jl index 84c0346c636d6..7ace6c47a152d 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -66,6 +66,21 @@ macro pure(ex) esc(isa(ex, Expr) ? pushmeta!(ex, :pure) : ex) end +""" + @propagate_inbounds(ex) + +Tells the compiler to inline a function while retaining the caller's inbounds context. +""" +macro propagate_inbounds(ex) + if isa(ex, Expr) + pushmeta!(ex, :inline) + pushmeta!(ex, :propagate_inbounds) + esc(ex) + else + esc(ex) + end +end + ## some macro utilities ## find_vars(e) = find_vars(e, []) diff --git a/base/inference.jl b/base/inference.jl index f3c0e0e39ede4..b8ed3dcc05239 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -1861,6 +1861,7 @@ function typeinf_uncached(linfo::LambdaStaticData, atypes::ANY, sparams::SimpleV fulltree.args[3] = inlining_pass(fulltree.args[3], sv, fulltree)[1] # inlining can add variables sv.vars = append_any(f_argnames(fulltree), fulltree.args[2][1]) + inbounds_meta_elim_pass(fulltree.args[3]) end tuple_elim_pass(fulltree, sv) getfield_elim_pass(fulltree.args[3], sv) @@ -2414,6 +2415,7 @@ function inlineable(f::ANY, e::Expr, atype::ANY, sv::StaticVarInfo, enclosing_as body = Expr(:block) body.args = ast.args[3].args::Array{Any,1} + propagate_inbounds, _ = popmeta!(body, :propagate_inbounds) cost::Int = 1000 if incompletematch cost *= 4 @@ -2773,6 +2775,12 @@ function inlineable(f::ANY, e::Expr, atype::ANY, sv::StaticVarInfo, enclosing_as end end + if !isempty(stmts) && !propagate_inbounds + # inlined statements are out-of-bounds by default + unshift!(stmts, Expr(:inbounds, false)) + push!(stmts, Expr(:inbounds, :pop)) + end + if isa(expr,Expr) old_t = e.typ if old_t <: expr.typ @@ -2985,7 +2993,7 @@ function inlining_pass(e::Expr, sv, ast) end res = inlineable(f, e, atype, sv, ast) if isa(res,Tuple) - if isa(res[2],Array) + if isa(res[2],Array) && !isempty(res[2]) append!(stmts,res[2]) end res = res[1] @@ -3265,6 +3273,16 @@ function occurs_outside_getfield(e::ANY, sym::ANY, sv::StaticVarInfo, tuplen::In return false end +# removes inbounds metadata if we never encounter an inbounds=true or +# boundscheck context in the method body +function inbounds_meta_elim_pass(e::Expr) + if findfirst(x -> isa(x, Expr) && + ((x.head === :inbounds && x.args[1] == true) || x.head === :boundscheck), + e.args) == 0 + filter!(x -> !(isa(x, Expr) && x.head === :inbounds), e.args) + end +end + # replace getfield(tuple(exprs...), i) with exprs[i] function getfield_elim_pass(e::Expr, sv) for i = 1:length(e.args) diff --git a/doc/devdocs/ast.rst b/doc/devdocs/ast.rst index 7d70c973c6f3a..1a4d3c843615b 100644 --- a/doc/devdocs/ast.rst +++ b/doc/devdocs/ast.rst @@ -139,12 +139,17 @@ These symbols appear in the ``head`` field of ``Expr``\s in lowered form. ``leave`` pop exception handlers. ``args[1]`` is the number of handlers to pop. -``boundscheck`` +``inbounds`` controls turning bounds checks on or off. A stack is maintained; if the first argument of this expression is true or false (``true`` means bounds checks are enabled), it is pushed onto the stack. If the first argument is ``:pop``, the stack is popped. +``boundscheck`` + indicates the beginning or end of a section of code that performs a bounds + check. Like ``inbounds``, a stack is maintained, and the second argument + can be one of: ``true``, ``false``, or ``:pop``. + ``copyast`` part of the implementation of quasi-quote. The argument is a surface syntax AST that is simply copied recursively and returned at run time. diff --git a/doc/devdocs/boundscheck.rst b/doc/devdocs/boundscheck.rst new file mode 100644 index 0000000000000..3f08d59981146 --- /dev/null +++ b/doc/devdocs/boundscheck.rst @@ -0,0 +1,61 @@ +.. _devdocs-subarrays: + +.. currentmodule:: Base + +************************** +Bounds checking +************************** + +Like many modern programming languages, Julia uses bounds checking to ensure +program safety when accessing arrays. In tight inner loops or other performance +critical situations, you may wish to skip these bounds checks to improve runtime +performance. For instance, in order to emit vectorized (SIMD) instructions, your +loop body cannot contain branches, and thus cannot contain bounds checks. +Consequently, Julia includes an ``@inbounds(...)`` macro to tell the compiler to +skip such bounds checks within the given block. For the built-in ``Array`` type, +the magic happens inside the ``arrayref`` and ``arrayset`` intrinsics. +User-defined array types instead use the ``@boundscheck(...)`` macro to achieve +context-sensitive code selection. + +Eliding bounds checks +--------------------- + +The ``@boundscheck(...)`` macro marks blocks of code that perform bounds +checking. When such blocks appear inside of an ``@inbounds(...)`` block, the +compiler removes these blocks. When the ``@boundscheck(...)`` is nested inside +of a calling function containing an ``@inbounds(...)``, the compiler will remove +the ``@boundscheck`` block *only if it is inlined* into the calling function. +For example, you might write the method ``sum`` as:: + + function sum(A::AbstractArray) + r = zero(eltype(A)) + for i = 1:length(A) + @inbounds r += A[i] + end + return r + end + +With a custom array-like type ``MyArray`` having:: + + @inline getindex(A::MyArray, i::Real) = (@boundscheck checkbounds(A,i); A.data[to_index(i)]) + +Then when ``getindex`` is inlined into ``sum``, the call to ``checkbounds(A,i)`` +will be elided. If your function contains multiple layers of inlining, only +``@boundscheck`` blocks at most one level of inlining deeper are eliminated. The +rule prevents unintended changes in program behavior from code further up the +stack. + + +Propagating inbounds +-------------------- + +There may be certain scenarios where for code-organization reasons you want more +than one layer between the ``@inbounds`` and ``@boundscheck`` declarations. For +instance, the default ``getindex`` methods have the chain +``getindex(A::AbstractArray, i::Real)`` calls +``getindex(linearindexing(A), A, i)`` calls +``_getindex(::LinearFast, A, i)``. + +To override the "one layer of inlining" rule, a function may be marked with +``@propagate_inbounds`` to propagate an inbounds context (or out of bounds +context) through one additional layer of inlining. diff --git a/doc/devdocs/julia.rst b/doc/devdocs/julia.rst index 538add920f2a5..915d7a7bbaeda 100644 --- a/doc/devdocs/julia.rst +++ b/doc/devdocs/julia.rst @@ -22,3 +22,4 @@ llvm stdio promote-op + boundscheck diff --git a/src/alloc.c b/src/alloc.c index 309a51217c7d3..7ae8d2ebdc1b8 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -100,11 +100,11 @@ jl_sym_t *abstracttype_sym; jl_sym_t *bitstype_sym; jl_sym_t *compositetype_sym; jl_sym_t *type_goto_sym; jl_sym_t *global_sym; jl_sym_t *tuple_sym; jl_sym_t *dot_sym; jl_sym_t *newvar_sym; -jl_sym_t *boundscheck_sym; jl_sym_t *copyast_sym; -jl_sym_t *fastmath_sym; jl_sym_t *pure_sym; -jl_sym_t *simdloop_sym; jl_sym_t *meta_sym; -jl_sym_t *arrow_sym; jl_sym_t *inert_sym; -jl_sym_t *vararg_sym; +jl_sym_t *boundscheck_sym; jl_sym_t *inbounds_sym; +jl_sym_t *copyast_sym; jl_sym_t *fastmath_sym; +jl_sym_t *pure_sym; jl_sym_t *simdloop_sym; +jl_sym_t *meta_sym; jl_sym_t *arrow_sym; +jl_sym_t *inert_sym; jl_sym_t *vararg_sym; typedef struct { int64_t a; diff --git a/src/cgutils.cpp b/src/cgutils.cpp index 7a5b0c0b598ac..c18816e58bcfc 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -1290,12 +1290,26 @@ static void emit_typecheck(const jl_cgval_t &x, jl_value_t *type, const std::str builder.SetInsertPoint(passBB); } +static bool is_inbounds(jl_codectx_t *ctx) +{ + // inbounds rule is either of top two values on inbounds stack are true + bool inbounds = !ctx->inbounds.empty() && ctx->inbounds.back(); + if (ctx->inbounds.size() > 1) + inbounds |= ctx->inbounds[ctx->inbounds.size()-2]; + return inbounds; +} + +static bool is_bounds_check_block(jl_codectx_t *ctx) +{ + return !ctx->boundsCheck.empty() && ctx->boundsCheck.back(); +} + #define CHECK_BOUNDS 1 static Value *emit_bounds_check(const jl_cgval_t &ainfo, jl_value_t *ty, Value *i, Value *len, jl_codectx_t *ctx) { Value *im1 = builder.CreateSub(i, ConstantInt::get(T_size, 1)); #if CHECK_BOUNDS==1 - if (((ctx->boundsCheck.empty() || ctx->boundsCheck.back()==true) && + if ((!is_inbounds(ctx) && jl_options.check_bounds != JL_OPTIONS_CHECK_BOUNDS_OFF) || jl_options.check_bounds == JL_OPTIONS_CHECK_BOUNDS_ON) { Value *ok = builder.CreateICmpULT(im1, len); @@ -1800,7 +1814,7 @@ static Value *emit_array_nd_index(const jl_cgval_t &ainfo, jl_value_t *ex, size_ Value *i = ConstantInt::get(T_size, 0); Value *stride = ConstantInt::get(T_size, 1); #if CHECK_BOUNDS==1 - bool bc = ((ctx->boundsCheck.empty() || ctx->boundsCheck.back()==true) && + bool bc = (!is_inbounds(ctx) && jl_options.check_bounds != JL_OPTIONS_CHECK_BOUNDS_OFF) || jl_options.check_bounds == JL_OPTIONS_CHECK_BOUNDS_ON; BasicBlock *failBB=NULL, *endBB=NULL; diff --git a/src/codegen.cpp b/src/codegen.cpp index 499f3aff82db4..03adfacc295f2 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -580,6 +580,7 @@ typedef struct { bool sret; int nReqArgs; std::vector boundsCheck; + std::vector inbounds; jl_gcinfo_t gc; @@ -3875,9 +3876,31 @@ static jl_cgval_t emit_expr(jl_value_t *expr, jl_codectx_t *ctx, bool isboxed, b #endif builder.SetInsertPoint(tryblk); } + else if (head == inbounds_sym) { + // manipulate inbounds stack + // note that when entering an inbounds context, we must also update + // the boundsCheck context to be false + if (jl_array_len(ex->args) > 0) { + jl_value_t *arg = args[0]; + if (arg == jl_true) { + ctx->inbounds.push_back(true); + ctx->boundsCheck.push_back(false); + } + else if (arg == jl_false) { + ctx->inbounds.push_back(false); + ctx->boundsCheck.push_back(false); + } + else { + if (!ctx->inbounds.empty()) + ctx->inbounds.pop_back(); + if (!ctx->boundsCheck.empty()) + ctx->boundsCheck.pop_back(); + } + } + return ghostValue(jl_void_type); + } else if (head == boundscheck_sym) { - if (jl_array_len(ex->args) > 0 && - jl_options.check_bounds == JL_OPTIONS_CHECK_BOUNDS_DEFAULT) { + if (jl_array_len(ex->args) > 0) { jl_value_t *arg = args[0]; if (arg == jl_true) { ctx->boundsCheck.push_back(true); @@ -4014,6 +4037,7 @@ emit_gcpops(jl_codectx_t *ctx) { Function *F = ctx->f; for(Function::iterator I = F->begin(), E = F->end(); I != E; ++I) { + assert(I->getTerminator() != NULL); if (isa(I->getTerminator())) { builder.SetInsertPoint(I->getTerminator()); // set insert *before* Ret Instruction *gcpop = @@ -4033,7 +4057,6 @@ static void finalize_gc_frame(jl_codectx_t *ctx) clear_gc_frame(gc); return; } - BasicBlock::iterator bbi(gc->gcframe); AllocaInst *newgcframe = gc->gcframe; builder.SetInsertPoint(&*++gc->last_gcframe_inst); // set insert *before* point, e.g. after the gcframe // Allocate the real GC frame @@ -4436,7 +4459,8 @@ static void emit_function(jl_lambda_info_t *lam, jl_llvm_functions_t *declaratio ctx.funcName = jl_symbol_name(lam->name); ctx.vaName = NULL; ctx.vaStack = false; - ctx.boundsCheck.push_back(true); + ctx.inbounds.push_back(false); + ctx.boundsCheck.push_back(false); ctx.cyclectx = cyclectx; // step 2. process var-info lists to see what vars are captured, need boxing @@ -4896,14 +4920,15 @@ static void emit_function(jl_lambda_info_t *lam, jl_llvm_functions_t *declaratio addr.push_back(i * sizeof(void*)); addr.push_back(llvm::dwarf::DW_OP_deref); prepare_call(Intrinsic::getDeclaration(builtins_module, Intrinsic::dbg_value)); + ctx.dbuilder->insertDbgValueIntrinsic( + argArray, + 0, + ctx.vars[s].dinfo, + ctx.dbuilder->createExpression(addr), #ifdef LLVM37 - ctx.dbuilder->insertDbgValueIntrinsic(argArray, 0, ctx.vars[s].dinfo, - ctx.dbuilder->createExpression(addr), - builder.getCurrentDebugLocation().get(), builder.GetInsertBlock()); -#else - ctx.dbuilder->insertDbgValueIntrinsic(argArray, 0, ctx.vars[s].dinfo, - ctx.dbuilder->createExpression(addr), builder.GetInsertBlock()); + builder.getCurrentDebugLocation().get(), #endif + builder.GetInsertBlock()); } } #endif @@ -5342,8 +5367,14 @@ static void emit_function(jl_lambda_info_t *lam, jl_llvm_functions_t *declaratio builder.SetInsertPoint(bb); } } - else { - (void)emit_expr(stmt, &ctx, false, false); + else if (jl_is_expr(stmt) && ((jl_expr_t*)stmt)->head == boundscheck_sym) { + // always emit expressions that update the boundscheck stack + emit_expr(stmt, &ctx, false, false); + } + else if (is_inbounds(&ctx) && is_bounds_check_block(&ctx)) { + // elide bounds check blocks + } else { + emit_expr(stmt, &ctx, false, false); } } @@ -5354,6 +5385,14 @@ static void emit_function(jl_lambda_info_t *lam, jl_llvm_functions_t *declaratio builder.CreateUnreachable(); } + // patch up dangling BasicBlocks from skipped labels + for (std::map::iterator it = labels.begin(); it != labels.end(); ++it) { + if (it->second->getTerminator() == NULL) { + builder.SetInsertPoint(it->second); + builder.CreateUnreachable(); + } + } + // step 16. fix up size of stack root list finalize_gc_frame(&ctx); diff --git a/src/interpreter.c b/src/interpreter.c index 4db311a59fb4f..e70bc36fedcbc 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -471,6 +471,9 @@ static jl_value_t *eval(jl_value_t *e, jl_value_t **locals, size_t nl, size_t ng else if (ex->head == boundscheck_sym) { return (jl_value_t*)jl_nothing; } + else if (ex->head == inbounds_sym) { + return (jl_value_t*)jl_nothing; + } else if (ex->head == fastmath_sym) { return (jl_value_t*)jl_nothing; } diff --git a/src/jltypes.c b/src/jltypes.c index 0e23900640f0b..f2f9550568182 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3608,6 +3608,7 @@ void jl_init_types(void) kw_sym = jl_symbol("kw"); dot_sym = jl_symbol("."); boundscheck_sym = jl_symbol("boundscheck"); + inbounds_sym = jl_symbol("inbounds"); fastmath_sym = jl_symbol("fastmath"); newvar_sym = jl_symbol("newvar"); copyast_sym = jl_symbol("copyast"); diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 86a7c96f35eef..0a7a58c1dcc78 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -1878,9 +1878,9 @@ (if (null? ranges) `(block (= ,oneresult ,expr) ,@(if atype '() `((type_goto ,initlabl ,oneresult))) - (boundscheck false) + (inbounds false) (call (top setindex!) ,result ,oneresult ,ri) - (boundscheck pop) + (inbounds pop) (= ,ri (call (top +) ,ri 1))) `(block (= ,(car states) (call (top start) ,(car rv))) diff --git a/src/julia.h b/src/julia.h index a89723330d943..87cf135c1f216 100644 --- a/src/julia.h +++ b/src/julia.h @@ -607,10 +607,11 @@ extern jl_sym_t *anonymous_sym; extern jl_sym_t *underscore_sym; extern jl_sym_t *abstracttype_sym; extern jl_sym_t *bitstype_sym; extern jl_sym_t *compositetype_sym; extern jl_sym_t *type_goto_sym; extern jl_sym_t *global_sym; extern jl_sym_t *tuple_sym; -extern jl_sym_t *boundscheck_sym; extern jl_sym_t *copyast_sym; -extern jl_sym_t *fastmath_sym; extern jl_sym_t *pure_sym; -extern jl_sym_t *simdloop_sym; extern jl_sym_t *meta_sym; -extern jl_sym_t *arrow_sym; extern jl_sym_t *inert_sym; +extern jl_sym_t *boundscheck_sym; extern jl_sym_t *inbounds_sym; +extern jl_sym_t *copyast_sym; extern jl_sym_t *fastmath_sym; +extern jl_sym_t *pure_sym; extern jl_sym_t *simdloop_sym; +extern jl_sym_t *meta_sym; extern jl_sym_t *arrow_sym; +extern jl_sym_t *inert_sym; // gc ------------------------------------------------------------------------- diff --git a/test/boundscheck.jl b/test/boundscheck.jl new file mode 100644 index 0000000000000..ec6343afa8982 --- /dev/null +++ b/test/boundscheck.jl @@ -0,0 +1,106 @@ + +module TestBoundsCheck + +using Base.Test + +# test for boundscheck block eliminated at same level +@inline function A1() + r = 0 + @boundscheck r += 1 + return r +end + +@noinline function A1_noinline() + r = 0 + @boundscheck r += 1 + return r +end + +function A1_inbounds() + r = 0 + @inbounds begin + @boundscheck r += 1 + end + return r +end + +@test A1() == 1 +@test A1_inbounds() == 0 + +# test for boundscheck block eliminated one layer deep, if the called method is inlined +@inline function A2() + r = A1()+1 + return r +end + +function A2_inbounds() + @inbounds r = A1()+1 + return r +end + +function A2_notinlined() + @inbounds r = A1_noinline()+1 + return r +end + +Base.@propagate_inbounds function A2_propagate_inbounds() + r = A1()+1 + return r +end + +@test A2() == 2 +@test A2_inbounds() == 1 +@test A2_notinlined() == 2 +@test A2_propagate_inbounds() == 2 + +# test boundscheck NOT eliminated two layers deep, unless propagated + +function A3() + r = A2()+1 + return r +end + +function A3_inbounds() + @inbounds r = A2()+1 + return r +end + +function A3_inbounds2() + @inbounds r = A2_propagate_inbounds()+1 + return r +end + +@test A3() == 3 +@test A3_inbounds() == 3 +@test A3_inbounds2() == 2 + +# swapped nesting order of @boundscheck and @inbounds +function A1_nested() + r = 0 + @boundscheck @inbounds r += 1 + return r +end + +@test A1_nested() == 1 + +# elide a throw +cb(x) = x > 0 || throw("cb:error") + +function B1() + y = [1,2,3] + @inbounds begin + @boundscheck cb(0) + end + return 0 +end + +cond(x) = x > 0 ? x : -x +function B2() + y = [1,2,3] + @inbounds begin + @boundscheck cond(0) + end + return 0 +end + +end diff --git a/test/choosetests.jl b/test/choosetests.jl index f485972243497..b9346a96758ee 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -32,7 +32,8 @@ function choosetests(choices = []) "nullable", "meta", "profile", "libgit2", "docs", "markdown", "base64", "serialize", "functors", "misc", "threads", "enums", "cmdlineargs", "i18n", "workspace", "libdl", "int", - "checked", "intset", "floatfuncs", "compile", "parallel", "inline" + "checked", "intset", "floatfuncs", "compile", "parallel", "inline", + "boundscheck" ] if Base.USE_GPL_LIBS