diff --git a/base/inference.jl b/base/inference.jl index 0906187f8aa58..6857ab70dab6e 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -1280,6 +1280,13 @@ widenconst(t::ANY) = t issubstate(a::VarState, b::VarState) = (a.typ โŠ‘ b.typ && a.undef <= b.undef) +# Meta expression head, these generally can't be deleted even when they are +# in a dead branch but can be ignored when analyzing uses/liveness. +is_meta_expr_head(head::Symbol) = + (head === :inbounds || head === :boundscheck || head === :meta || + head === :line) +is_meta_expr(ex::Expr) = is_meta_expr_head(ex.head) + function tmerge(typea::ANY, typeb::ANY) typea โŠ‘ typeb && return typeb typeb โŠ‘ typea && return typea @@ -1389,9 +1396,7 @@ function find_ssavalue_uses(e::ANY, uses, line) elseif isa(e,Expr) b = e::Expr head = b.head - if head === :line - return - end + is_meta_expr_head(head) && return if head === :(=) if isa(b.args[1],SSAValue) id = (b.args[1]::SSAValue).id+1 @@ -1439,6 +1444,7 @@ function unshare_linfo!(li::LambdaInfo) end inlining_enabled() = (JLOptions().can_inline == 1) +coverage_enabled() = (JLOptions().code_coverage != 0) #### entry points for inferring a LambdaInfo given a type signature #### function typeinf_edge(method::Method, atypes::ANY, sparams::SimpleVector, needtree::Bool, optimize::Bool, cached::Bool, caller) @@ -1604,6 +1610,7 @@ function typeinf_ext(linfo::LambdaInfo) linfo.ssavaluetypes = code.ssavaluetypes linfo.pure = code.pure linfo.inlineable = code.inlineable + linfo.propagate_inbounds = code.propagate_inbounds ccall(:jl_set_lambda_rettype, Void, (Any, Any), linfo, code.rettype) if code.jlcall_api == 2 linfo.constval = code.constval @@ -1902,6 +1909,7 @@ function finish(me::InferenceState) type_annotate!(me.linfo, me.stmt_types, me, me.nargs) # run optimization passes on fulltree + force_noinline = false if me.optimize # This pass is required for the AST to be valid in codegen # if any `SSAValue` is created by type inference. Ref issue #6068 @@ -1910,11 +1918,14 @@ function finish(me::InferenceState) # optimizing and use unoptimized IR in codegen. gotoifnot_elim_pass!(me.linfo, me) inlining_pass!(me.linfo, me) - inbounds_meta_elim_pass!(me.linfo.code) + void_use_elim_pass!(me.linfo, me) alloc_elim_pass!(me.linfo, me) getfield_elim_pass!(me.linfo, me) - # remove placeholders - filter!(x->x!==nothing, me.linfo.code) + # Clean up for `alloc_elim_pass!` and `getfield_elim_pass!` + void_use_elim_pass!(me.linfo, me) + meta_elim_pass!(me.linfo, me.linfo.code::Array{Any,1}) + # Pop metadata before label reindexing + force_noinline = popmeta!(me.linfo.code::Array{Any,1}, :noinline)[1] reindex_labels!(me.linfo, me) end widen_all_consts!(me.linfo) @@ -1949,7 +1960,7 @@ function finish(me::InferenceState) end # determine and cache inlineability - if !me.linfo.inlineable + if !me.linfo.inlineable && !force_noinline me.linfo.inlineable = me.linfo.jlcall_api==2 || isinlineable(me.linfo) end @@ -2025,7 +2036,7 @@ function eval_annotate(e::ANY, vtypes::ANY, sv::InferenceState, undefs, pass) e = e::Expr head = e.head - if is(head,:line) || is(head,:const) + if is_meta_expr_head(head) || is(head,:const) return e elseif is(head,:(=)) e.args[2] = eval_annotate(e.args[2], vtypes, sv, undefs, pass) @@ -2041,12 +2052,6 @@ function eval_annotate(e::ANY, vtypes::ANY, sv::InferenceState, undefs, pass) return e end -function expr_cannot_delete(ex::Expr) - head = ex.head - return (head === :inbounds || head === :boundscheck || head === :meta || - head === :line) -end - # annotate types of all symbols in AST function type_annotate!(linfo::LambdaInfo, states::Array{Any,1}, sv::ANY, nargs) nslots = length(states[1]) @@ -2075,7 +2080,7 @@ function type_annotate!(linfo::LambdaInfo, states::Array{Any,1}, sv::ANY, nargs) record_slot_type!(id, widenconst(states[i+1][id].typ), linfo.slottypes) end elseif optimize - if ((isa(expr, Expr) && expr_cannot_delete(expr::Expr)) || + if ((isa(expr, Expr) && is_meta_expr(expr::Expr)) || isa(expr, LineNumberNode)) i += 1 continue @@ -2147,9 +2152,10 @@ function substitute!(e::ANY, na, argexprs, spvals, offset) end if isa(e,Expr) e = e::Expr - if e.head === :static_parameter + head = e.head + if head === :static_parameter return spvals[e.args[1]] - elseif e.head !== :line + elseif !is_meta_expr_head(head) for i=1:length(e.args) e.args[i] = substitute!(e.args[i], na, argexprs, spvals, offset) end @@ -2162,7 +2168,8 @@ end function occurs_more(e::ANY, pred, n) if isa(e,Expr) e = e::Expr - e.head === :line && return 0 + head = e.head + is_meta_expr_head(head) && return 0 c = 0 for a = e.args c += occurs_more(a, pred, n) @@ -2247,8 +2254,7 @@ function effect_free(e::ANY, linfo::LambdaInfo, allow_volatile::Bool) elseif isa(e, Expr) e = e::Expr head = e.head - if head === :static_parameter || head === :meta || head === :line || - head === :inbounds || head === :boundscheck + if head === :static_parameter || is_meta_expr_head(head) return true end ea = e.args @@ -2472,7 +2478,6 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference if spec_miss !== nothing push!(stmts, merge) end - #println(stmts) return (ret_var, stmts) end else @@ -2582,7 +2587,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference body = Expr(:block) body.args = ast - propagate_inbounds, _ = popmeta!(body, :propagate_inbounds) + propagate_inbounds = linfo.propagate_inbounds # see if each argument occurs only once in the body expression stmts = Any[] @@ -2720,7 +2725,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference end if !isempty(stmts) - if all(stmt -> (isa(stmt,Expr) && stmt.head === :line) || isa(stmt, LineNumberNode) || stmt === nothing, + if all(stmt -> (isa(stmt, Expr) && is_meta_expr(stmt::Expr)) || isa(stmt, LineNumberNode) || stmt === nothing, stmts) empty!(stmts) else @@ -2766,10 +2771,10 @@ inline_worthy(body::ANY, cost::Integer) = true # should the expression be part of the inline cost model function inline_ignore(ex::ANY) - isa(ex, LineNumberNode) || - ex === nothing || - isa(ex, Expr) && ((ex::Expr).head === :line || - (ex::Expr).head === :meta) + if isa(ex, LineNumberNode) || ex === nothing + return true + end + return isa(ex, Expr) && is_meta_expr(ex::Expr) end function inline_worthy(body::Expr, cost::Integer=1000) # precondition: 0 < cost; nominal cost = 1000 @@ -2779,7 +2784,7 @@ function inline_worthy(body::Expr, cost::Integer=1000) # precondition: 0 < cost; symlim = 1000 + 5_000_000 รท cost nstmt = 0 for stmt in body.args - if !inline_ignore(stmt) + if !(isa(stmt, SSAValue) || inline_ignore(stmt)) nstmt += 1 end end @@ -2796,7 +2801,7 @@ end ssavalue_increment(body::ANY, incr) = body ssavalue_increment(body::SSAValue, incr) = SSAValue(body.id + incr) function ssavalue_increment(body::Expr, incr) - if body.head === :line + if is_meta_expr(body) return body end for i in 1:length(body.args) @@ -3164,6 +3169,8 @@ function occurs_outside_getfield(linfo::LambdaInfo, e::ANY, sym::ANY, end if isa(e,Expr) e = e::Expr + head = e.head + is_meta_expr_head(head) && return false if is_known_call(e, getfield, linfo) && symequal(e.args[2],sym) idx = e.args[3] if isa(idx,QuoteNode) && (idx.value in field_names) @@ -3174,11 +3181,11 @@ function occurs_outside_getfield(linfo::LambdaInfo, e::ANY, sym::ANY, end return true end - if is(e.head,:(=)) + if head === :(=) return occurs_outside_getfield(linfo, e.args[2], sym, sv, field_count, field_names) else - if (e.head === :block && isa(sym, Slot) && + if (head === :block && isa(sym, Slot) && linfo.slotflags[(sym::Slot).id] & Slot_UsedUndef == 0) ignore_void = true else @@ -3197,14 +3204,279 @@ function occurs_outside_getfield(linfo::LambdaInfo, e::ANY, sym::ANY, 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!(code::Array{Any,1}) - if findfirst(x -> isa(x, Expr) && - ((x.head === :inbounds && x.args[1] === true) || x.head === :boundscheck), - code) == 0 - filter!(x -> !(isa(x, Expr) && x.head === :inbounds), code) +function void_use_elim_pass!(linfo::LambdaInfo, sv) + # Remove top level SSAValue and slots that is `!usedUndef`. + # Also remove some `nothing` while we are at it.... + not_void_use = function (ex::ANY) + if isa(ex, SSAValue) + # Explicitly listed here for clarity + return false + elseif isa(ex, Slot) + return linfo.slotflags[(ex::Slot).id] & Slot_UsedUndef != 0 + elseif isa(ex, GlobalRef) + ex = ex::GlobalRef + return !isdefined(ex.mod, ex.name) + elseif (isa(ex, Expr) || isa(ex, GotoNode) || isa(ex, LineNumberNode) || + isa(ex, NewvarNode) || isa(ex, Symbol) || isa(ex, LabelNode)) + # This is a list of special type handled by the compiler + return true + end + return false + end + filter!(not_void_use, linfo.code::Array{Any,1}) + return +end + +function meta_elim_pass!(linfo::LambdaInfo, code::Array{Any,1}) + # 1. Remove place holders + # + # 2. If coverage is off, remove line number nodes that don't mark any + # real expressions. + # + # 3. Remove top level SSAValue + # + # 4. Handle bounds check elision + # + # 4.1. If check_bounds is always on, delete all `Expr(:boundscheck)` + # 4.2. If check_bounds is always off, delete all boundscheck blocks. + # 4.3. If check_bounds is default, figure out whether each checkbounds + # blocks needs to be eliminated or could be eliminated when inlined + # into another function. Delete the blocks that should be eliminated + # and delete the `Expr(:boundscheck)` for blocks that will never be + # deleted. (i.e. the ones that are not eliminated with + # `length(inbounds_stack) >= 2`) + # + # When deleting IR with boundscheck, keep the label node in order to not + # confuse later passes or codegen. (we could also track if any SSAValue + # is deleted while still having uses that are not but that's a little + # expensive). + # + # 5. Clean up `Expr(:inbounds)` + # + # Delete all `Expr(:inbounds)` that is unnecessary, which is all of them + # for non-default check_bounds. For default check_bounds this includes + # + # * `Expr(:inbounds, true)` in `Expr(:inbounds, true)` + # * `Expr(:inbounds, false)` when + # `!is_inbounds && length(inbounds_stack) >= 2` + # + # Functions without `propagate_inbounds` have an implicit `false` on the + # `inbounds_stack` + # + # There are other cases in which we can eliminate `Expr(:inbounds)` or + # `Expr(:boundscheck)` (e.g. when they don't enclose any non-meta + # expressions). Those are a little harder to detect and are hopefully + # not too common. + do_coverage = coverage_enabled() + check_bounds = JLOptions().check_bounds + + inbounds_stack = linfo.propagate_inbounds ? Bool[] : [false] + # Whether the push is deleted (therefore if the pop has to be too) + # Shared for `Expr(:boundscheck)` and `Expr(:inbounds)` + bounds_elim_stack = Bool[] + # The expression index of the push, set to `0` when encountering a + # non-meta expression that might be affect by the push. + # The clearing needs to be propagated up during pop + # This is not pushed to if the push is already eliminated + # Also shared for `Expr(:boundscheck)` and `Expr(:inbounds)` + bounds_push_pos_stack = [0] + # Number of boundscheck pushes in a eliminated boundscheck block + void_boundscheck_depth = 0 + is_inbounds = check_bounds == 2 + enabled = true + + # Position of the last line number node without any non-meta expressions + # in between. + prev_dbg_stack = [0] + # Whether there's any non-meta exprs after the enclosing `push_loc` + push_loc_pos_stack = [0] + + for i in 1:length(code) + ex = code[i] + if ex === nothing + continue + elseif isa(ex, SSAValue) + code[i] = nothing + continue + elseif isa(ex, LabelNode) + prev_dbg_stack[end] = 0 + push_loc_pos_stack[end] = 0 + continue + elseif !do_coverage && (isa(ex, LineNumberNode) || + (isa(ex, Expr) && (ex::Expr).head === :line)) + prev_label = prev_dbg_stack[end] + if prev_label != 0 + code[prev_label] = nothing + end + prev_dbg_stack[end] = i + continue + elseif !isa(ex, Expr) + if enabled + prev_dbg_stack[end] = 0 + push_loc_pos_stack[end] = 0 + bounds_push_pos_stack[end] = 0 + else + code[i] = nothing + end + continue + end + ex = ex::Expr + args = ex.args + head = ex.head + if head === :boundscheck + if !enabled + # we are in an eliminated boundscheck, simply record the number + # of push/pop + if !(args[1] === :pop) + void_boundscheck_depth += 1 + elseif void_boundscheck_depth == 0 + pop!(bounds_elim_stack) + enabled = true + else + void_boundscheck_depth -= 1 + end + code[i] = nothing + elseif args[1] === :pop + # This will also delete pops that don't match + if (isempty(bounds_elim_stack) ? true : + pop!(bounds_elim_stack)) + code[i] = nothing + continue + end + push_idx = bounds_push_pos_stack[end] + if length(bounds_push_pos_stack) > 1 + pop!(bounds_push_pos_stack) + end + if push_idx > 0 + code[push_idx] = nothing + code[i] = nothing + else + bounds_push_pos_stack[end] = 0 + end + elseif is_inbounds + code[i] = nothing + push!(bounds_elim_stack, true) + enabled = false + elseif check_bounds == 1 || length(inbounds_stack) >= 2 + # Not inbounds and at least two levels deep, this will never + # be eliminated when inlined to another function. + code[i] = nothing + push!(bounds_elim_stack, true) + else + push!(bounds_elim_stack, false) + push!(bounds_push_pos_stack, i) + end + continue + end + if !enabled && !(do_coverage && head === :meta) + code[i] = nothing + continue + end + if head === :inbounds + if check_bounds != 0 + code[i] = nothing + continue + end + arg1 = args[1] + if arg1 === true + if inbounds_stack[end] + code[i] = nothing + push!(bounds_elim_stack, true) + else + is_inbounds = true + push!(bounds_elim_stack, false) + push!(bounds_push_pos_stack, i) + end + push!(inbounds_stack, true) + elseif arg1 === false + if is_inbounds + if !inbounds_stack[end] + is_inbounds = false + end + push!(bounds_elim_stack, false) + push!(bounds_push_pos_stack, i) + elseif length(inbounds_stack) >= 2 + code[i] = nothing + push!(bounds_elim_stack, true) + else + push!(bounds_elim_stack, false) + push!(bounds_push_pos_stack, i) + end + push!(inbounds_stack, false) + else + # pop + inbounds_len = length(inbounds_stack) + if inbounds_len != 0 + pop!(inbounds_stack) + inbounds_len -= 1 + end + # This will also delete pops that don't match + if (isempty(bounds_elim_stack) ? true : + pop!(bounds_elim_stack)) + # No need to update `is_inbounds` since the push was a no-op + code[i] = nothing + continue + end + if inbounds_len >= 2 + is_inbounds = (inbounds_stack[inbounds_len] || + inbounds_stack[inbounds_len - 1]) + elseif inbounds_len == 1 + is_inbounds = inbounds_stack[inbounds_len] + else + is_inbounds = false + end + push_idx = bounds_push_pos_stack[end] + if length(bounds_push_pos_stack) > 1 + pop!(bounds_push_pos_stack) + end + if push_idx > 0 + code[push_idx] = nothing + code[i] = nothing + else + bounds_push_pos_stack[end] = 0 + end + end + continue + end + if head !== :meta + prev_dbg_stack[end] = 0 + push_loc_pos_stack[end] = 0 + bounds_push_pos_stack[end] = 0 + continue + end + nargs = length(args) + if do_coverage || nargs == 0 + continue + end + arg1 = args[1] + if arg1 === :push_loc + push!(prev_dbg_stack, 0) + push!(push_loc_pos_stack, i) + elseif arg1 === :pop_loc + prev_dbg = if length(prev_dbg_stack) > 1 + pop!(prev_dbg_stack) + else + prev_dbg_stack[end] + end + if prev_dbg > 0 + code[prev_dbg] = nothing + end + push_loc = if length(push_loc_pos_stack) > 1 + pop!(push_loc_pos_stack) + else + push_loc_pos_stack[end] + end + if push_loc > 0 + code[push_loc] = nothing + code[i] = nothing + else + push_loc_pos_stack[end] = 0 + end + else + continue + end end + filter!(x->x!==nothing, code) end # does the same job as alloc_elim_pass for allocations inline in getfields diff --git a/src/alloc.c b/src/alloc.c index 6255e056abb29..289bc8a12e4ce 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -101,6 +101,7 @@ jl_sym_t *meta_sym; jl_sym_t *compiler_temp_sym; jl_sym_t *inert_sym; jl_sym_t *vararg_sym; jl_sym_t *unused_sym; jl_sym_t *static_parameter_sym; jl_sym_t *polly_sym; jl_sym_t *inline_sym; +jl_sym_t *propagate_inbounds_sym; typedef struct { int64_t a; @@ -349,6 +350,8 @@ static void jl_lambda_info_set_ast(jl_lambda_info_t *li, jl_expr_t *ast) li->pure = 1; else if (ma == (jl_value_t*)inline_sym) li->inlineable = 1; + else if (ma == (jl_value_t*)propagate_inbounds_sym) + li->propagate_inbounds = 1; else jl_array_ptr_set(meta, ins++, ma); } @@ -431,6 +434,7 @@ JL_DLLEXPORT jl_lambda_info_t *jl_new_lambda_info_uninit(void) li->constval = NULL; li->pure = 0; li->inlineable = 0; + li->propagate_inbounds = 0; return li; } @@ -544,6 +548,7 @@ static jl_lambda_info_t *jl_copy_lambda(jl_lambda_info_t *linfo) new_linfo->sparam_vals = linfo->sparam_vals; new_linfo->pure = linfo->pure; new_linfo->inlineable = linfo->inlineable; + new_linfo->propagate_inbounds = linfo->propagate_inbounds; new_linfo->nargs = linfo->nargs; new_linfo->isva = linfo->isva; new_linfo->rettype = linfo->rettype; diff --git a/src/codegen.cpp b/src/codegen.cpp index 6e931439ac3b7..8d9f29d663600 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -4498,18 +4498,13 @@ static std::unique_ptr emit_function(jl_lambda_info_t *lam, jl_llvm_func DebugLoc loc; StringRef file; ssize_t line; - bool enabled; bool is_inbounds; bool loc_changed; bool is_poploc; }; std::vector stmtprops(stmtslen); std::vector DI_stack; - std::vector boundsCheck_stack{false}; std::vector inbounds_stack{false}; - auto is_bounds_check_block = [&] () { - return !boundsCheck_stack.empty() && boundsCheck_stack.back(); - }; auto is_inbounds = [&] () { // inbounds rule is either of top two values on inbounds stack are true size_t sz = inbounds_stack.size(); @@ -4518,21 +4513,22 @@ static std::unique_ptr emit_function(jl_lambda_info_t *lam, jl_llvm_func inbounds |= inbounds_stack[sz - 2]; return inbounds; }; - StmtProp cur_prop{topdebugloc, filename, toplineno, true, false, true, false}; - // this should not be necessary but it seems that meta node can cause - // an offset between the label number and the statement number - std::map label_map; + StmtProp cur_prop{topdebugloc, filename, toplineno, false, true, false}; for (i = 0; i < stmtslen; i++) { cur_prop.loc_changed = false; cur_prop.is_poploc = false; jl_value_t *stmt = jl_array_ptr_ref(stmts, i); jl_expr_t *expr = jl_is_expr(stmt) ? (jl_expr_t*)stmt : nullptr; +#ifndef NDEBUG if (jl_is_labelnode(stmt)) { int lname = jl_labelnode_label(stmt); if (lname != i + 1) { - label_map[lname] = i + 1; + jl_safe_printf("Label number mismatch.\n"); + jl_(stmts); + abort(); } } +#endif if (jl_is_linenode(stmt) || (expr && expr->head == line_sym)) { ssize_t lno = -1; if (jl_is_linenode(stmt)) { @@ -4627,64 +4623,26 @@ static std::unique_ptr emit_function(jl_lambda_info_t *lam, jl_llvm_func } if (expr) { jl_value_t **args = (jl_value_t**)jl_array_data(expr->args); - if (expr->head == boundscheck_sym) { - if (jl_array_len(expr->args) > 0) { - jl_value_t *arg = args[0]; - if (arg == jl_true) { - boundsCheck_stack.push_back(true); - } - else if (arg == jl_false) { - boundsCheck_stack.push_back(false); - } - else { - if (!boundsCheck_stack.empty()) { - boundsCheck_stack.pop_back(); - } - } - } - } - else if (cur_prop.enabled && expr->head == inbounds_sym) { + if (expr->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(expr->args) > 0) { jl_value_t *arg = args[0]; if (arg == jl_true) { inbounds_stack.push_back(true); - boundsCheck_stack.push_back(false); } else if (arg == jl_false) { inbounds_stack.push_back(false); - boundsCheck_stack.push_back(false); } - else { - if (!inbounds_stack.empty()) - inbounds_stack.pop_back(); - if (!boundsCheck_stack.empty()) - boundsCheck_stack.pop_back(); + else if (!inbounds_stack.empty()) { + inbounds_stack.pop_back(); } } } } - bool is_ib = is_inbounds(); - if (is_ib && is_bounds_check_block() && - jl_options.check_bounds != JL_OPTIONS_CHECK_BOUNDS_ON) { - // elide bounds check blocks in inbounds context - cur_prop.enabled = false; - } - else if (is_bounds_check_block() && - jl_options.check_bounds == JL_OPTIONS_CHECK_BOUNDS_OFF) { - // elide bounds check blocks when turned off by options - cur_prop.enabled = false; - } - else { - cur_prop.enabled = true; - } - cur_prop.is_inbounds = is_ib; + cur_prop.is_inbounds = is_inbounds(); stmtprops[i] = cur_prop; } DI_stack.clear(); - boundsCheck_stack.clear(); inbounds_stack.clear(); // step 12. Do codegen in control flow order @@ -4718,9 +4676,6 @@ static std::unique_ptr emit_function(jl_lambda_info_t *lam, jl_llvm_func // if `unconditional` a unconditional branch is created to the target // label and the cursor is set to the next statement to process auto handle_label = [&] (int lname, bool unconditional) { - auto it = label_map.find(lname); - if (it != label_map.end()) - lname = it->second; auto &bb = labels[lname]; BasicBlock *cur_bb = builder.GetInsertBlock(); // Check if we've already visited this label @@ -4774,7 +4729,7 @@ static std::unique_ptr emit_function(jl_lambda_info_t *lam, jl_llvm_func if (ctx.debug_enabled) builder.SetCurrentDebugLocation(props.loc); // Disable coverage for pop_loc, it doesn't start a new expression - if (do_coverage && props.enabled && !props.is_poploc) { + if (do_coverage && !props.is_poploc) { coverageVisitLine(props.file, props.line); } } @@ -4787,10 +4742,6 @@ static std::unique_ptr emit_function(jl_lambda_info_t *lam, jl_llvm_func handle_label(lname, true); continue; } - if (!props.enabled) { - find_next_stmt(cursor + 1); - continue; - } if (expr && expr->head == return_sym) { bool retboxed = false; Type *retty; diff --git a/src/dump.c b/src/dump.c index 3f16463d56afe..c2c387e7af753 100644 --- a/src/dump.c +++ b/src/dump.c @@ -949,6 +949,7 @@ static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v) jl_serialize_value(s, (jl_value_t*)li->sparam_vals); write_int8(s->s, li->pure); write_int8(s->s, li->inlineable); + write_int8(s->s, li->propagate_inbounds); write_int8(s->s, li->isva); write_int32(s->s, li->nargs); jl_serialize_value(s, (jl_value_t*)li->def); @@ -1616,6 +1617,7 @@ static jl_value_t *jl_deserialize_value_(jl_serializer_state *s, jl_value_t *vta li->unspecialized_ducttape = NULL; li->pure = read_int8(s->s); li->inlineable = read_int8(s->s); + li->propagate_inbounds = read_int8(s->s); li->isva = read_int8(s->s); li->nargs = read_int32(s->s); li->def = (jl_method_t*)jl_deserialize_value(s, (jl_value_t**)&li->def); diff --git a/src/jltypes.c b/src/jltypes.c index 9caf8b720f0a8..a0a224e737b5a 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3880,7 +3880,7 @@ void jl_init_types(void) jl_lambda_info_type = jl_new_datatype(jl_symbol("LambdaInfo"), jl_any_type, jl_emptysvec, - jl_svec(24, + jl_svec(25, jl_symbol("rettype"), jl_symbol("sparam_syms"), jl_symbol("sparam_vals"), @@ -3898,13 +3898,14 @@ void jl_init_types(void) jl_symbol("inferred"), jl_symbol("pure"), jl_symbol("inlineable"), + jl_symbol("propagate_inbounds"), jl_symbol("inInference"), jl_symbol("inCompile"), jl_symbol("jlcall_api"), jl_symbol(""), jl_symbol("fptr"), jl_symbol(""), jl_symbol("")), - jl_svec(24, + jl_svec(25, jl_any_type, jl_simplevector_type, jl_simplevector_type, @@ -3924,6 +3925,7 @@ void jl_init_types(void) jl_bool_type, jl_bool_type, jl_bool_type, + jl_bool_type, jl_uint8_type, jl_bool_type, jl_any_type, @@ -3991,9 +3993,9 @@ void jl_init_types(void) jl_svecset(jl_methtable_type->types, 6, jl_int32_type); // DWORD #endif jl_svecset(jl_methtable_type->types, 7, jl_int32_type); // uint32_t - jl_svecset(jl_lambda_info_type->types, 21, jl_voidpointer_type); jl_svecset(jl_lambda_info_type->types, 22, jl_voidpointer_type); jl_svecset(jl_lambda_info_type->types, 23, jl_voidpointer_type); + jl_svecset(jl_lambda_info_type->types, 24, jl_voidpointer_type); jl_compute_field_offsets(jl_datatype_type); jl_compute_field_offsets(jl_typename_type); @@ -4071,6 +4073,7 @@ void jl_init_types(void) compiler_temp_sym = jl_symbol("#temp#"); polly_sym = jl_symbol("polly"); inline_sym = jl_symbol("inline"); + propagate_inbounds_sym = jl_symbol("propagate_inbounds"); tttvar = jl_new_typevar(jl_symbol("T"), (jl_value_t*)jl_bottom_type, diff --git a/src/julia.h b/src/julia.h index 6b5c790603c83..61af9fc6930d1 100644 --- a/src/julia.h +++ b/src/julia.h @@ -258,6 +258,7 @@ typedef struct _jl_lambda_info_t { int8_t inferred; int8_t pure; int8_t inlineable; + int8_t propagate_inbounds; int8_t inInference; // flags to tell if inference is running on this function int8_t inCompile; // flag to tell if codegen is running on this function int8_t jlcall_api; // the c-abi for fptr; 0 = jl_fptr_t, 1 = jl_fptr_sparam_t, 2 = constval @@ -555,36 +556,7 @@ extern JL_DLLEXPORT jl_value_t *jl_false; extern JL_DLLEXPORT jl_value_t *jl_nothing; // some important symbols -extern jl_sym_t *call_sym; extern jl_sym_t *invoke_sym; -extern jl_sym_t *empty_sym; extern jl_sym_t *body_sym; -extern jl_sym_t *dots_sym; extern jl_sym_t *vararg_sym; -extern jl_sym_t *quote_sym; extern jl_sym_t *newvar_sym; -extern jl_sym_t *top_sym; extern jl_sym_t *dot_sym; -extern jl_sym_t *line_sym; extern jl_sym_t *toplevel_sym; -extern jl_sym_t *core_sym; extern jl_sym_t *globalref_sym; extern JL_DLLEXPORT jl_sym_t *jl_incomplete_sym; -extern jl_sym_t *error_sym; extern jl_sym_t *amp_sym; -extern jl_sym_t *module_sym; extern jl_sym_t *colons_sym; -extern jl_sym_t *export_sym; extern jl_sym_t *import_sym; -extern jl_sym_t *importall_sym; extern jl_sym_t *using_sym; -extern jl_sym_t *goto_sym; extern jl_sym_t *goto_ifnot_sym; -extern jl_sym_t *label_sym; extern jl_sym_t *return_sym; -extern jl_sym_t *lambda_sym; extern jl_sym_t *assign_sym; -extern jl_sym_t *method_sym; extern jl_sym_t *slot_sym; -extern jl_sym_t *enter_sym; extern jl_sym_t *leave_sym; -extern jl_sym_t *exc_sym; extern jl_sym_t *new_sym; -extern jl_sym_t *compiler_temp_sym; -extern jl_sym_t *const_sym; extern jl_sym_t *thunk_sym; -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 *global_sym; extern jl_sym_t *unused_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 *list_sym; -extern jl_sym_t *inert_sym; extern jl_sym_t *static_parameter_sym; -extern jl_sym_t *polly_sym; extern jl_sym_t *inline_sym; // gc ------------------------------------------------------------------------- diff --git a/src/julia_internal.h b/src/julia_internal.h index f9015882d3738..abc4d7323e163 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -766,6 +766,37 @@ JL_DLLEXPORT jl_array_t *jl_array_cconvert_cstring(jl_array_t *a); int isabspath(const char *in); +extern jl_sym_t *call_sym; extern jl_sym_t *invoke_sym; +extern jl_sym_t *empty_sym; extern jl_sym_t *body_sym; +extern jl_sym_t *dots_sym; extern jl_sym_t *vararg_sym; +extern jl_sym_t *quote_sym; extern jl_sym_t *newvar_sym; +extern jl_sym_t *top_sym; extern jl_sym_t *dot_sym; +extern jl_sym_t *line_sym; extern jl_sym_t *toplevel_sym; +extern jl_sym_t *core_sym; extern jl_sym_t *globalref_sym; +extern jl_sym_t *error_sym; extern jl_sym_t *amp_sym; +extern jl_sym_t *module_sym; extern jl_sym_t *colons_sym; +extern jl_sym_t *export_sym; extern jl_sym_t *import_sym; +extern jl_sym_t *importall_sym; extern jl_sym_t *using_sym; +extern jl_sym_t *goto_sym; extern jl_sym_t *goto_ifnot_sym; +extern jl_sym_t *label_sym; extern jl_sym_t *return_sym; +extern jl_sym_t *lambda_sym; extern jl_sym_t *assign_sym; +extern jl_sym_t *method_sym; extern jl_sym_t *slot_sym; +extern jl_sym_t *enter_sym; extern jl_sym_t *leave_sym; +extern jl_sym_t *exc_sym; extern jl_sym_t *new_sym; +extern jl_sym_t *compiler_temp_sym; +extern jl_sym_t *const_sym; extern jl_sym_t *thunk_sym; +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 *global_sym; extern jl_sym_t *unused_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 *list_sym; +extern jl_sym_t *inert_sym; extern jl_sym_t *static_parameter_sym; +extern jl_sym_t *polly_sym; extern jl_sym_t *inline_sym; +extern jl_sym_t *propagate_inbounds_sym; + #ifdef __cplusplus } #endif diff --git a/test/boundscheck_exec.jl b/test/boundscheck_exec.jl index e711fcf172da8..daf602c91c06e 100644 --- a/test/boundscheck_exec.jl +++ b/test/boundscheck_exec.jl @@ -151,4 +151,27 @@ end @test B2() == 0 +# Make sure type inference doesn't incorrectly optimize out +# `Expr(:inbounds, false)` +# Simply `return a[1]` doesn't work due to inlining bug +@inline function f1(a) + # This has to be an arrayget / arrayset since these currently have a + # implicit `Expr(:boundscheck)` that's not visible to type inference + x = a[1] + return x +end +# second level +@inline function g1(a) + x = f1(a) + return x +end +function k1(a) + # This `Expr(:inbounds, true)` shouldn't affect `f1` + @inbounds x = g1(a) + return x +end +if bc_opt != bc_off + @test_throws BoundsError k1(Int[]) +end + end