diff --git a/base/atomics.jl b/base/atomics.jl index e6d62c3fc807b..7312206c19896 100644 --- a/base/atomics.jl +++ b/base/atomics.jl @@ -20,7 +20,7 @@ export # - LLVM doesn't currently support atomics on floats for ppc64 # C++20 is adding limited support for atomics on float, but as of # now Clang does not support that yet. -if Sys.ARCH == :i686 || startswith(string(Sys.ARCH), "arm") || +if Sys.ARCH === :i686 || startswith(string(Sys.ARCH), "arm") || Sys.ARCH === :powerpc64le || Sys.ARCH === :ppc64le const inttypes = (Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64) diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index 1e812db13c9eb..48a315de83fa9 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -30,7 +30,6 @@ const IR_FLAG_EFFECT_FREE = 0x01 << 4 # This statement was proven not to throw const IR_FLAG_NOTHROW = 0x01 << 5 - const TOP_TUPLE = GlobalRef(Core, :tuple) ##################### @@ -579,7 +578,7 @@ function run_passes( @pass "Inlining" ir = ssa_inlining_pass!(ir, ir.linetable, sv.inlining, ci.propagate_inbounds) # @timeit "verify 2" verify_ir(ir) @pass "compact 2" ir = compact!(ir) - @pass "SROA" ir = sroa_pass!(ir, sv.inlining) + @pass "SROA" ir = sroa_pass!(ir) @pass "ADCE" ir = adce_pass!(ir) @pass "type lift" ir = type_lift_pass!(ir) @pass "compact 3" ir = compact!(ir) @@ -772,7 +771,7 @@ function statement_cost(ex::Expr, line::Int, src::Union{CodeInfo, IRCode}, sptyp return 0 end return error_path ? params.inline_error_path_cost : params.inline_nonleaf_penalty - elseif head === :foreigncall || head === :invoke || head == :invoke_modify + elseif head === :foreigncall || head === :invoke || head === :invoke_modify # Calls whose "return type" is Union{} do not actually return: # they are errors. Since these are not part of the typical # run-time of the function, we omit them from diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index ca9eb818f0edb..dca085d2ac75d 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -767,7 +767,7 @@ function rewrite_apply_exprargs!( elseif isa(new_info, MethodMatchInfo) || isa(new_info, UnionSplitInfo) new_infos = isa(new_info, MethodMatchInfo) ? MethodMatchInfo[new_info] : new_info.matches # See if we can inline this call to `iterate` - analyze_single_call!( + handle_call!( ir, state1.id, new_stmt, new_infos, flag, new_sig, istate, todo) end @@ -874,8 +874,7 @@ function validate_sparams(sparams::SimpleVector) end function analyze_method!(match::MethodMatch, argtypes::Vector{Any}, - flag::UInt8, state::InliningState, - do_resolve::Bool = true) + flag::UInt8, state::InliningState) method = match.method spec_types = match.spec_types @@ -909,7 +908,7 @@ function analyze_method!(match::MethodMatch, argtypes::Vector{Any}, todo = InliningTodo(mi, match, argtypes) # If we don't have caches here, delay resolving this MethodInstance # until the batch inlining step (or an external post-processing pass) - do_resolve && state.mi_cache === nothing && return todo + state.mi_cache === nothing && return todo return resolve_todo(todo, state, flag) end @@ -921,7 +920,7 @@ function retrieve_ir_for_inlining(mi::MethodInstance, src::Array{UInt8, 1}) src = ccall(:jl_uncompress_ir, Any, (Any, Ptr{Cvoid}, Any), mi.def, C_NULL, src::Vector{UInt8})::CodeInfo return inflate_ir!(src, mi) end -retrieve_ir_for_inlining(mi::MethodInstance, src::CodeInfo) = inflate_ir(src, mi)::IRCode +retrieve_ir_for_inlining(mi::MethodInstance, src::CodeInfo) = inflate_ir(src, mi) retrieve_ir_for_inlining(mi::MethodInstance, ir::IRCode) = copy(ir) function handle_single_case!( @@ -1225,10 +1224,8 @@ function process_simple!(ir::IRCode, idx::Int, state::InliningState, todo::Vecto end # TODO inline non-`isdispatchtuple`, union-split callsites? -function compute_inlining_cases( - infos::Vector{MethodMatchInfo}, flag::UInt8, - sig::Signature, state::InliningState, - do_resolve::Bool = true) +function compute_inlining_cases(infos::Vector{MethodMatchInfo}, + flag::UInt8, sig::Signature, state::InliningState) argtypes = sig.argtypes cases = InliningCase[] local any_fully_covered = false @@ -1245,7 +1242,7 @@ function compute_inlining_cases( continue end for match in meth - handled_all_cases &= handle_match!(match, argtypes, flag, state, cases, true, do_resolve) + handled_all_cases &= handle_match!(match, argtypes, flag, state, cases, #=allow_abstract=#true) any_fully_covered |= match.fully_covers end end @@ -1258,23 +1255,10 @@ function compute_inlining_cases( return cases, handled_all_cases & any_fully_covered end -function analyze_single_call!( - ir::IRCode, idx::Int, stmt::Expr, infos::Vector{MethodMatchInfo}, flag::UInt8, - sig::Signature, state::InliningState, todo::Vector{Pair{Int, Any}}) - - r = compute_inlining_cases(infos, flag, sig, state) - r === nothing && return nothing - cases, all_covered = r - handle_cases!(ir, idx, stmt, argtypes_to_type(sig.argtypes), cases, - all_covered, todo, state.params) -end - -# similar to `analyze_single_call!`, but with constant results -function handle_const_call!( - ir::IRCode, idx::Int, stmt::Expr, cinfo::ConstCallInfo, flag::UInt8, - sig::Signature, state::InliningState, todo::Vector{Pair{Int, Any}}) +function compute_inlining_cases(info::ConstCallInfo, + flag::UInt8, sig::Signature, state::InliningState) argtypes = sig.argtypes - (; call, results) = cinfo + (; call, results) = info infos = isa(call, MethodMatchInfo) ? MethodMatchInfo[call] : call.matches cases = InliningCase[] local any_fully_covered = false @@ -1302,7 +1286,7 @@ function handle_const_call!( handled_all_cases &= handle_const_prop_result!(result, argtypes, flag, state, cases, true) else @assert result === nothing - handled_all_cases &= handle_match!(match, argtypes, flag, state, cases, true) + handled_all_cases &= handle_match!(match, argtypes, flag, state, cases, #=allow_abstract=#true) end end end @@ -1312,21 +1296,39 @@ function handle_const_call!( filter!(case::InliningCase->isdispatchtuple(case.sig), cases) end - handle_cases!(ir, idx, stmt, argtypes_to_type(argtypes), cases, - handled_all_cases & any_fully_covered, todo, state.params) + return cases, handled_all_cases & any_fully_covered +end + +function handle_call!( + ir::IRCode, idx::Int, stmt::Expr, infos::Vector{MethodMatchInfo}, flag::UInt8, + sig::Signature, state::InliningState, todo::Vector{Pair{Int, Any}}) + cases = compute_inlining_cases(infos, flag, sig, state) + cases === nothing && return nothing + cases, all_covered = cases + handle_cases!(ir, idx, stmt, argtypes_to_type(sig.argtypes), cases, + all_covered, todo, state.params) +end + +function handle_const_call!( + ir::IRCode, idx::Int, stmt::Expr, info::ConstCallInfo, flag::UInt8, + sig::Signature, state::InliningState, todo::Vector{Pair{Int, Any}}) + cases = compute_inlining_cases(info, flag, sig, state) + cases === nothing && return nothing + cases, all_covered = cases + handle_cases!(ir, idx, stmt, argtypes_to_type(sig.argtypes), cases, + all_covered, todo, state.params) end function handle_match!( match::MethodMatch, argtypes::Vector{Any}, flag::UInt8, state::InliningState, - cases::Vector{InliningCase}, allow_abstract::Bool = false, - do_resolve::Bool = true) + cases::Vector{InliningCase}, allow_abstract::Bool = false) spec_types = match.spec_types allow_abstract || isdispatchtuple(spec_types) || return false # we may see duplicated dispatch signatures here when a signature gets widened # during abstract interpretation: for the purpose of inlining, we can just skip # processing this dispatch candidate _any(case->case.sig === spec_types, cases) && return true - item = analyze_method!(match, argtypes, flag, state, do_resolve) + item = analyze_method!(match, argtypes, flag, state) item === nothing && return false push!(cases, InliningCase(spec_types, item)) return true @@ -1384,6 +1386,42 @@ function handle_const_opaque_closure_call!( return nothing end +function handle_finalizer_call!( + ir::IRCode, stmt::Expr, info::FinalizerInfo, state::InliningState) + # Only inline finalizers that are known nothrow and notls. + # This avoids having to set up state for finalizer isolation + (is_nothrow(info.effects) && is_notaskstate(info.effects)) || return nothing + + info = info.info + if isa(info, MethodMatchInfo) + infos = MethodMatchInfo[info] + elseif isa(info, UnionSplitInfo) + infos = info.matches + # elseif isa(info, ConstCallInfo) + # # NOTE currently this code path isn't active as constant propagation won't happen + # # for `Core.finalizer` call because inference currently isn't able to fold a mutable + # # object as a constant + else + return nothing + end + + ft = argextype(stmt.args[2], ir) + has_free_typevars(ft) && return nothing + f = singleton_type(ft) + argtypes = Vector{Any}(undef, 2) + argtypes[1] = ft + argtypes[2] = argextype(stmt.args[3], ir) + sig = Signature(f, ft, argtypes) + + cases = compute_inlining_cases(infos, #=flag=#UInt8(0), sig, state) + cases === nothing && return nothing + cases, all_covered = cases + if all_covered && length(cases) == 1 + push!(stmt.args, cases[1].item) + end + return nothing +end + function inline_const_if_inlineable!(inst::Instruction) rt = inst[:type] if rt isa Const && is_inlineable_constant(rt.val) @@ -1434,53 +1472,15 @@ function assemble_inline_todo!(ir::IRCode, state::InliningState) end # Handle invoke - if sig.f === Core.invoke - if isa(info, InvokeCallInfo) - inline_invoke!(ir, idx, stmt, info, flag, sig, state, todo) - end + if isa(info, InvokeCallInfo) + inline_invoke!(ir, idx, stmt, info, flag, sig, state, todo) continue end # Handle finalizer - if sig.f === Core.finalizer - if isa(info, FinalizerInfo) - # Only inline finalizers that are known nothrow and notls. - # This avoids having to set up state for finalizer isolation - (is_nothrow(info.effects) && is_notaskstate(info.effects)) || continue - - info = info.info - if isa(info, MethodMatchInfo) - infos = MethodMatchInfo[info] - elseif isa(info, UnionSplitInfo) - infos = info.matches - else - continue - end - - ft = argextype(stmt.args[2], ir) - has_free_typevars(ft) && return nothing - f = singleton_type(ft) - argtypes = Vector{Any}(undef, 2) - argtypes[1] = ft - argtypes[2] = argextype(stmt.args[3], ir) - sig = Signature(f, ft, argtypes) - - cases, all_covered = compute_inlining_cases(infos, UInt8(0), sig, state, false) - length(cases) == 0 && continue - if all_covered && length(cases) == 1 - if isa(cases[1], InliningCase) - case1 = cases[1].item - if isa(case1, InliningTodo) - push!(stmt.args, true) - push!(stmt.args, case1.mi) - elseif isa(case1, InvokeCase) - push!(stmt.args, false) - push!(stmt.args, case1.invoke) - end - end - end - continue - end + if isa(info, FinalizerInfo) + handle_finalizer_call!(ir, stmt, info, state) + continue end # if inference arrived here with constant-prop'ed result(s), @@ -1501,7 +1501,7 @@ function assemble_inline_todo!(ir::IRCode, state::InliningState) continue # isa(info, ReturnTypeCallInfo), etc. end - analyze_single_call!(ir, idx, stmt, infos, flag, sig, state, todo) + handle_call!(ir, idx, stmt, infos, flag, sig, state, todo) end return todo diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index 047d4577cc7bc..ef7149be25cae 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -14,7 +14,7 @@ GetfieldUse(idx::Int) = SSAUse(:getfield, idx) PreserveUse(idx::Int) = SSAUse(:preserve, idx) NoPreserve() = SSAUse(:nopreserve, 0) IsdefinedUse(idx::Int) = SSAUse(:isdefined, idx) -AddFinalizerUse(idx::Int) = SSAUse(:add_finalizer, idx) +FinalizerUse(idx::Int) = SSAUse(:finalizer, idx) """ du::SSADefUse @@ -736,7 +736,7 @@ its argument). In a case when all usages are fully eliminated, `struct` allocation may also be erased as a result of succeeding dead code elimination. """ -function sroa_pass!(ir::IRCode, inlining::Union{Nothing, InliningState} = nothing) +function sroa_pass!(ir::IRCode) compact = IncrementalCompact(ir) defuses = nothing # will be initialized once we encounter mutability in order to reduce dynamic allocations lifting_cache = IdDict{Pair{AnySSAValue, Any}, AnySSAValue}() @@ -769,11 +769,11 @@ function sroa_pass!(ir::IRCode, inlining::Union{Nothing, InliningState} = nothin widenconst(field_ordering) === Bool && (field_ordering = :unspecified) end elseif is_known_call(stmt, Core.finalizer, compact) - 3 <= length(stmt.args) <= 5 || continue + 3 <= length(stmt.args) <= 4 || continue # Inlining performs legality checks on the finalizer to determine # whether or not we may inline it. If so, it appends extra arguments # at the end of the intrinsic. Detect that here. - length(stmt.args) == 5 || continue + length(stmt.args) == 4 || continue is_finalizer = true elseif isexpr(stmt, :foreigncall) nccallargs = length(stmt.args[3]::SimpleVector) @@ -876,7 +876,7 @@ function sroa_pass!(ir::IRCode, inlining::Union{Nothing, InliningState} = nothin elseif is_isdefined push!(defuse.uses, IsdefinedUse(idx)) elseif is_finalizer - push!(defuse.uses, AddFinalizerUse(idx)) + push!(defuse.uses, FinalizerUse(idx)) else push!(defuse.uses, GetfieldUse(idx)) end @@ -934,7 +934,7 @@ function sroa_pass!(ir::IRCode, inlining::Union{Nothing, InliningState} = nothin used_ssas = copy(compact.used_ssas) simple_dce!(compact, (x::SSAValue) -> used_ssas[x.id] -= 1) ir = complete(compact) - sroa_mutables!(ir, defuses, used_ssas, lazydomtree, inlining) + sroa_mutables!(ir, defuses, used_ssas, lazydomtree) return ir else simple_dce!(compact) @@ -942,36 +942,32 @@ function sroa_pass!(ir::IRCode, inlining::Union{Nothing, InliningState} = nothin end end -function try_inline_finalizer!(ir::IRCode, argexprs::Vector{Any}, idx::Int, mi::MethodInstance, inlining::InliningState) - code = get(inlining.mi_cache, mi, nothing) - if code isa CodeInstance - if use_const_api(code) - # No code in the function - Nothing to do - inlining.et !== nothing && push!(inlining.et, mi) - return true - end - src = code.inferred - else - src = code +function inline_finalizer!(ir::IRCode, argexprs::Vector{Any}, idx::Int, @nospecialize(case)) + if isa(case, ConstantCase) + # No code in the function - Nothing to do + return true + elseif isa(case, InvokeCase) + insert_node!(ir, idx, NewInstruction(Expr(:invoke, case.invoke, argexprs...), Nothing), true) + return true + elseif case === nothing + @assert false "this case should never happen" end - src = inlining_policy(inlining.interp, src, IR_FLAG_NULL, mi, Any[]) - src === nothing && return false - src = retrieve_ir_for_inlining(mi, src) + # now try to inline the finalizer body + (; mi, spec) = case::InliningTodo + src = (spec::ResolvedInliningSpec).ir # For now: Require finalizer to only have one basic block length(src.cfg.blocks) == 1 || return false # Ok, we're committed to inlining the finalizer - inlining.et !== nothing && push!(inlining.et, mi) linetable_offset, extra_coverage_line = ir_inline_linetable!(ir.linetable, src, mi.def, ir[SSAValue(idx)][:line]) if extra_coverage_line != 0 insert_node!(ir, idx, NewInstruction(Expr(:code_coverage_effect), Nothing, extra_coverage_line)) end - # TODO: Use the actual inliner here rather than open coding this special - # purpose inliner. + # TODO: Use the actual inliner here rather than open coding this special purpose inliner. spvals = mi.sparam_vals ssa_rename = Vector{Any}(undef, length(src.stmts)) for idx′ = 1:length(src.stmts) @@ -995,7 +991,54 @@ function try_inline_finalizer!(ir::IRCode, argexprs::Vector{Any}, idx::Int, mi:: end is_nothrow(ir::IRCode, pc::Int) = ir.stmts[pc][:flag] & (IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW) ≠ 0 -function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse}}, used_ssas::Vector{Int}, lazydomtree::LazyDomtree, inlining::Union{Nothing, InliningState}) + +function try_inline_finalizer!(ir::IRCode, idx::Int, finalizer_idx::Int, defuse::SSADefUse) + # For now: Require that all uses and defs are in the same basic block, + # so that live range calculations are easy. + bb = ir.cfg.blocks[block_for_inst(ir.cfg, first(defuse.uses).idx)] + minval::Int = typemax(Int) + maxval::Int = 0 + + function check_in_range(x::Union{Int,SSAUse}) + if isa(x, SSAUse) + didx = x.idx + else + didx = x + end + didx in bb.stmts || return false + if didx < minval + minval = didx + end + if didx > maxval + maxval = didx + end + return true + end + + check_in_range(idx) || return nothing + _all(check_in_range, defuse.uses) || return nothing + _all(check_in_range, defuse.defs) || return nothing + + # For now: Require all statements in the basic block range to be nothrow. + _all(minval:maxval) do idx::Int + return is_nothrow(ir, idx) || idx == finalizer_idx + end || return nothing + + # Ok, finalizer rewrite is legal. + finalizer_stmt = ir[SSAValue(finalizer_idx)][:inst] + argexprs = Any[finalizer_stmt.args[2], finalizer_stmt.args[3]] + case = finalizer_stmt.args[4] + if inline_finalizer!(ir, argexprs, maxval, case) + # Erase call to finalizer + ir[SSAValue(finalizer_idx)][:inst] = nothing + else + pop!(finalizer_stmt.args) + @assert length(finalizer_stmt.args) == 3 + end + return nothing +end + +function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse}}, used_ssas::Vector{Int}, lazydomtree::LazyDomtree) for (idx, (intermediaries, defuse)) in defuses intermediaries = collect(intermediaries) # Check if there are any uses we did not account for. If so, the variable @@ -1018,72 +1061,22 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse # error at runtime, but is not illegal to have in the IR. ismutabletype(typ) || continue typ = typ::DataType - # First check for any add_finalizer calls - add_finalizer_idx = nothing + # First check for any finalizer calls + finalizer_idx = nothing for use in defuse.uses - if use.kind === :add_finalizer - # For now: Only allow one add_finalizer per allocation - add_finalizer_idx !== nothing && @goto skip - add_finalizer_idx = use.idx + if use.kind === :finalizer + # For now: Only allow one finalizer per allocation + finalizer_idx !== nothing && @goto skip + finalizer_idx = use.idx end end - if add_finalizer_idx !== nothing - # For now: Require that all uses and defs are in the same basic block, - # so that live range calculations are easy. - bb = ir.cfg.blocks[block_for_inst(ir.cfg, first(defuse.uses).idx)] - minval::Int = typemax(Int) - maxval::Int = 0 - - check_in_range(defuse) = check_in_range(defuse.idx) - function check_in_range(didx::Int) - didx in bb.stmts || return false - if didx < minval - minval = didx - end - if didx > maxval - maxval = didx - end - return true - end - - check_in_range(idx) || continue - _all(check_in_range, defuse.uses) || continue - _all(check_in_range, defuse.defs) || continue - - # For now: Require all statements in the basic block range to be - # nothrow. - all_nothrow = _all(idx->is_nothrow(ir, idx) || idx == add_finalizer_idx, minval:maxval) - all_nothrow || continue - - # Ok, finalizer rewrite is legal. - add_finalizer_stmt = ir[SSAValue(add_finalizer_idx)][:inst] - argexprs = Any[add_finalizer_stmt.args[2], add_finalizer_stmt.args[3]] - may_inline = add_finalizer_stmt.args[4]::Bool - mi = add_finalizer_stmt.args[5]::Union{MethodInstance, Nothing} - if may_inline && mi !== nothing - if try_inline_finalizer!(ir, argexprs, maxval, add_finalizer_stmt.args[5], inlining) - @goto done_finalizer - end - mi = compileable_specialization(inlining.et, mi, Effects()).invoke - end - if mi !== nothing - insert_node!(ir, maxval, - NewInstruction(Expr(:invoke, mi, argexprs...), Nothing), - true) - else - insert_node!(ir, maxval, - NewInstruction(Expr(:call, argexprs...), Nothing), - true) - end - @label done_finalizer - # Erase call to add_finalizer - ir[SSAValue(add_finalizer_idx)][:inst] = nothing + if finalizer_idx !== nothing + try_inline_finalizer!(ir, idx, finalizer_idx, defuse) continue end # Partition defuses by field fielddefuse = SSADefUse[SSADefUse() for _ = 1:fieldcount(typ)] all_eliminated = all_forwarded = true - has_finalizer = false for use in defuse.uses if use.kind === :preserve for du in fielddefuse diff --git a/base/compiler/ssair/slot2ssa.jl b/base/compiler/ssair/slot2ssa.jl index 7d534e5bd647a..6561422cb4f65 100644 --- a/base/compiler/ssair/slot2ssa.jl +++ b/base/compiler/ssair/slot2ssa.jl @@ -481,7 +481,7 @@ function domsort_ssa!(ir::IRCode, domtree::DomTree) result[inst_range[end]][:inst] = GotoIfNot(terminator.cond, bb_rename[terminator.dest]) elseif !isa(terminator, ReturnNode) if isa(terminator, Expr) - if terminator.head == :enter + if terminator.head === :enter terminator.args[1] = bb_rename[terminator.args[1]] end end diff --git a/base/experimental.jl b/base/experimental.jl index 174d532ad1f4d..cc8d368023b49 100644 --- a/base/experimental.jl +++ b/base/experimental.jl @@ -155,7 +155,7 @@ the MethodTable). """ macro max_methods(n::Int, fdef::Expr) 0 < n <= 255 || error("We must have that `1 <= max_methods <= 255`, but `max_methods = $n`.") - (fdef.head == :function && length(fdef.args) == 1) || error("Second argument must be a function forward declaration") + (fdef.head === :function && length(fdef.args) == 1) || error("Second argument must be a function forward declaration") return :(typeof($(esc(fdef))).name.max_methods = $(UInt8(n))) end diff --git a/base/iterators.jl b/base/iterators.jl index 40fad992958d5..36f841eb33d4f 100644 --- a/base/iterators.jl +++ b/base/iterators.jl @@ -1380,6 +1380,7 @@ else end end +Stateful(x::Stateful) = x convert(::Type{Stateful}, itr) = Stateful(itr) @inline isdone(s::Stateful, st=nothing) = s.nextvalstate === nothing diff --git a/base/meta.jl b/base/meta.jl index cf59d3fa3274e..c9bad2bb8a4a5 100644 --- a/base/meta.jl +++ b/base/meta.jl @@ -96,7 +96,7 @@ rather than line 2 where `@test` is used as an implementation detail. """ function replace_sourceloc!(sourceloc, @nospecialize(ex)) if ex isa Expr - if ex.head == :macrocall + if ex.head === :macrocall ex.args[2] = sourceloc end map!(e -> replace_sourceloc!(sourceloc, e), ex.args, ex.args) diff --git a/base/reflection.jl b/base/reflection.jl index 644714c8440cb..ad28a617f4c6b 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1642,7 +1642,7 @@ function bodyfunction(basemethod::Method) f = nothing if isa(ast, Core.CodeInfo) && length(ast.code) >= 2 callexpr = ast.code[end-1] - if isa(callexpr, Expr) && callexpr.head == :call + if isa(callexpr, Expr) && callexpr.head === :call fsym = callexpr.args[1] if isa(fsym, Symbol) f = getfield(fmod, fsym) diff --git a/base/show.jl b/base/show.jl index 9841d34efe88b..5885c1f1a62c6 100644 --- a/base/show.jl +++ b/base/show.jl @@ -1197,7 +1197,7 @@ function sourceinfo_slotnames(src::CodeInfo) names = Dict{String,Int}() printnames = Vector{String}(undef, length(slotnames)) for i in eachindex(slotnames) - if slotnames[i] == :var"#unused#" + if slotnames[i] === :var"#unused#" printnames[i] = "_" continue end @@ -2021,7 +2021,7 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In # other call-like expressions ("A[1,2]", "T{X,Y}", "f.(X,Y)") elseif haskey(expr_calls, head) && nargs >= 1 # :ref/:curly/:calldecl/:(.) funcargslike = head === :(.) ? (args[2]::Expr).args : args[2:end] - show_call(head == :ref ? IOContext(io, beginsym=>true) : io, head, args[1], funcargslike, indent, quote_level, head !== :curly) + show_call(head === :ref ? IOContext(io, beginsym=>true) : io, head, args[1], funcargslike, indent, quote_level, head !== :curly) # comprehensions elseif head === :typed_comprehension && nargs == 2 diff --git a/base/threadingconstructs.jl b/base/threadingconstructs.jl index b00dfb389ce3b..0852fafe192ec 100644 --- a/base/threadingconstructs.jl +++ b/base/threadingconstructs.jl @@ -26,9 +26,9 @@ function nthreads end nthreads() = Int(unsafe_load(cglobal(:jl_n_threads, Cint))) function nthreads(pool::Symbol) - if pool == :default + if pool === :default tpid = Int8(0) - elseif pool == :interactive + elseif pool === :interactive tpid = Int8(1) else error("invalid threadpool specified") diff --git a/stdlib/Distributed/src/managers.jl b/stdlib/Distributed/src/managers.jl index 7b048807eddae..8dd833197c951 100644 --- a/stdlib/Distributed/src/managers.jl +++ b/stdlib/Distributed/src/managers.jl @@ -281,7 +281,7 @@ function launch_on_machine(manager::SSHManager, machine::AbstractString, cnt, pa end # Julia process with passed in command line flag arguments - if shell == :posix + if shell === :posix # ssh connects to a POSIX shell cmds = "exec $(shell_escape_posixly(exename)) $(shell_escape_posixly(exeflags))" @@ -297,7 +297,7 @@ function launch_on_machine(manager::SSHManager, machine::AbstractString, cnt, pa # shell login (-l) with string command (-c) to launch julia process remotecmd = shell_escape_posixly(`sh -l -c $cmds`) - elseif shell == :csh + elseif shell === :csh # ssh connects to (t)csh remotecmd = "exec $(shell_escape_csh(exename)) $(shell_escape_csh(exeflags))" @@ -313,7 +313,7 @@ function launch_on_machine(manager::SSHManager, machine::AbstractString, cnt, pa remotecmd = "cd $(shell_escape_csh(dir))\n$remotecmd" end - elseif shell == :wincmd + elseif shell === :wincmd # ssh connects to Windows cmd.exe any(c -> c == '"', exename) && throw(ArgumentError("invalid exename")) diff --git a/stdlib/InteractiveUtils/src/clipboard.jl b/stdlib/InteractiveUtils/src/clipboard.jl index 7bc718b91b2bd..db1142344b24c 100644 --- a/stdlib/InteractiveUtils/src/clipboard.jl +++ b/stdlib/InteractiveUtils/src/clipboard.jl @@ -51,7 +51,7 @@ elseif Sys.islinux() || Sys.KERNEL === :FreeBSD _clipboardcmd !== nothing && return _clipboardcmd for cmd in (:xclip, :xsel, :wlclipboard) # wl-clipboard ships wl-copy/paste individually - c = cmd == :wlclipboard ? Symbol("wl-copy") : cmd + c = cmd === :wlclipboard ? Symbol("wl-copy") : cmd success(pipeline(`which $c`, devnull)) && return _clipboardcmd = cmd end pkgs = @static if Sys.KERNEL === :FreeBSD @@ -83,7 +83,7 @@ elseif Sys.iswindows() x_u16 = Base.cwstring(x) pdata = Ptr{UInt16}(C_NULL) function cleanup(cause) - errno = cause == :success ? UInt32(0) : Libc.GetLastError() + errno = cause === :success ? UInt32(0) : Libc.GetLastError() if cause !== :OpenClipboard if cause !== :success && pdata != C_NULL ccall((:GlobalFree, "kernel32"), stdcall, Cint, (Ptr{UInt16},), pdata) diff --git a/stdlib/LinearAlgebra/src/lbt.jl b/stdlib/LinearAlgebra/src/lbt.jl index 7648157a01a7d..f8fbd7f526ccb 100644 --- a/stdlib/LinearAlgebra/src/lbt.jl +++ b/stdlib/LinearAlgebra/src/lbt.jl @@ -159,9 +159,9 @@ function Base.show(io::IO, mime::MIME{Symbol("text/plain")}, lbt::LBTConfig) println(io, "Libraries: ") for (i,l) in enumerate(lbt.loaded_libs) char = i == length(lbt.loaded_libs) ? "└" : "├" - interface_str = if l.interface == :ilp64 + interface_str = if l.interface === :ilp64 "ILP64" - elseif l.interface == :lp64 + elseif l.interface === :lp64 " LP64" else "UNKWN" diff --git a/stdlib/LinearAlgebra/src/qr.jl b/stdlib/LinearAlgebra/src/qr.jl index 34914f2f6511c..d562adf935770 100644 --- a/stdlib/LinearAlgebra/src/qr.jl +++ b/stdlib/LinearAlgebra/src/qr.jl @@ -314,9 +314,9 @@ julia> a = [1. 2.; 3. 4.] 3.0 4.0 julia> qr!(a) -QRCompactWY{Float64, Matrix{Float64}, Matrix{Float64}} +LinearAlgebra.QRCompactWY{Float64, Matrix{Float64}, Matrix{Float64}} Q factor: -2×2 QRCompactWYQ{Float64, Matrix{Float64}, Matrix{Float64}}: +2×2 LinearAlgebra.QRCompactWYQ{Float64, Matrix{Float64}, Matrix{Float64}}: -0.316228 -0.948683 -0.948683 0.316228 R factor: @@ -401,9 +401,9 @@ julia> A = [3.0 -6.0; 4.0 -8.0; 0.0 1.0] 0.0 1.0 julia> F = qr(A) -QRCompactWY{Float64, Matrix{Float64}, Matrix{Float64}} +LinearAlgebra.QRCompactWY{Float64, Matrix{Float64}, Matrix{Float64}} Q factor: -3×3 QRCompactWYQ{Float64, Matrix{Float64}, Matrix{Float64}}: +3×3 LinearAlgebra.QRCompactWYQ{Float64, Matrix{Float64}, Matrix{Float64}}: -0.6 0.0 0.8 -0.8 0.0 -0.6 0.0 -1.0 0.0 diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index 593f265eba3fa..12a44b9acda9a 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -242,7 +242,7 @@ function print(io::IO, tasks::Union{UInt,AbstractVector{UInt}} = typemin(UInt):typemax(UInt)) pf = ProfileFormat(;C, combine, maxdepth, mincount, noisefloor, sortedby, recur) - if groupby == :none + if groupby === :none print(io, data, lidict, pf, format, threads, tasks, false) else if !in(groupby, [:thread, :task, [:task, :thread], [:thread, :task]]) @@ -285,7 +285,7 @@ function print(io::IO, end end end - elseif groupby == :task + elseif groupby === :task threads = 1:typemax(Int) for taskid in intersect(get_task_ids(data), tasks) printstyled(io, "Task $(Base.repr(taskid)) "; bold=true, color=Base.debug_color()) @@ -293,7 +293,7 @@ function print(io::IO, nosamples && (any_nosamples = true) println(io) end - elseif groupby == :thread + elseif groupby === :thread tasks = 1:typemax(UInt) for threadid in intersect(get_thread_ids(data), threads) printstyled(io, "Thread $threadid "; bold=true, color=Base.info_color()) diff --git a/stdlib/REPL/src/LineEdit.jl b/stdlib/REPL/src/LineEdit.jl index b30a1d816a83f..0d00063b5c880 100644 --- a/stdlib/REPL/src/LineEdit.jl +++ b/stdlib/REPL/src/LineEdit.jl @@ -1323,9 +1323,9 @@ end function edit_input(s, f = (filename, line, column) -> InteractiveUtils.edit(filename, line, column)) mode_name = guess_current_mode_name(s) filename = tempname() - if mode_name == :julia + if mode_name === :julia filename *= ".jl" - elseif mode_name == :shell + elseif mode_name === :shell filename *= ".sh" end buf = buffer(s) diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index d3d0c9bc98582..9ef9d6b8a6f26 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -179,8 +179,8 @@ function check_for_missing_packages_and_run_hooks(ast) end function modules_to_be_loaded(ast::Expr, mods::Vector{Symbol} = Symbol[]) - ast.head == :quote && return mods # don't search if it's not going to be run during this eval - if ast.head in [:using, :import] + ast.head === :quote && return mods # don't search if it's not going to be run during this eval + if ast.head === :using || ast.head === :import for arg in ast.args arg = arg::Expr arg1 = first(arg.args) diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index 6e4132aaab1cd..fcc571d8a44ef 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -1323,7 +1323,7 @@ fake_repl() do stdin_write, stdout_read, repl # necessary to read at least some part of the buffer, # for the "region_active" to have time to be updated - @test LineEdit.state(repl.mistate).region_active == :off + @test LineEdit.state(repl.mistate).region_active === :off @test s4 == "anything" # no control characters between the last two occurrences of "anything" write(stdin_write, "\x15\x04") Base.wait(repltask) diff --git a/test/compiler/inline.jl b/test/compiler/inline.jl index 044ba03bacf32..9ad499d01598b 100644 --- a/test/compiler/inline.jl +++ b/test/compiler/inline.jl @@ -1300,7 +1300,6 @@ mutable struct DoAllocNoEscape end end end - let src = code_typed1() do for i = 1:1000 DoAllocNoEscape() @@ -1309,6 +1308,22 @@ let src = code_typed1() do @test count(isnew, src.code) == 0 end +# Test that we can inline a finalizer that just returns a constant value +mutable struct DoAllocConst + function DoAllocConst() + finalizer(new()) do this + return nothing + end + end +end +let src = code_typed1() do + for i = 1:1000 + DoAllocConst() + end + end + @test count(isnew, src.code) == 0 +end + # Test that finalizer elision doesn't cause a throw to be inlined into a function # that shouldn't have it const finalizer_should_throw = Ref{Bool}(true) @@ -1334,7 +1349,6 @@ end @test f_finalizer_throws() # Test finalizers with static parameters -global last_finalizer_type::Type = Any mutable struct DoAllocNoEscapeSparam{T} x::T function finalizer_sparam(d::DoAllocNoEscapeSparam{T}) where {T} @@ -1346,7 +1360,6 @@ mutable struct DoAllocNoEscapeSparam{T} end end DoAllocNoEscapeSparam(x::T) where {T} = DoAllocNoEscapeSparam{T}(x) - let src = code_typed1(Tuple{Any}) do x for i = 1:1000 DoAllocNoEscapeSparam(x) @@ -1366,7 +1379,6 @@ mutable struct DoAllocNoEscapeNoInline finalizer(noinline_finalizer, new()) end end - let src = code_typed1() do for i = 1:1000 DoAllocNoEscapeNoInline() @@ -1376,6 +1388,30 @@ let src = code_typed1() do @test count(isinvoke(:noinline_finalizer), src.code) == 1 end +# Test that we don't form invalid `finalizer` call for cases we don't handle currently +mutable struct DoAllocNoEscapeBranch + val::Int + function DoAllocNoEscapeBranch(val::Int) + finalizer(new(val)) do this + if this.val > 500 + nothrow_side_effect(this.val) + else + nothrow_side_effect(nothing) + end + end + end +end +let src = code_typed1() do + for i = 1:1000 + DoAllocNoEscapeBranch(i) + end + end + @test any(src.code) do @nospecialize x + iscall((src, Core.finalizer))(x) && + length(x.args) == 3 + end +end + # optimize `[push!|pushfirst!](::Vector{Any}, x...)` @testset "optimize `$f(::Vector{Any}, x...)`" for f = Any[push!, pushfirst!] @eval begin diff --git a/test/iterators.jl b/test/iterators.jl index 453f27ca8885c..0258de116caa3 100644 --- a/test/iterators.jl +++ b/test/iterators.jl @@ -326,6 +326,8 @@ let itr @test collect(itr) == Int[] # Stateful do not preserve shape itr = (i-1 for i in Base.Stateful(zeros(Int, 0, 0))) @test collect(itr) == Int[] # Stateful do not preserve shape + itr = Iterators.Stateful(Iterators.Stateful(1:1)) + @test collect(itr) == [1] end # with 1D inputs