diff --git a/Makefile b/Makefile index 855795f114cab..e4891565c5a8e 100644 --- a/Makefile +++ b/Makefile @@ -1,20 +1,13 @@ JULIAHOME := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) include $(JULIAHOME)/Make.inc -# TODO: Code bundled with Julia should be installed into a versioned directory, -# prefix/share/julia/VERSDIR, so that in the future one can have multiple -# major versions of Julia installed concurrently. Third-party code that -# is not controlled by Pkg should be installed into -# prefix/share/julia/site/VERSDIR (not prefix/share/julia/VERSDIR/site ... -# so that prefix/share/julia/VERSDIR can be overwritten without touching -# third-party code). VERSDIR := v`cut -d. -f1-2 < $(JULIAHOME)/VERSION` default: $(JULIA_BUILD_MODE) # contains either "debug" or "release" all: debug release # sort is used to remove potential duplicates -DIRS := $(sort $(build_bindir) $(build_depsbindir) $(build_libdir) $(build_private_libdir) $(build_libexecdir) $(build_includedir) $(build_includedir)/julia $(build_sysconfdir)/julia $(build_datarootdir)/julia $(build_datarootdir)/julia/site $(build_man1dir)) +DIRS := $(sort $(build_bindir) $(build_depsbindir) $(build_libdir) $(build_private_libdir) $(build_libexecdir) $(build_includedir) $(build_includedir)/julia $(build_sysconfdir)/julia $(build_datarootdir)/julia $(build_datarootdir)/julia/stdlib $(build_man1dir)) ifneq ($(BUILDROOT),$(JULIAHOME)) BUILDDIRS := $(BUILDROOT) $(addprefix $(BUILDROOT)/,base src ui doc deps test test/embedding) BUILDDIRMAKE := $(addsuffix /Makefile,$(BUILDDIRS)) @@ -46,8 +39,8 @@ endif $(foreach dir,$(DIRS),$(eval $(call dir_target,$(dir)))) $(foreach link,base $(JULIAHOME)/test,$(eval $(call symlink_target,$(link),$(build_datarootdir)/julia,$(notdir $(link))))) -build_defaultpkgdir = $(build_datarootdir)/julia/site/$(shell echo $(VERSDIR)) -$(eval $(call symlink_target,$(JULIAHOME)/stdlib,$(build_datarootdir)/julia/site,$(shell echo $(VERSDIR)))) +build_defaultpkgdir = $(build_datarootdir)/julia/stdlib/$(shell echo $(VERSDIR)) +$(eval $(call symlink_target,$(JULIAHOME)/stdlib,$(build_datarootdir)/julia/stdlib,$(shell echo $(VERSDIR)))) julia_flisp.boot.inc.phony: julia-deps @$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT)/src julia_flisp.boot.inc.phony @@ -284,7 +277,7 @@ endef install: $(build_depsbindir)/stringreplace $(BUILDROOT)/doc/_build/html/en/index.html @$(MAKE) $(QUIET_MAKE) all - @for subdir in $(bindir) $(datarootdir)/julia/site/$(VERSDIR) $(docdir) $(man1dir) $(includedir)/julia $(libdir) $(private_libdir) $(sysconfdir); do \ + @for subdir in $(bindir) $(datarootdir)/julia/stdlib/$(VERSDIR) $(docdir) $(man1dir) $(includedir)/julia $(libdir) $(private_libdir) $(sysconfdir); do \ mkdir -p $(DESTDIR)$$subdir; \ done diff --git a/base/Enums.jl b/base/Enums.jl index 0cec49e77a7ad..e0423496cc6f0 100644 --- a/base/Enums.jl +++ b/base/Enums.jl @@ -75,7 +75,7 @@ macro enum(T, syms...) if isa(T, Expr) && T.head == :(::) && length(T.args) == 2 && isa(T.args[1], Symbol) typename = T.args[1] basetype = eval(__module__, T.args[2]) - if !isa(basetype, DataType) || !(basetype <: Integer) || !isbits(basetype) + if !isa(basetype, DataType) || !(basetype <: Integer) || !isbitstype(basetype) throw(ArgumentError("invalid base type for Enum $typename, $T=::$basetype; base type must be an integer primitive type")) end elseif !isa(T, Symbol) diff --git a/base/abstractdict.jl b/base/abstractdict.jl index 07ed4dd24fb0d..040b6e07d5ac4 100644 --- a/base/abstractdict.jl +++ b/base/abstractdict.jl @@ -672,11 +672,11 @@ _oidd_nextind(a, i) = reinterpret(Int, ccall(:jl_eqtable_nextind, Csize_t, (Any, start(d::IdDict) = _oidd_nextind(d.ht, 0) done(d::IdDict, i) = (i == -1) -next(d::IdDict{K,V}, i) where {K, V} = (Pair{K,V}(d.ht[i+1], d.ht[i+2]), _oidd_nextind(d.ht, i+2)) +next(d::IdDict{K, V}, i) where {K, V} = (Pair{K, V}(d.ht[i + 1]::K, d.ht[i + 2]::V), _oidd_nextind(d.ht, i + 2)) length(d::IdDict) = d.count -copy(d::IdDict) = IdDict(d) +copy(d::IdDict) = typeof(d)(d) get!(d::IdDict{K,V}, @nospecialize(key), @nospecialize(default)) where {K, V} = (d[key] = get(d, key, default))::V @@ -689,14 +689,14 @@ mutable struct IdSet{T} <: AbstractSet{T} dict::IdDict{T,Nothing} IdSet{T}() where {T} = new(IdDict{T,Nothing}()) - IdSet{T}(s::IdSet{T}) where {T} = new(IdDict{T,Nothing}(s.dict)) + IdSet{T}(s::IdSet{T}) where {T} = new(copy(s.dict)) end IdSet{T}(itr) where {T} = union!(IdSet{T}(), itr) IdSet() = IdSet{Any}() -copy(s::IdSet{T}) where {T} = IdSet{T}(s) -copymutable(s::IdSet{T}) where {T} = IdSet{T}(s) +copymutable(s::IdSet) = typeof(s)(s) +copy(s::IdSet) = typeof(s)(s) isempty(s::IdSet) = isempty(s.dict) length(s::IdSet) = length(s.dict) diff --git a/base/array.jl b/base/array.jl index d4c0dd15294e8..2f6956960a6fb 100644 --- a/base/array.jl +++ b/base/array.jl @@ -85,7 +85,6 @@ UInt8 ``` """ eltype(::Type) = Any -eltype(::Type{Any}) = Any eltype(::Type{Bottom}) = throw(ArgumentError("Union{} does not have elements")) eltype(x) = eltype(typeof(x)) @@ -117,7 +116,7 @@ asize_from(a::Array, n) = n > ndims(a) ? () : (arraysize(a,n), asize_from(a, n+1 """ Base.isbitsunion(::Type{T}) -Return whether a type is an "is-bits" Union type, meaning each type included in a Union is `isbits`. +Return whether a type is an "is-bits" Union type, meaning each type included in a Union is `isbitstype`. """ isbitsunion(u::Union) = ccall(:jl_array_store_unboxed, Cint, (Any,), u) == Cint(1) isbitsunion(x) = false @@ -125,7 +124,7 @@ isbitsunion(x) = false """ Base.bitsunionsize(U::Union) -For a Union of `isbits` types, return the size of the largest type; assumes `Base.isbitsunion(U) == true` +For a Union of `isbitstype` types, return the size of the largest type; assumes `Base.isbitsunion(U) == true` """ function bitsunionsize(u::Union) sz = Ref{Csize_t}(0) @@ -135,7 +134,7 @@ function bitsunionsize(u::Union) end length(a::Array) = arraylen(a) -elsize(::Type{<:Array{T}}) where {T} = isbits(T) ? sizeof(T) : (isbitsunion(T) ? bitsunionsize(T) : sizeof(Ptr)) +elsize(::Type{<:Array{T}}) where {T} = isbitstype(T) ? sizeof(T) : (isbitsunion(T) ? bitsunionsize(T) : sizeof(Ptr)) sizeof(a::Array) = Core.sizeof(a) function isassigned(a::Array, i::Int...) @@ -178,7 +177,7 @@ the same manner as C. function unsafe_copyto!(dest::Array{T}, doffs, src::Array{T}, soffs, n) where T t1 = @_gc_preserve_begin dest t2 = @_gc_preserve_begin src - if isbits(T) + if isbitstype(T) unsafe_copyto!(pointer(dest, doffs), pointer(src, soffs), n) elseif isbitsunion(T) ccall(:memmove, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, UInt), @@ -1478,7 +1477,7 @@ function vcat(arrays::Vector{T}...) where T end arr = Vector{T}(undef, n) ptr = pointer(arr) - if isbits(T) + if isbitstype(T) elsz = Core.sizeof(T) elseif isbitsunion(T) elsz = bitsunionsize(T) @@ -1490,7 +1489,7 @@ function vcat(arrays::Vector{T}...) where T for a in arrays na = length(a) nba = na * elsz - if isbits(T) + if isbitstype(T) ccall(:memcpy, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, UInt), ptr, a, nba) elseif isbitsunion(T) diff --git a/base/arraymath.jl b/base/arraymath.jl index 383f6cdac22fb..d86dff975fe96 100644 --- a/base/arraymath.jl +++ b/base/arraymath.jl @@ -94,7 +94,7 @@ function reverse(A::Array{T}; dims::Integer) where T end end else - if isbits(T) && M>200 + if isbitstype(T) && M>200 for i = 1:sd ri = sd+1-i for j=0:stride:(N-stride) diff --git a/base/atomics.jl b/base/atomics.jl index b9a3289b68944..cb7b2259168b8 100644 --- a/base/atomics.jl +++ b/base/atomics.jl @@ -26,10 +26,12 @@ else UInt8, UInt16, UInt32, UInt64, UInt128) end const floattypes = (Float16, Float32, Float64) -# TODO: Support Bool, Ptr -const atomictypes = (inttypes..., floattypes...) +const arithmetictypes = (inttypes..., floattypes...) +# TODO: Support Ptr +const atomictypes = (arithmetictypes..., Bool) const IntTypes = Union{inttypes...} const FloatTypes = Union{floattypes...} +const ArithmeticTypes = Union{arithmetictypes...} const AtomicTypes = Union{atomictypes...} """ @@ -39,8 +41,8 @@ Holds a reference to an object of type `T`, ensuring that it is only accessed atomically, i.e. in a thread-safe manner. Only certain "simple" types can be used atomically, namely the -primitive integer and float-point types. These are `Int8`...`Int128`, -`UInt8`...`UInt128`, and `Float16`...`Float64`. +primitive boolean, integer, and float-point types. These are `Bool`, +`Int8`...`Int128`, `UInt8`...`UInt128`, and `Float16`...`Float64`. New atomic objects can be created from a non-atomic values; if none is specified, the atomic object is initialized with zero. @@ -130,11 +132,12 @@ julia> x[] function atomic_xchg! end """ - Threads.atomic_add!(x::Atomic{T}, val::T) where T + Threads.atomic_add!(x::Atomic{T}, val::T) where T <: ArithmeticTypes Atomically add `val` to `x` -Performs `x[] += val` atomically. Returns the **old** value. +Performs `x[] += val` atomically. Returns the **old** value. Not defined for +`Atomic{Bool}`. For further details, see LLVM's `atomicrmw add` instruction. @@ -153,11 +156,12 @@ julia> x[] function atomic_add! end """ - Threads.atomic_sub!(x::Atomic{T}, val::T) where T + Threads.atomic_sub!(x::Atomic{T}, val::T) where T <: ArithmeticTypes Atomically subtract `val` from `x` -Performs `x[] -= val` atomically. Returns the **old** value. +Performs `x[] -= val` atomically. Returns the **old** value. Not defined for +`Atomic{Bool}`. For further details, see LLVM's `atomicrmw sub` instruction. @@ -317,7 +321,7 @@ unsafe_convert(::Type{Ptr{T}}, x::Atomic{T}) where {T} = convert(Ptr{T}, pointer setindex!(x::Atomic{T}, v) where {T} = setindex!(x, convert(T, v)) const llvmtypes = IdDict{Any,String}( - Bool => "i1", + Bool => "i8", # julia represents bools with 8-bits for now. # TODO: is this okay? Int8 => "i8", UInt8 => "i8", Int16 => "i16", UInt16 => "i16", Int32 => "i32", UInt32 => "i32", @@ -380,13 +384,15 @@ for typ in atomictypes unsafe_convert(Ptr{$typ}, x), cmp, new) end - for rmwop in [:xchg, :add, :sub, :and, :nand, :or, :xor, :max, :min] + arithmetic_ops = [:add, :sub] + for rmwop in [arithmetic_ops..., :xchg, :and, :nand, :or, :xor, :max, :min] rmw = string(rmwop) fn = Symbol("atomic_", rmw, "!") if (rmw == "max" || rmw == "min") && typ <: Unsigned # LLVM distinguishes signedness in the operation, not the integer type. rmw = "u" * rmw end + if rmwop in arithmetic_ops && !(typ <: ArithmeticTypes) continue end if typ <: Integer @eval $fn(x::Atomic{$typ}, v::$typ) = llvmcall($""" diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index f3a8f98370c70..68299eb5385cb 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -187,6 +187,7 @@ function abstract_call_method(method::Method, @nospecialize(sig), sparams::Simpl cyclei = 0 infstate = sv edgecycle = false + method2 = method_for_inference_heuristics(method, sig, sparams, sv.params.world) # Union{Method, Nothing} while !(infstate === nothing) infstate = infstate::InferenceState if method === infstate.linfo.def @@ -197,7 +198,9 @@ function abstract_call_method(method::Method, @nospecialize(sig), sparams::Simpl edgecycle = true break end - if topmost === nothing + inf_method2 = infstate.src.method_for_inference_limit_heuristics # limit only if user token match + inf_method2 isa Method || (inf_method2 = nothing) # Union{Method, Nothing} + if topmost === nothing && method2 === inf_method2 # inspect the parent of this edge, # to see if they are the same Method as sv # in which case we'll need to ensure it is convergent diff --git a/base/compiler/compiler.jl b/base/compiler/compiler.jl index 6ed61657292b0..80879dd66b5d8 100644 --- a/base/compiler/compiler.jl +++ b/base/compiler/compiler.jl @@ -103,6 +103,12 @@ using .Sort inlining_enabled() = (JLOptions().can_inline == 1) coverage_enabled() = (JLOptions().code_coverage != 0) +function inbounds_option() + opt_check_bounds = JLOptions().check_bounds + opt_check_bounds == 0 && return :default + opt_check_bounds == 1 && return :on + return :off +end include("compiler/utilities.jl") include("compiler/validation.jl") diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index 13828cf0a4bf8..eb213270fe0ad 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -218,6 +218,9 @@ const SLOT_USEDUNDEF = 32 # slot has uses that might raise UndefVarError # const SLOT_CALLED = 64 +const IR_FLAG_INBOUNDS = 0x01 + + # known affect-free calls (also effect-free) const _PURE_BUILTINS = Any[tuple, svec, fieldtype, apply_type, ===, isa, typeof, UnionAll, nfields] @@ -263,7 +266,7 @@ function isinlineable(m::Method, src::CodeInfo, mod::Module, params::Params, bon return inlineable end -const enable_new_optimizer = RefValue(true) +const enable_new_optimizer = RefValue(false) # converge the optimization work function optimize(me::InferenceState) @@ -396,7 +399,7 @@ function optimize(me::InferenceState) me.src.inlineable = false elseif !me.src.inlineable && isa(def, Method) bonus = 0 - if me.bestguess ⊑ Tuple && !isbits(widenconst(me.bestguess)) + if me.bestguess ⊑ Tuple && !isbitstype(widenconst(me.bestguess)) bonus = me.params.inline_tupleret_bonus end me.src.inlineable = isinlineable(def, me.src, me.mod, me.params, bonus) @@ -706,7 +709,7 @@ end # since codegen optimizations of functions like `is` will depend on knowing it function widen_slot_type(@nospecialize(ty), untypedload::Bool) if isa(ty, DataType) - if untypedload || isbits(ty) || isdefined(ty, :instance) + if untypedload || isbitstype(ty) || isdefined(ty, :instance) return ty end elseif isa(ty, Union) @@ -1154,6 +1157,7 @@ function inlineable(@nospecialize(f), @nospecialize(ft), e::Expr, atypes::Vector f === Core.sizeof || f === isdefined || istopfunction(topmod, f, :typejoin) || istopfunction(topmod, f, :isbits) || + istopfunction(topmod, f, :isbitstype) || istopfunction(topmod, f, :promote_type) || (f === Core.kwfunc && length(argexprs) == 2) || (is_inlineable_constant(val) && @@ -2712,7 +2716,7 @@ function merge_value_ssa!(ctx::AllocOptContext, info, key) # There are other cases that we can merge # but those require control flow analysis. var_has_static_undef(ctx.sv.src, key.first, key.second) && return false - local defkey + local defkey, deftyp for def in info.defs # No NewvarNode def for variables that aren't used undefined defex = (def.assign::Expr).args[2] @@ -2735,6 +2739,12 @@ function merge_value_ssa!(ctx::AllocOptContext, info, key) else return @isdefined(defkey) end + new_deftyp = exprtype(defex, ctx.sv.src, ctx.sv.mod) + if @isdefined(deftyp) && deftyp != new_deftyp + return true + else + deftyp = new_deftyp + end end if defkey.second || defkey.first > ctx.sv.nargs @@ -2759,8 +2769,18 @@ function merge_value_ssa!(ctx::AllocOptContext, info, key) if defkey.second # SSAValue def replace_v = SSAValue(defkey.first - 1) + # don't replace TypedSlots with SSAValues + replace_typ = exprtype(replace_v, ctx.sv.src, ctx.sv.mod) + if !(replace_typ ⊑ deftyp) + return false + end else replace_v = SlotNumber(defkey.first) + # don't lose type information from TypedSlots + replace_typ = exprtype(replace_v, ctx.sv.src, ctx.sv.mod) + if !(replace_typ ⊑ deftyp) + replace_v = TypedSlot(defkey.first, deftyp) + end end for use in info.uses if isdefined(use, :expr) @@ -3077,7 +3097,7 @@ function structinfo_new(ctx::AllocOptContext, ex::Expr, vt::DataType) si.defs[i] = ex.args[i + 1] else ft = fieldtype(vt, i) - if isbits(ft) + if isbitstype(ft) ex = Expr(:new, ft) ex.typ = ft si.defs[i] = ex @@ -3614,7 +3634,7 @@ function split_struct_alloc_single!(ctx::AllocOptContext, info, key, nf, has_pre if !@isdefined(fld_name) fld_name = :struct_field end - need_preserved_root = has_preserve && !isbits(field_typ) + need_preserved_root = has_preserve && !isbitstype(field_typ) local var_slot if !has_def # If there's no direct use of the field diff --git a/base/compiler/ssair/driver.jl b/base/compiler/ssair/driver.jl index bc93f7a1298ec..cb42408d6973b 100644 --- a/base/compiler/ssair/driver.jl +++ b/base/compiler/ssair/driver.jl @@ -114,26 +114,44 @@ function just_construct_ssa(ci::CodeInfo, code::Vector{Any}, nargs::Int, linetab end idx += 1 end - reindex_labels!(code) + reindex_labels!(code) # update labels changed above + + inbounds_depth = 0 # Number of stacked inbounds meta = Any[] lines = fill(0, length(code)) + flags = fill(0x00, length(code)) let loc = RefValue(1) for i = 1:length(code) stmt = code[i] - stmt = normalize(stmt, meta, linetable, loc) + if isexpr(stmt, :inbounds) + arg1 = stmt.args[1] + if arg1 === true # push + inbounds_depth += 1 + elseif arg1 === false # clear + inbounds_depth = 0 + elseif inbounds_depth > 0 # pop + inbounds_depth -= 1 + end + stmt = nothing + else + stmt = normalize(stmt, meta, linetable, loc) + end code[i] = stmt if !(stmt === nothing) lines[i] = loc[] + if inbounds_depth > 0 + flags[i] |= IR_FLAG_INBOUNDS + end end end end - code = strip_trailing_junk!(code, lines) + code = strip_trailing_junk!(code, lines, flags) cfg = compute_basic_blocks(code) defuse_insts = scan_slot_def_use(nargs, ci, code) @timeit "domtree 1" domtree = construct_domtree(cfg) ir = let code = Any[nothing for _ = 1:length(code)] argtypes = ci.slottypes[1:(nargs+1)] - IRCode(code, Any[], lines, cfg, argtypes, mod, meta) + IRCode(code, Any[], lines, flags, cfg, argtypes, mod, meta) end @timeit "construct_ssa" ir = construct_ssa!(ci, code, ir, domtree, defuse_insts, nargs) return ir diff --git a/base/compiler/ssair/inlining2.jl b/base/compiler/ssair/inlining2.jl index ea872cd036660..085cb2733fb83 100644 --- a/base/compiler/ssair/inlining2.jl +++ b/base/compiler/ssair/inlining2.jl @@ -1,4 +1,20 @@ -const InliningTodo = Tuple{Int, Tuple{Bool, Bool, Bool, Int}, Method, Vector{Any}, Vector{LineInfoNode}, IRCode, Bool} +struct InliningTodo + idx::Int # The statement to replace + # Properties of the call - these determine how arguments + # need to be rewritten. + isva::Bool + isinvoke::Bool + isapply::Bool + na::Int + method::Method # The method being inlined + sparams::Vector{Any} # The static parameters we computed for this call site + # The LineTable and IR of the inlinee + linetable::Vector{LineInfoNode} + ir::IRCode + # If the function being inlined is a single basic block we can use a + # simpler inlining algorithm. This flag determines whether that's allowed + linear_inline_eligible::Bool +end function ssa_inlining_pass!(ir::IRCode, linetable::Vector{LineInfoNode}, sv::OptimizationState) # Go through the function, perfoming simple ininlingin (e.g. replacing call by constants @@ -19,16 +35,22 @@ function batch_inline!(todo::Vector{InliningTodo}, ir::IRCode, linetable::Vector bb_rename = zeros(Int, length(ir.cfg.blocks)) split_targets = BitSet() merged_orig_blocks = BitSet() - for (idx, _a, _b, _c, _d, ir2, lie) in todo + boundscheck = inbounds_option() + if boundscheck === :default && sv.src.propagate_inbounds + boundscheck = :propagate + end + for item in todo + local idx, ir2, lie # A linear inline does not modify the CFG - lie && continue + item.linear_inline_eligible && continue + inlinee_cfg = item.ir.cfg # Figure out if we need to split the BB need_split_before = false need_split = true - block = block_for_inst(ir.cfg, idx) + block = block_for_inst(ir.cfg, item.idx) last_block_idx = last(ir.cfg.blocks[block].stmts) - if !isempty(ir2.cfg.blocks[1].preds) + if !isempty(inlinee_cfg.blocks[1].preds) need_split_before = true end @@ -43,7 +65,7 @@ function batch_inline!(todo::Vector{InliningTodo}, ir::IRCode, linetable::Vector need_split = false post_bb_id = -ir[SSAValue(last_block_idx)].label else - post_bb_id = length(new_cfg_blocks) + length(ir2.cfg.blocks) + (need_split_before ? 1 : 0) + post_bb_id = length(new_cfg_blocks) + length(inlinee_cfg.blocks) + (need_split_before ? 1 : 0) need_split = true #!(idx == last_block_idx) end @@ -57,21 +79,21 @@ function batch_inline!(todo::Vector{InliningTodo}, ir::IRCode, linetable::Vector orig_succs = copy(new_cfg_blocks[end].succs) empty!(new_cfg_blocks[end].succs) if need_split_before - bb_rename_range = (1+length(new_cfg_blocks)):(length(ir2.cfg.blocks)+length(new_cfg_blocks)) + bb_rename_range = (1+length(new_cfg_blocks)):(length(inlinee_cfg.blocks)+length(new_cfg_blocks)) push!(new_cfg_blocks[end].succs, length(new_cfg_blocks)+1) - append!(new_cfg_blocks, ir2.cfg.blocks) + append!(new_cfg_blocks, inlinee_cfg.blocks) else # Merge the last block that was already there with the first block we're adding - bb_rename_range = length(new_cfg_blocks):(length(new_cfg_blocks)+length(ir2.cfg.blocks)-1) - append!(new_cfg_blocks[end].succs, ir2.cfg.blocks[1].succs) - append!(new_cfg_blocks, ir2.cfg.blocks[2:end]) + bb_rename_range = length(new_cfg_blocks):(length(inlinee_cfg.blocks)+length(new_cfg_blocks)-1) + append!(new_cfg_blocks[end].succs, inlinee_cfg.blocks[1].succs) + append!(new_cfg_blocks, inlinee_cfg.blocks[2:end]) end if need_split push!(new_cfg_blocks, BasicBlock(ir.cfg.blocks[block].stmts, Int[], orig_succs)) push!(split_targets, length(new_cfg_blocks)) end - new_block_range = (length(new_cfg_blocks)-length(ir2.cfg.blocks)+1):length(new_cfg_blocks) + new_block_range = (length(new_cfg_blocks)-length(inlinee_cfg.blocks)+1):length(new_cfg_blocks) push!(inserted_block_ranges, new_block_range) # Fixup the edges of the newely added blocks @@ -96,8 +118,8 @@ function batch_inline!(todo::Vector{InliningTodo}, ir::IRCode, linetable::Vector for (old_block, new_block) in enumerate(bb_rename_range) if (length(new_cfg_blocks[new_block].succs) == 0) - terminator_idx = last(ir2.cfg.blocks[old_block].stmts) - terminator = ir2[SSAValue(terminator_idx)] + terminator_idx = last(inlinee_cfg.blocks[old_block].stmts) + terminator = item.ir[SSAValue(terminator_idx)] if isa(terminator, ReturnNode) && isdefined(terminator, :val) push!(new_cfg_blocks[new_block].succs, post_bb_id) if need_split @@ -142,19 +164,20 @@ function batch_inline!(todo::Vector{InliningTodo}, ir::IRCode, linetable::Vector let compact = IncrementalCompact(ir) compact.result_bbs = new_cfg_blocks - nnewnodes = length(compact.result) + (sum(todo) do (_a, _b, _c, _d, _e, ir2, _f) - return length(ir2.stmts) + length(ir2.new_nodes) + nnewnodes = length(compact.result) + (sum(todo) do item + return length(item.ir.stmts) + length(item.ir.new_nodes) end) resize!(compact, nnewnodes) - (inline_idx, (isva, isinvoke, isapply, na), method, spvals, inline_linetable, inline_ir, lie) = popfirst!(todo) + item = popfirst!(todo) + inline_idx = item.idx for (idx, stmt) in compact if compact.idx - 1 == inline_idx # Ok, do the inlining here - inline_cfg = inline_ir.cfg + inline_cfg = item.ir.cfg linetable_offset = length(linetable) # Append the linetable of the inlined function to our line table inlined_at = compact.result_lines[idx] - for entry in inline_linetable + for entry in item.linetable push!(linetable, LineInfoNode(entry.mod, entry.method, entry.file, entry.line, (entry.inlined_at > 0 ? entry.inlined_at + linetable_offset : inlined_at))) end @@ -174,23 +197,29 @@ function batch_inline!(todo::Vector{InliningTodo}, ir::IRCode, linetable::Vector end end argexprs = rewrite_exprargs((node, typ)->insert_node_here!(compact, node, typ, compact.result_lines[idx]), - arg->compact_exprtype(compact, arg), isinvoke, isapply, argexprs) - if isva - vararg = mk_tuplecall!(compact, argexprs[na:end], compact.result_lines[idx]) - argexprs = Any[argexprs[1:(na - 1)]..., vararg] + arg->compact_exprtype(compact, arg), item.isinvoke, item.isapply, argexprs) + if item.isva + vararg = mk_tuplecall!(compact, argexprs[item.na:end], compact.result_lines[idx]) + argexprs = Any[argexprs[1:(item.na - 1)]..., vararg] + end + flag = compact.result_flags[idx] + boundscheck_idx = boundscheck + if boundscheck_idx === :default || boundscheck_idx === :propagate + if (flag & IR_FLAG_INBOUNDS) != 0 + boundscheck_idx = :off + end end # Special case inlining that maintains the current basic block if there's only one BB in the target - if lie - terminator = inline_ir[SSAValue(last(inline_cfg.blocks[1].stmts))] + if item.linear_inline_eligible + terminator = item.ir[SSAValue(last(inline_cfg.blocks[1].stmts))] compact[idx] = nothing - inline_compact = IncrementalCompact(compact, inline_ir, compact.result_idx) + inline_compact = IncrementalCompact(compact, item.ir, compact.result_idx) for (idx′, stmt′) in inline_compact # This dance is done to maintain accurate usage counts in the # face of rename_arguments! mutating in place - should figure out # something better eventually. inline_compact[idx′] = nothing - stmt′ = ssa_substitute!(stmt′, argexprs, method.sig, spvals) - compact.result_lines[idx′] += linetable_offset + stmt′ = ssa_substitute!(idx′, stmt′, argexprs, item.method.sig, item.sparams, linetable_offset, boundscheck_idx, compact) if isa(stmt′, ReturnNode) isa(stmt′.val, SSAValue) && (compact.used_ssas[stmt′.val.id] += 1) compact.ssa_rename[compact.idx-1] = stmt′.val @@ -204,17 +233,16 @@ function batch_inline!(todo::Vector{InliningTodo}, ir::IRCode, linetable::Vector else bb_offset, post_bb_id = popfirst!(todo_bbs) # This implements the need_split_before flag above - need_split_before = !isempty(inline_ir.cfg.blocks[1].preds) + need_split_before = !isempty(item.ir.cfg.blocks[1].preds) if need_split_before finish_current_bb!(compact) end pn = PhiNode() compact[idx] = nothing - inline_compact = IncrementalCompact(compact, inline_ir, compact.result_idx) + inline_compact = IncrementalCompact(compact, item.ir, compact.result_idx) for (idx′, stmt′) in inline_compact inline_compact[idx′] = nothing - stmt′ = ssa_substitute!(stmt′, argexprs, method.sig, spvals) - compact.result_lines[idx′] += linetable_offset + stmt′ = ssa_substitute!(idx′, stmt′, argexprs, item.method.sig, item.sparams, linetable_offset, boundscheck_idx, compact) if isa(stmt′, ReturnNode) if isdefined(stmt′, :val) push!(pn.edges, inline_compact.active_result_bb-1) @@ -250,7 +278,8 @@ function batch_inline!(todo::Vector{InliningTodo}, ir::IRCode, linetable::Vector refinish && finish_current_bb!(compact) end if !isempty(todo) - (inline_idx, (isva, isinvoke, isapply, na), method, spvals, inline_linetable, inline_ir, lie) = popfirst!(todo) + item = popfirst!(todo) + inline_idx = item.idx else inline_idx = -1 end @@ -309,14 +338,22 @@ function maybe_make_invoke!(ir::IRCode, idx::Int, @nospecialize(etype), atypes:: @nospecialize(atype_unlimited), isinvoke::Bool, isapply::Bool, @nospecialize(invoke_data)) nu = countunionsplit(atypes) nu > 1 && return # TODO: The old optimizer did union splitting here. Is this the right place? - ex = Expr(:invoke) linfo = spec_lambda(atype_unlimited, sv, invoke_data) - linfo === nothing && return - add_backedge!(linfo, sv) + if linfo === nothing + if !isapply || isinvoke + return + end + # We might not have an linfo, but we can still rewrite the _apply into a regular call + # based on our analysis + ex = Expr(:call) + else + ex = Expr(:invoke) + add_backedge!(linfo, sv) + end argexprs = ir[SSAValue(idx)].args argexprs = rewrite_exprargs((node, typ)->insert_node!(ir, idx, typ, node), arg->exprtype(arg, ir, ir.mod), isinvoke, isapply, argexprs) - pushfirst!(argexprs, linfo) + linfo !== nothing && pushfirst!(argexprs, linfo) ex.typ = etype ex.args = argexprs ir[SSAValue(idx)] = ex @@ -466,7 +503,6 @@ function assemble_inline_todo!(ir::IRCode, linetable::Vector{LineInfoNode}, sv:: methsp = methsp::SimpleVector end - methsig = method.sig if !(atype <: metharg) maybe_make_invoke!(ir, idx, stmt.typ, atypes, sv, atype_unlimited, isinvoke, isapply, invoke_data) @@ -561,7 +597,10 @@ function assemble_inline_todo!(ir::IRCode, linetable::Vector{LineInfoNode}, sv:: end #verify_ir(ir2) - push!(todo, (idx, (isva, isinvoke, isapply, na), method, Any[methsp...], inline_linetable, ir2, linear_inline_eligible(ir2))) + push!(todo, InliningTodo(idx, + isva, isinvoke, isapply, na, + method, Any[methsp...], + inline_linetable, ir2, linear_inline_eligible(ir2))) end todo end @@ -629,6 +668,7 @@ function early_inline_special_case(ir::IRCode, @nospecialize(f), @nospecialize(f f === Core.sizeof || f === isdefined || istopfunction(topmod, f, :typejoin) || istopfunction(topmod, f, :isbits) || + istopfunction(topmod, f, :isbitstype) || istopfunction(topmod, f, :promote_type) || (f === Core.kwfunc && length(atypes) == 2) || (is_inlineable_constant(val) && @@ -681,7 +721,16 @@ function late_inline_special_case!(ir::IRCode, idx::Int, stmt::Expr, atypes::Vec return false end -function ssa_substitute!(@nospecialize(val), arg_replacements::Vector{Any}, @nospecialize(spsig), spvals::Vector{Any}) +function ssa_substitute!(idx::Int, @nospecialize(val), arg_replacements::Vector{Any}, + @nospecialize(spsig), spvals::Vector{Any}, + linetable_offset::Int, boundscheck::Symbol, compact::IncrementalCompact) + compact.result_flags[idx] &= ~IR_FLAG_INBOUNDS + compact.result_lines[idx] += linetable_offset + return ssa_substitute_op!(val, arg_replacements, spsig, spvals, boundscheck) +end + +function ssa_substitute_op!(@nospecialize(val), arg_replacements::Vector{Any}, + @nospecialize(spsig), spvals::Vector{Any}, boundscheck::Symbol) if isa(val, Argument) return arg_replacements[val.n] end @@ -703,21 +752,19 @@ function ssa_substitute!(@nospecialize(val), arg_replacements::Vector{Any}, @nos e.args[3] = svec(argtuple...) end end - #= elseif head === :boundscheck - if boundscheck === :propagate - return e - elseif boundscheck === :off + if boundscheck === :off # inbounds == true return false - else + elseif boundscheck === :propagate + return e + else # on or default return true end - =# end end urs = userefs(val) for op in urs - op[] = ssa_substitute!(op[], arg_replacements, spsig, spvals) + op[] = ssa_substitute_op!(op[], arg_replacements, spsig, spvals, boundscheck) end return urs[] end diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index e8d447f714a47..f5e5a55682e7c 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -153,17 +153,20 @@ struct IRCode stmts::Vector{Any} types::Vector{Any} lines::Vector{Int} + flags::Vector{UInt8} argtypes::Vector{Any} cfg::CFG new_nodes::Vector{NewNode} mod::Module meta::Vector{Any} - function IRCode(stmts::Vector{Any}, types::Vector{Any}, lines::Vector{Int}, cfg::CFG, argtypes::Vector{Any}, mod::Module, meta::Vector{Any}) - return new(stmts, types, lines, argtypes, cfg, NewNode[], mod, meta) + function IRCode(stmts::Vector{Any}, types::Vector{Any}, lines::Vector{Int}, flags::Vector{UInt8}, + cfg::CFG, argtypes::Vector{Any}, mod::Module, meta::Vector{Any}) + return new(stmts, types, lines, flags, argtypes, cfg, NewNode[], mod, meta) end - function IRCode(ir::IRCode, stmts::Vector{Any}, types::Vector{Any}, lines::Vector{Int}, cfg::CFG, new_nodes::Vector{NewNode}) - return new(stmts, types, lines, ir.argtypes, cfg, new_nodes, ir.mod, ir.meta) + function IRCode(ir::IRCode, stmts::Vector{Any}, types::Vector{Any}, lines::Vector{Int}, flags::Vector{UInt8}, + cfg::CFG, new_nodes::Vector{NewNode}) + return new(stmts, types, lines, flags, ir.argtypes, cfg, new_nodes, ir.mod, ir.meta) end end @@ -392,6 +395,7 @@ mutable struct IncrementalCompact result::Vector{Any} result_types::Vector{Any} result_lines::Vector{Int} + result_flags::Vector{UInt8} result_bbs::Vector{BasicBlock} ssa_rename::Vector{Any} used_ssas::Vector{Int} @@ -409,10 +413,11 @@ mutable struct IncrementalCompact result = Array{Any}(undef, new_len) result_types = Array{Any}(undef, new_len) result_lines = fill(0, new_len) + result_flags = fill(0x00, new_len) used_ssas = fill(0, new_len) ssa_rename = Any[SSAValue(i) for i = 1:new_len] late_fixup = Vector{Int}() - return new(code, result, result_types, result_lines, code.cfg.blocks, ssa_rename, used_ssas, late_fixup, perm, 1, 1, 1, 1) + return new(code, result, result_types, result_lines, result_flags, code.cfg.blocks, ssa_rename, used_ssas, late_fixup, perm, 1, 1, 1, 1) end # For inlining @@ -422,8 +427,8 @@ mutable struct IncrementalCompact ssa_rename = Any[SSAValue(i) for i = 1:new_len] used_ssas = fill(0, new_len) late_fixup = Vector{Int}() - return new(code, parent.result, parent.result_types, parent.result_lines, parent.result_bbs, ssa_rename, parent.used_ssas, - late_fixup, perm, 1, 1, result_offset, parent.active_result_bb) + return new(code, parent.result, parent.result_types, parent.result_lines, parent.result_flags, parent.result_bbs, + ssa_rename, parent.used_ssas, late_fixup, perm, 1, 1, result_offset, parent.active_result_bb) end end @@ -461,6 +466,7 @@ function insert_node_here!(compact::IncrementalCompact, @nospecialize(val), @nos compact.result[compact.result_idx] = val compact.result_types[compact.result_idx] = typ compact.result_lines[compact.result_idx] = ltable_idx + compact.result_flags[compact.result_idx] = 0x00 count_added_node!(compact, val) ret = SSAValue(compact.result_idx) compact.result_idx += 1 @@ -570,6 +576,7 @@ function resize!(compact::IncrementalCompact, nnewnodes) resize!(compact.result, nnewnodes) resize!(compact.result_types, nnewnodes) resize!(compact.result_lines, nnewnodes) + resize!(compact.result_flags, nnewnodes) resize!(compact.used_ssas, nnewnodes) compact.used_ssas[(old_length+1):nnewnodes] = 0 nothing @@ -584,6 +591,7 @@ function finish_current_bb!(compact, old_result_idx=compact.result_idx) compact.result[old_result_idx] = nothing compact.result_types[old_result_idx] = Nothing compact.result_lines[old_result_idx] = 0 + compact.result_flags[old_result_idx] = 0x00 compact.result_idx = old_result_idx + 1 end compact.result_bbs[compact.active_result_bb] = BasicBlock(bb, StmtRange(first(bb.stmts), compact.result_idx-1)) @@ -614,6 +622,7 @@ function next(compact::IncrementalCompact, (idx, active_bb)::Tuple{Int, Int}) new_idx += length(compact.ir.stmts) compact.result_types[old_result_idx] = typ compact.result_lines[old_result_idx] = new_line + compact.result_flags[old_result_idx] = 0x00 result_idx = process_node!(compact, old_result_idx, new_node, new_idx, idx) compact.result_idx = result_idx # If this instruction has reverse affinity and we were at the end of a basic block, @@ -629,6 +638,7 @@ function next(compact::IncrementalCompact, (idx, active_bb)::Tuple{Int, Int}) # result_idx is not, incremented, but that's ok and expected compact.result_types[old_result_idx] = compact.ir.types[idx] compact.result_lines[old_result_idx] = compact.ir.lines[idx] + compact.result_flags[old_result_idx] = compact.ir.flags[idx] result_idx = process_node!(compact, old_result_idx, compact.ir.stmts[idx], idx, idx) stmt_if_any = old_result_idx == result_idx ? nothing : compact.result[old_result_idx] compact.result_idx = result_idx @@ -697,6 +707,7 @@ function finish(compact::IncrementalCompact) resize!(compact.result, result_idx-1) resize!(compact.result_types, result_idx-1) resize!(compact.result_lines, result_idx-1) + resize!(compact.result_flags, result_idx-1) bb = compact.result_bbs[end] compact.result_bbs[end] = BasicBlock(bb, StmtRange(first(bb.stmts), result_idx-1)) @@ -711,7 +722,7 @@ function finish(compact::IncrementalCompact) maybe_erase_unused!(extra_worklist, compact, pop!(extra_worklist)) end cfg = CFG(compact.result_bbs, Int[first(bb.stmts) for bb in compact.result_bbs[2:end]]) - return IRCode(compact.ir, compact.result, compact.result_types, compact.result_lines, cfg, NewNode[]) + return IRCode(compact.ir, compact.result, compact.result_types, compact.result_lines, compact.result_flags, cfg, NewNode[]) end function compact!(code::IRCode) diff --git a/base/compiler/ssair/legacy.jl b/base/compiler/ssair/legacy.jl index 665d00ca7c273..6e0c9c1444ff8 100644 --- a/base/compiler/ssair/legacy.jl +++ b/base/compiler/ssair/legacy.jl @@ -137,6 +137,7 @@ function replace_code!(ci::CodeInfo, code::IRCode, nargs::Int, linetable::Vector topline = 1 for (idx, stmt) in pairs(code.stmts) line = code.lines[idx] + flag = code.flags[idx] # push labels first if haskey(block_start, idx) push!(new_code, LabelNode(length(new_code) + 1)) @@ -149,6 +150,9 @@ function replace_code!(ci::CodeInfo, code::IRCode, nargs::Int, linetable::Vector push_new_lineinfo!(new_code, topline, line, linetable) topline = line end + #if flag & IR_FLAG_INBOUNDS != 0x00 + # push!(new_code, Expr(:inbounds, true)) + #end # record if this'll need a fixup after stmt number if isa(stmt, GotoIfNot) new_stmt = Expr(:gotoifnot, rename(stmt.cond), stmt.dest) @@ -180,6 +184,9 @@ function replace_code!(ci::CodeInfo, code::IRCode, nargs::Int, linetable::Vector end # and finally, record the new new statement push!(new_code, new_stmt) + #if (flag & IR_FLAG_INBOUNDS != 0) && idx != length(code.stmts) + # push!(new_code, Expr(:inbounds, false)) + #end end for i in fixup val = new_code[i] @@ -242,7 +249,8 @@ function inflate_ir(ci::CodeInfo) code[i] = stmt end end - ir = IRCode(code, copy(ci.ssavaluetypes), copy(ci.codelocs), cfg, copy(ci.slottypes), ci.linetable[1].mod, Any[]) + ir = IRCode(code, copy(ci.ssavaluetypes), copy(ci.codelocs), copy(ci.ssaflags), cfg, copy(ci.slottypes), ci.linetable[1].mod, Any[]) + return ir end function replace_code_newstyle!(ci::CodeInfo, ir::IRCode, nargs, linetable) @@ -255,6 +263,7 @@ function replace_code_newstyle!(ci::CodeInfo, ir::IRCode, nargs, linetable) ci.codelocs = ir.lines ci.linetable = linetable ci.ssavaluetypes = ir.types + ci.ssaflags = ir.flags # Translate BB Edges to statement edges # (and undo normalization for now) for i = 1:length(ci.code) diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index c0d2bc569b9c9..cd6d419cf73c5 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -189,7 +189,7 @@ is_tuple_call(ir, def) = isa(def, Expr) && is_known_call(def, tuple, ir, ir.mod) function process_immutable_preserve(new_preserves::Vector{Any}, compact::IncrementalCompact, def::Expr) for arg in (isexpr(def, :new) ? def.args : def.args[2:end]) - if !isbits(compact_exprtype(compact, arg)) + if !isbitstype(compact_exprtype(compact, arg)) push!(new_preserves, arg) end end @@ -373,7 +373,7 @@ function getfield_elim_pass!(ir::IRCode, domtree::DomTree) for stmt in du.uses ir[SSAValue(stmt)] = compute_value_for_use(ir, domtree, allblocks, du, phinodes, fidx, stmt) end - if !isbits(fieldtype(typ, fidx)) + if !isbitstype(fieldtype(typ, fidx)) for (use, list) in preserve_uses push!(list, compute_value_for_use(ir, domtree, allblocks, du, phinodes, fidx, use)) end diff --git a/base/compiler/ssair/slot2ssa.jl b/base/compiler/ssair/slot2ssa.jl index a5678a55c6256..72cb54b13647e 100644 --- a/base/compiler/ssair/slot2ssa.jl +++ b/base/compiler/ssair/slot2ssa.jl @@ -181,13 +181,14 @@ function rename_uses!(ir::IRCode, ci::CodeInfo, idx::Int, @nospecialize(stmt), r return fixemup!(stmt->true, stmt->renames[slot_id(stmt)], ir, ci, idx, stmt) end -function strip_trailing_junk!(code::Vector{Any}, lines::Vector{Int}) +function strip_trailing_junk!(code::Vector{Any}, lines::Vector{Int}, flags::Vector{UInt8}) # Remove `nothing`s at the end, we don't handle them well # (we expect the last instruction to be a terminator) for i = length(code):-1:1 if code[i] !== nothing resize!(code, i) resize!(lines, i) + resize!(flags, i) break end end @@ -197,6 +198,7 @@ function strip_trailing_junk!(code::Vector{Any}, lines::Vector{Int}) if !isa(term, GotoIfNot) && !isa(term, GotoNode) && !isa(term, ReturnNode) push!(code, ReturnNode()) push!(lines, 0) + push!(flags, 0x00) end return code end @@ -360,6 +362,7 @@ function domsort_ssa!(ir::IRCode, domtree::DomTree) result_stmts = Vector{Any}(undef, nstmts + ncritbreaks + nnewfallthroughs) result_types = Any[Any for i = 1:length(result_stmts)] result_ltable = fill(0, length(result_stmts)) + result_flags = fill(0x00, length(result_stmts)) inst_rename = Vector{Any}(undef, length(ir.stmts)) for i = 1:length(ir.new_nodes) push!(inst_rename, SSAValue(nstmts + i + ncritbreaks + nnewfallthroughs)) @@ -384,6 +387,7 @@ function domsort_ssa!(ir::IRCode, domtree::DomTree) end result_types[nidx] = ir.types[idx] result_ltable[nidx] = ir.lines[idx] + result_flags[nidx] = ir.flags[idx] end # Now fix up the terminator terminator = result_stmts[inst_range[end]] @@ -427,7 +431,7 @@ function domsort_ssa!(ir::IRCode, domtree::DomTree) node = renumber_ssa!(node, inst_rename, true) (inst_rename[pos].id, reverse_affinity, typ, node, lno) end for i in 1:length(ir.new_nodes)] - new_ir = IRCode(ir, result_stmts, result_types, result_ltable, cfg, new_new_nodes) + new_ir = IRCode(ir, result_stmts, result_types, result_ltable, result_flags, cfg, new_new_nodes) return new_ir end @@ -770,7 +774,7 @@ function construct_ssa!(ci::CodeInfo, code::Vector{Any}, ir::IRCode, domtree::Do new_nodes = NewNode[let (pt, reverse_affinity, typ, stmt, line) = new_nodes[i] (pt, reverse_affinity, typ, new_to_regular(renumber_ssa!(stmt, ssavalmap)), line) end for i in 1:length(new_nodes)] - ir = IRCode(ir, new_code, types, ir.lines, ir.cfg, new_nodes) + ir = IRCode(ir, new_code, types, ir.lines, ir.flags, ir.cfg, new_nodes) @timeit "domsort" ir = domsort_ssa!(ir, domtree) return ir end diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 45aa51dfe1de3..22a027a3b153a 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -261,7 +261,7 @@ function isdefined_tfunc(args...) end elseif idx <= 0 || (!isvatuple(a1) && idx > fieldcount(a1)) return Const(false) - elseif !isvatuple(a1) && isbits(fieldtype(a1, idx)) + elseif !isvatuple(a1) && isbitstype(fieldtype(a1, idx)) return Const(true) elseif isa(arg1, Const) && isimmutable((arg1::Const).val) return Const(isdefined((arg1::Const).val, idx)) diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 77fbc21cf8873..534106c79f2b2 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -168,16 +168,15 @@ function typeinf_code(linfo::MethodInstance, optimize::Bool, cached::Bool, method = linfo.def::Method tree = ccall(:jl_new_code_info_uninit, Ref{CodeInfo}, ()) tree.code = Any[ Expr(:return, quoted(linfo.inferred_const)) ] - tree.signature_for_inference_heuristics = nothing + tree.method_for_inference_limit_heuristics = nothing tree.slotnames = Any[ COMPILER_TEMP_SYM for i = 1:method.nargs ] - tree.slotflags = UInt8[ 0 for i = 1:method.nargs ] + tree.slotflags = fill(0x00, Int(method.nargs)) tree.slottypes = nothing tree.ssavaluetypes = 0 tree.inferred = true + tree.ssaflags = UInt8[] tree.pure = true tree.inlineable = true - tree.codelocs = nothing - tree.linetable = nothing i == 2 && ccall(:jl_typeinf_end, Cvoid, ()) return svec(linfo, tree, linfo.rettype) elseif isa(inf, CodeInfo) @@ -319,11 +318,14 @@ function typeinf_work(frame::InferenceState) else # general case frame.handler_at[l] = frame.cur_hand + changes_else = changes if isa(condt, Conditional) - changes_else = StateUpdate(condt.var, VarState(condt.elsetype, false), changes) - changes = StateUpdate(condt.var, VarState(condt.vtype, false), changes) - else - changes_else = changes + if condt.elsetype !== Any && condt.elsetype !== changes[slot_id(condt.var)] + changes_else = StateUpdate(condt.var, VarState(condt.elsetype, false), changes_else) + end + if condt.vtype !== Any && condt.vtype !== changes[slot_id(condt.var)] + changes = StateUpdate(condt.var, VarState(condt.vtype, false), changes) + end end newstate_else = stupdate!(s[l], changes_else) if newstate_else !== false diff --git a/base/compiler/typelimits.jl b/base/compiler/typelimits.jl index 6313f6aecbee1..72e6cf1d834bd 100644 --- a/base/compiler/typelimits.jl +++ b/base/compiler/typelimits.jl @@ -73,7 +73,7 @@ function is_derived_type(@nospecialize(t), @nospecialize(c), mindepth::Int) for p in cP is_derived_type(t, p, mindepth) && return true end - if isconcretetype(c) && isbits(c) + if isconcretetype(c) && isbitstype(c) # see if it was extracted from a fieldtype # however, only look through types that can be inlined # to ensure monotonicity of derivation @@ -302,14 +302,34 @@ function type_more_complex(@nospecialize(t), @nospecialize(c), sources::SimpleVe return true end +# pick a wider type that contains both typea and typeb, +# with some limits on how "large" it can get, +# but without losing too much precision in common cases +# and also trying to be associative and commutative function tmerge(@nospecialize(typea), @nospecialize(typeb)) typea ⊑ typeb && return typeb typeb ⊑ typea && return typea + # type-lattice for MaybeUndef wrapper if isa(typea, MaybeUndef) || isa(typeb, MaybeUndef) return MaybeUndef(tmerge( isa(typea, MaybeUndef) ? typea.typ : typea, isa(typeb, MaybeUndef) ? typeb.typ : typeb)) end + # type-lattice for Conditional wrapper + if isa(typea, Conditional) && isa(typeb, Const) + if typeb.val === true + typeb = Conditional(typea.var, Any, Union{}) + elseif typeb.val === false + typeb = Conditional(typea.var, Union{}, Any) + end + end + if isa(typeb, Conditional) && isa(typea, Const) + if typea.val === true + typea = Conditional(typeb.var, Any, Union{}) + elseif typea.val === false + typea = Conditional(typeb.var, Union{}, Any) + end + end if isa(typea, Conditional) && isa(typeb, Conditional) if typea.var === typeb.var vtype = tmerge(typea.vtype, typeb.vtype) @@ -320,6 +340,7 @@ function tmerge(@nospecialize(typea), @nospecialize(typeb)) end return Bool end + # no special type-inference lattice, join the types typea, typeb = widenconst(typea), widenconst(typeb) typea === typeb && return typea if !(isa(typea, Type) || isa(typea, TypeVar)) || diff --git a/base/compiler/typeutils.jl b/base/compiler/typeutils.jl index db744396066a3..95bdecaa6a5e7 100644 --- a/base/compiler/typeutils.jl +++ b/base/compiler/typeutils.jl @@ -53,11 +53,11 @@ end function valid_tparam(@nospecialize(x)) if isa(x, Tuple) for t in x - isa(t, Symbol) || isbits(typeof(t)) || return false + isa(t, Symbol) || isbitstype(typeof(t)) || return false end return true end - return isa(x, Symbol) || isbits(typeof(x)) + return isa(x, Symbol) || isbitstype(typeof(x)) end has_free_typevars(@nospecialize(t)) = ccall(:jl_has_free_typevars, Cint, (Any,), t) != 0 diff --git a/base/compiler/utilities.jl b/base/compiler/utilities.jl index 7955241182ca9..94a7d087a3bad 100644 --- a/base/compiler/utilities.jl +++ b/base/compiler/utilities.jl @@ -155,33 +155,21 @@ function code_for_method(method::Method, @nospecialize(atypes), sparams::SimpleV return ccall(:jl_specializations_get_linfo, Ref{MethodInstance}, (Any, Any, Any, UInt), method, atypes, sparams, world) end -# TODO: Use these functions instead of directly manipulating -# the "actual" method for appropriate places in inference (see #24676) -function method_for_inference_heuristics(cinfo, default) - if isa(cinfo, CodeInfo) - # appropriate format for `sig` is svec(ftype, argtypes, world) - sig = cinfo.signature_for_inference_heuristics - if isa(sig, SimpleVector) && length(sig) == 3 - methods = _methods(sig[1], sig[2], -1, sig[3]) - if length(methods) == 1 - _, _, m = methods[] - if isa(m, Method) - return m - end - end - end - end - return default -end - -function method_for_inference_heuristics(method::Method, @nospecialize(sig), sparams, world) +# This function is used for computing alternate limit heuristics +function method_for_inference_heuristics(method::Method, @nospecialize(sig), sparams::SimpleVector, world::UInt) if isdefined(method, :generator) && method.generator.expand_early method_instance = code_for_method(method, sig, sparams, world, false) if isa(method_instance, MethodInstance) - return method_for_inference_heuristics(get_staged(method_instance), method) + cinfo = get_staged(method_instance) + if isa(cinfo, CodeInfo) + method2 = cinfo.method_for_inference_limit_heuristics + if method2 isa Method + return method2 + end + end end end - return method + return nothing end function exprtype(@nospecialize(x), src, mod::Module) diff --git a/base/deepcopy.jl b/base/deepcopy.jl index 45a7755a77ae6..09c6e559e35ee 100644 --- a/base/deepcopy.jl +++ b/base/deepcopy.jl @@ -54,7 +54,7 @@ end function deepcopy_internal(@nospecialize(x), stackdict::IdDict) T = typeof(x)::DataType nf = nfields(x) - (isbits(T) || nf == 0) && return x + (isbitstype(T) || nf == 0) && return x if haskey(stackdict, x) return stackdict[x] end @@ -79,7 +79,7 @@ function deepcopy_internal(x::Array, stackdict::IdDict) end function _deepcopy_array_t(@nospecialize(x), T, stackdict::IdDict) - if isbits(T) + if isbitstype(T) return (stackdict[x]=copy(x)) end dest = similar(x) @@ -87,7 +87,7 @@ function _deepcopy_array_t(@nospecialize(x), T, stackdict::IdDict) for i = 1:(length(x)::Int) if ccall(:jl_array_isassigned, Cint, (Any, Csize_t), x, i-1) != 0 xi = ccall(:jl_arrayref, Any, (Any, Csize_t), x, i-1) - if !isbits(typeof(xi)) + if !isbitstype(typeof(xi)) xi = deepcopy_internal(xi, stackdict) end ccall(:jl_arrayset, Cvoid, (Any, Any, Csize_t), dest, xi, i-1) @@ -101,7 +101,7 @@ function deepcopy_internal(x::Union{Dict,IdDict}, stackdict::IdDict) return stackdict[x]::typeof(x) end - if isbits(eltype(x)) + if isbitstype(eltype(x)) return (stackdict[x] = copy(x)) end diff --git a/base/deprecated.jl b/base/deprecated.jl index f9002cb7ccc63..e8554bc9e05a6 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -1609,6 +1609,9 @@ end # To remove this deprecation, remove the `keep` argument from the function signatures as well as # the internal logic that deals with the renaming. These live in base/strings/util.jl. +# when this is removed, `isbitstype(typeof(x))` can be replaced with `isbits(x)` +@deprecate isbits(@nospecialize(t::Type)) isbitstype(t) + # END 0.7 deprecations # BEGIN 1.0 deprecations diff --git a/base/exports.jl b/base/exports.jl index d4d4b5f05a579..996d4f3dd0689 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -731,6 +731,7 @@ export fieldcount, # propertynames, isabstracttype, + isbitstype, isprimitivetype, isstructtype, isconcretetype, diff --git a/base/expr.jl b/base/expr.jl index 955c9eccaec01..20500514b3343 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -32,23 +32,33 @@ end ## expressions ## -copy(e::Expr) = (n = Expr(e.head); - n.args = copy_exprargs(e.args); - n.typ = e.typ; - n) +function copy(e::Expr) + n = Expr(e.head) + n.args = copy_exprargs(e.args) + n.typ = e.typ + return n +end # copy parts of an AST that the compiler mutates -copy_exprs(x::Expr) = copy(x) copy_exprs(@nospecialize(x)) = x +copy_exprs(x::Expr) = copy(x) function copy_exprs(x::PhiNode) new_values = Vector{Any}(undef, length(x.values)) - for i = 1:length(x.edges) + for i = 1:length(x.values) + isassigned(x.values, i) || continue + new_values[i] = copy_exprs(x.values[i]) + end + return PhiNode(copy(x.edges), new_values) +end +function copy_exprs(x::PhiCNode) + new_values = Vector{Any}(undef, length(x.values)) + for i = 1:length(x.values) isassigned(x.values, i) || continue new_values[i] = copy_exprs(x.values[i]) end - PhiNode(copy(x.edges), new_values) + return PhiCNode(new_values) end -copy_exprargs(x::Array{Any,1}) = Any[copy_exprs(a) for a in x] +copy_exprargs(x::Array{Any,1}) = Any[copy_exprs(x[i]) for i in 1:length(x)] ==(x::Expr, y::Expr) = x.head === y.head && isequal(x.args, y.args) ==(x::QuoteNode, y::QuoteNode) = isequal(x.value, y.value) diff --git a/base/initdefs.jl b/base/initdefs.jl index 8aa8b0fa1bba4..7b206d75c522d 100644 --- a/base/initdefs.jl +++ b/base/initdefs.jl @@ -48,7 +48,9 @@ function init_depot_path(BINDIR = Sys.BINDIR) depots = split(ENV["JULIA_DEPOT_PATH"], Sys.iswindows() ? ';' : ':') append!(empty!(DEPOT_PATH), map(expanduser, depots)) else - push!(DEPOT_PATH, joinpath(homedir(), ".julia")) + push!(empty!(DEPOT_PATH), joinpath(homedir(), ".julia")) + push!(DEPOT_PATH, abspath(BINDIR, "..", "local", "share", "julia")) + push!(DEPOT_PATH, abspath(BINDIR, "..", "share", "julia")) end end @@ -115,14 +117,15 @@ function parse_load_path(str::String) return envs end +const default_named = parse_load_path("@v#.#.#|@v#.#|@v#|@default|@!v#.#") + function init_load_path(BINDIR = Sys.BINDIR) - if !Base.creating_sysimg - load_path = get(ENV, "JULIA_LOAD_PATH", "@|@v#.#.#|@v#.#|@v#|@default|@!v#.#") - append!(empty!(LOAD_PATH), parse_load_path(load_path)) - end vers = "v$(VERSION.major).$(VERSION.minor)" - push!(LOAD_PATH, abspath(BINDIR, "..", "local", "share", "julia", "site", vers)) - push!(LOAD_PATH, abspath(BINDIR, "..", "share", "julia", "site", vers)) + stdlib = abspath(BINDIR, "..", "share", "julia", "stdlib", vers) + load_path = Base.creating_sysimg ? Any[stdlib] : + haskey(ENV, "JULIA_LOAD_PATH") ? parse_load_path(ENV["JULIA_LOAD_PATH"]) : + Any[CurrentEnv(); default_named; stdlib] + append!(empty!(LOAD_PATH), load_path) end const atexit_hooks = [] diff --git a/base/int.jl b/base/int.jl index c83345cc75b06..80140c6d65191 100644 --- a/base/int.jl +++ b/base/int.jl @@ -745,6 +745,8 @@ end for op in (:+, :-, :*, :&, :|, :xor) @eval function $op(a::Integer, b::Integer) T = promote_typeof(a, b) - return $op(a % T, b % T) + aT, bT = a % T, b % T + not_sametype((a, b), (aT, bT)) + return $op(aT, bT) end end diff --git a/base/io.jl b/base/io.jl index f46823a14a847..d9371251f1f08 100644 --- a/base/io.jl +++ b/base/io.jl @@ -518,7 +518,7 @@ write(s::IO, x::Bool) = write(s, UInt8(x)) write(to::IO, p::Ptr) = write(to, convert(UInt, p)) function write(s::IO, A::AbstractArray) - if !isbits(eltype(A)) + if !isbitstype(eltype(A)) depwarn("Calling `write` on non-isbits arrays is deprecated. Use a loop or `serialize` instead.", :write) end nb = 0 @@ -529,7 +529,7 @@ function write(s::IO, A::AbstractArray) end function write(s::IO, a::Array) - if isbits(eltype(a)) + if isbitstype(eltype(a)) return GC.@preserve a unsafe_write(s, pointer(a), sizeof(a)) else depwarn("Calling `write` on non-isbits arrays is deprecated. Use a loop or `serialize` instead.", :write) @@ -542,7 +542,7 @@ function write(s::IO, a::Array) end function write(s::IO, a::SubArray{T,N,<:Array}) where {T,N} - if !isbits(T) + if !isbitstype(T) return invoke(write, Tuple{IO, AbstractArray}, s, a) end elsz = sizeof(T) @@ -605,7 +605,7 @@ function read!(s::IO, a::Array{UInt8}) end function read!(s::IO, a::Array{T}) where T - if isbits(T) + if isbitstype(T) GC.@preserve a unsafe_read(s, pointer(a), sizeof(a)) else for i in eachindex(a) diff --git a/base/iobuffer.jl b/base/iobuffer.jl index 0c378ea82a627..88ce78dc77aec 100644 --- a/base/iobuffer.jl +++ b/base/iobuffer.jl @@ -171,7 +171,7 @@ function read_sub(from::GenericIOBuffer, a::AbstractArray{T}, offs, nel) where T if offs+nel-1 > length(a) || offs < 1 || nel < 0 throw(BoundsError()) end - if isbits(T) && isa(a,Array) + if isbitstype(T) && isa(a,Array) nb = UInt(nel * sizeof(T)) GC.@preserve a unsafe_read(from, pointer(a, offs), nb) else diff --git a/base/iterators.jl b/base/iterators.jl index 8f5f91e7a7137..59fa36816cbac 100644 --- a/base/iterators.jl +++ b/base/iterators.jl @@ -226,10 +226,10 @@ length(v::Pairs) = length(v.itr) axes(v::Pairs) = axes(v.itr) size(v::Pairs) = size(v.itr) @inline start(v::Pairs) = start(v.itr) -@propagate_inbounds function next(v::Pairs, state) +@propagate_inbounds function next(v::Pairs{K, V}, state) where {K, V} indx, n = next(v.itr, state) item = v.data[indx] - return (Pair(indx, item), n) + return (Pair{K, V}(indx, item), n) end @inline done(v::Pairs, state) = done(v.itr, state) diff --git a/base/loading.jl b/base/loading.jl index 1c55c1dca5e50..c7710b2740c80 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -196,7 +196,7 @@ end find_env(env::Function) = find_env(env()) -load_path() = filter(env -> env ≠ nothing, map(find_env, LOAD_PATH)) +load_path() = String[env for env in map(find_env, LOAD_PATH) if env ≠ nothing] ## package identification: determine unique identity of package to be loaded ## diff --git a/base/pair.jl b/base/pair.jl index 2cec78a646be0..5db7e39e62f30 100644 --- a/base/pair.jl +++ b/base/pair.jl @@ -28,18 +28,27 @@ foo 7 ``` """ -struct Pair{A,B} +struct Pair{A, B} first::A second::B + function Pair{A, B}(@nospecialize(a), @nospecialize(b)) where {A, B} + @_inline_meta + # if we didn't inline this, it's probably because the callsite was actually dynamic + # to avoid potentially compiling many copies of this, we mark the arguments with `@nospecialize` + # but also mark the whole function with `@inline` to ensure we will inline it whenever possible + # (even if `convert(::Type{A}, a::A)` for some reason was expensive) + return new(a, b) + end end +Pair(a::A, b::B) where {A, B} = Pair{A, B}(a, b) const => = Pair start(p::Pair) = 1 done(p::Pair, i) = i>2 -next(p::Pair, i) = (getfield(p,i), i+1) -eltype(p::Type{Pair{A,B}}) where {A,B} = Union{A,B} +next(p::Pair, i) = (getfield(p, i), i+1) +eltype(p::Type{Pair{A, B}}) where {A, B} = Union{A, B} -indexed_next(p::Pair, i::Int, state) = (getfield(p,i), i+1) +indexed_next(p::Pair, i::Int, state) = (getfield(p, i), i+1) hash(p::Pair, h::UInt) = hash(p.second, hash(p.first, h)) diff --git a/base/pointer.jl b/base/pointer.jl index b0ded31b87a27..bbc5c215d38f2 100644 --- a/base/pointer.jl +++ b/base/pointer.jl @@ -143,8 +143,6 @@ function pointer_from_objref(@nospecialize(x)) ccall(:jl_value_ptr, Ptr{Cvoid}, (Any,), x) end -eltype(::Type{Ptr{T}}) where {T} = T - ## limited pointer arithmetic & comparison ## ==(x::Ptr, y::Ptr) = UInt(x) == UInt(y) diff --git a/base/promotion.jl b/base/promotion.jl index b9633c53f2217..a999c1743cbff 100644 --- a/base/promotion.jl +++ b/base/promotion.jl @@ -275,25 +275,6 @@ end ## promotions in arithmetic, etc. ## -# Because of the promoting fallback definitions for Number, we need -# a special case for undefined promote_rule on numeric types. -# Otherwise, typejoin(T,S) is called (returning Number) so no conversion -# happens, and +(promote(x,y)...) is called again, causing a stack -# overflow. -function promote_result(::Type{T},::Type{S},::Type{Bottom},::Type{Bottom}) where {T<:Number,S<:Number} - @_inline_meta - promote_to_supertype(T, S, typejoin(T,S)) -end - -# promote numeric types T and S to typejoin(T,S) if T<:S or S<:T -# for example this makes promote_type(Integer,Real) == Real without -# promoting arbitrary pairs of numeric types to Number. -promote_to_supertype(::Type{T}, ::Type{T}, ::Type{T}) where {T<:Number} = T -promote_to_supertype(::Type{T}, ::Type{S}, ::Type{T}) where {T<:Number,S<:Number} = T -promote_to_supertype(::Type{T}, ::Type{S}, ::Type{S}) where {T<:Number,S<:Number} = S -promote_to_supertype(::Type{T}, ::Type{S}, ::Type) where {T<:Number,S<:Number} = - error("no promotion exists for ", T, " and ", S) - promote() = () promote(x) = (x,) diff --git a/base/reducedim.jl b/base/reducedim.jl index 7585c91fba000..a556fbe3667aa 100644 --- a/base/reducedim.jl +++ b/base/reducedim.jl @@ -116,7 +116,7 @@ function _reducedim_init(f, op, fv, fop, A, region) if T !== Any && applicable(zero, T) x = f(zero(T)) z = op(fv(x), fv(x)) - Tr = typeof(z) == typeof(x) && !isbits(T) ? T : typeof(z) + Tr = typeof(z) == typeof(x) && !isbitstype(T) ? T : typeof(z) else z = fv(fop(f, A)) Tr = typeof(z) diff --git a/base/reflection.jl b/base/reflection.jl index 18e675a25d130..255aed7976a7e 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -352,11 +352,11 @@ function isprimitivetype(@nospecialize(t::Type)) end """ - isbits(T) + isbitstype(T) Return `true` if type `T` is a "plain data" type, meaning it is immutable and contains no references to other values, -only `primitive` types and other `isbits` types. +only `primitive` types and other `isbitstype` types. Typical examples are numeric types such as [`UInt8`](@ref), [`Float64`](@ref), and [`Complex{Float64}`](@ref). This category of types is significant since they are valid as type parameters, @@ -365,14 +365,20 @@ and have a defined layout that is compatible with C. # Examples ```jldoctest -julia> isbits(Complex{Float64}) +julia> isbitstype(Complex{Float64}) true -julia> isbits(Complex) +julia> isbitstype(Complex) false ``` """ -isbits(@nospecialize(t::Type)) = (@_pure_meta; isa(t, DataType) && t.isbitstype) +isbitstype(@nospecialize(t::Type)) = (@_pure_meta; isa(t, DataType) && t.isbitstype) + +""" + isbits(x) + +Return `true` if `x` is an instance of an `isbitstype` type. +""" isbits(@nospecialize x) = (@_pure_meta; typeof(x).isbitstype) """ diff --git a/base/refpointer.jl b/base/refpointer.jl index 0b1cc30c46af6..2f2989211f964 100644 --- a/base/refpointer.jl +++ b/base/refpointer.jl @@ -83,7 +83,7 @@ if is_primary_base_module return RefArray(a) # effectively a no-op end function Ref{P}(a::Array{T}) where P<:Union{Ptr,Cwstring,Cstring} where T - if (!isbits(T) && T <: eltype(P)) + if (!isbitstype(T) && T <: eltype(P)) # this Array already has the right memory layout for the requested Ref return RefArray(a,1,false) # root something, so that this function is type-stable else diff --git a/base/regex.jl b/base/regex.jl index cec07bb3ea5d8..6da10eda0742a 100644 --- a/base/regex.jl +++ b/base/regex.jl @@ -334,7 +334,7 @@ function next(itr::RegexMatchIterator, prev_match) offset = prev_match.offset end else - offset = prev_match.offset + lastindex(prev_match.match) + offset = prev_match.offset + ncodeunits(prev_match.match) end opts_nonempty = UInt32(PCRE.ANCHORED | PCRE.NOTEMPTY_ATSTART) diff --git a/base/reinterpretarray.jl b/base/reinterpretarray.jl index 2a929abb2ac87..126cbac9eee6b 100644 --- a/base/reinterpretarray.jl +++ b/base/reinterpretarray.jl @@ -24,8 +24,8 @@ struct ReinterpretArray{T,N,S,A<:AbstractArray{S, N}} <: AbstractArray{T, N} The resulting array would have non-integral first dimension. """)) end - isbits(T) || throwbits(S, T, T) - isbits(S) || throwbits(S, T, S) + isbitstype(T) || throwbits(S, T, T) + isbitstype(S) || throwbits(S, T, S) (N != 0 || sizeof(T) == sizeof(S)) || throwsize0(S, T) if N != 0 && sizeof(S) != sizeof(T) dim = size(a)[1] diff --git a/base/show.jl b/base/show.jl index e34a9290fbf7f..d94e4f17825db 100644 --- a/base/show.jl +++ b/base/show.jl @@ -1835,11 +1835,11 @@ end ## `summary` for AbstractArrays # sizes such as 0-dimensional, 4-dimensional, 2x3 -dims2string(d::Dims) = isempty(d) ? "0-dimensional" : - length(d) == 1 ? "$(d[1])-element" : - join(map(string,d), '×') +dims2string(d) = isempty(d) ? "0-dimensional" : + length(d) == 1 ? "$(d[1])-element" : + join(map(string,d), '×') -inds2string(inds::Indices) = join(map(string,inds), '×') +inds2string(inds) = join(map(string,inds), '×') # anything array-like gets summarized e.g. 10-element Array{Int64,1} summary(io::IO, a::AbstractArray) = summary(io, a, axes(a)) diff --git a/base/summarysize.jl b/base/summarysize.jl index 4c859ca3bb1d5..d6670874d334e 100644 --- a/base/summarysize.jl +++ b/base/summarysize.jl @@ -45,7 +45,7 @@ function summarysize(obj; else nf = _nfields(x) ft = typeof(x).types - if !isbits(ft[i]) && isdefined(x, i) + if !isbitstype(ft[i]) && isdefined(x, i) val = getfield(x, i) end end @@ -108,7 +108,7 @@ function (ss::SummarySize)(obj::Array) if !haskey(ss.seen, datakey) ss.seen[datakey] = true size += Core.sizeof(obj) - if !isbits(eltype(obj)) && !isempty(obj) + if !isbitstype(eltype(obj)) && !isempty(obj) push!(ss.frontier_x, obj) push!(ss.frontier_i, 1) end diff --git a/base/threadcall.jl b/base/threadcall.jl index 901699b7a4d5b..1dd48b4097f7a 100644 --- a/base/threadcall.jl +++ b/base/threadcall.jl @@ -68,7 +68,7 @@ function do_threadcall(fun_ptr::Ptr{Cvoid}, rettype::Type, argtypes::Vector, arg args_arr = Vector{UInt8}(undef, args_size) ptr = pointer(args_arr) for (T, x) in zip(argtypes, argvals) - isbits(T) || throw(ArgumentError("threadcall requires isbits argument types")) + isbitstype(T) || throw(ArgumentError("threadcall requires isbits argument types")) y = cconvert(T, x) push!(roots, y) unsafe_store!(convert(Ptr{T}, ptr), unsafe_convert(T, y)::T) diff --git a/deps/patchelf.mk b/deps/patchelf.mk index 552e48a941442..89ad1daf3b4cb 100644 --- a/deps/patchelf.mk +++ b/deps/patchelf.mk @@ -1,7 +1,7 @@ ## patchelf ## $(SRCCACHE)/patchelf-$(PATCHELF_VER).tar.gz: | $(SRCCACHE) - $(JLDOWNLOAD) $@ http://nixos.org/releases/patchelf/patchelf-$(PATCHELF_VER)/patchelf-$(PATCHELF_VER).tar.gz + $(JLDOWNLOAD) $@ https://nixos.org/releases/patchelf/patchelf-$(PATCHELF_VER)/patchelf-$(PATCHELF_VER).tar.gz $(SRCCACHE)/patchelf-$(PATCHELF_VER)/source-extracted: $(SRCCACHE)/patchelf-$(PATCHELF_VER).tar.gz $(JLCHECKSUM) $< diff --git a/deps/pcre.mk b/deps/pcre.mk index 6375c47bcdb99..ab229c84f0176 100644 --- a/deps/pcre.mk +++ b/deps/pcre.mk @@ -5,7 +5,7 @@ PCRE_CFLAGS := -O3 PCRE_LDFLAGS := $(RPATH_ESCAPED_ORIGIN) $(SRCCACHE)/pcre2-$(PCRE_VER).tar.bz2: | $(SRCCACHE) - $(JLDOWNLOAD) $@ https://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre2-$(PCRE_VER).tar.bz2 + $(JLDOWNLOAD) $@ https://ftp.pcre.org/pub/pcre/pcre2-$(PCRE_VER).tar.bz2 $(SRCCACHE)/pcre2-$(PCRE_VER)/source-extracted: $(SRCCACHE)/pcre2-$(PCRE_VER).tar.bz2 $(JLCHECKSUM) $< diff --git a/doc/make.jl b/doc/make.jl index f255228af7d88..1149e086a7112 100644 --- a/doc/make.jl +++ b/doc/make.jl @@ -79,6 +79,7 @@ const PAGES = [ "manual/environment-variables.md", "manual/embedding.md", "manual/packages.md", + "manual/code-loading.md", "manual/profile.md", "manual/stacktraces.md", "manual/performance-tips.md", diff --git a/doc/src/base/base.md b/doc/src/base/base.md index 1839bb8de443d..69cc4bcecc913 100644 --- a/doc/src/base/base.md +++ b/doc/src/base/base.md @@ -146,6 +146,7 @@ Base.fieldname Base.sizeof(::Type) Base.isconcretetype Base.isbits +Base.isbitstype Core.fieldtype Base.fieldcount Base.fieldoffset diff --git a/doc/src/manual/code-loading.md b/doc/src/manual/code-loading.md new file mode 100644 index 0000000000000..216e05e9ed10b --- /dev/null +++ b/doc/src/manual/code-loading.md @@ -0,0 +1,330 @@ +# Code Loading + +Julia has two mechanisms for loading code: + +1. **Code inclusion:** e.g. `include("source.jl")`. Inclusion allows you to split a single program across multiple source files. The expression `include("source.jl")` causes the contents of the file `source.jl` to be evaluated inside of the module where the `include` call occurs. If `include("source.jl")` is called multiple times, `source.jl` is evaluated multiple times. The included path, `source.jl`, is interpreted relative to the file where the `include` call occurs. This makes it simple to relocate a subtree of source files. In the REPL, included paths are interpreted relative to the current working directory, `pwd()`. +2. **Package loading:** e.g. `import X` or `using X`. The import mechanism allows you to load a package—i.e. an independent, reusable collection of Julia code, wrapped in a module—and makes the resulting module available by the name `X` inside of the importing module. If the same `X` package is imported multiple times in the same Julia session, it is only loaded the first time—on subsequent imports, the importing module gets a reference to the same module. It should be noted, however, that `import X` can load different packages in different contexts: `X` can refer to one package named `X` in the main project but potentially different packages named `X` in each dependency. More on this below. + +Code inclusion is quite straightforward: it simply parses and evaluates a source file in the context of the caller. Package loading is built on top of code inclusion and is quite a bit more complex. The rest of this chapter, therefore, focuses on the behavior and mechanics of package loading. + +!!! note + You only need to read this chapter if you want to understand the technical details of package loading in Julia. If you just want to install and use packages, simply use Julia's built-in package manager to add packages to your environment and write `import X` or `using X` in your code to load packages that you've added. + +A *package* is a source tree with a standard layout providing functionality that can be reused by other Julia projects. A package is loaded by `import X` or `using X` statements. These statements also make the module named `X`, which results from loading the package code, available within the module where the import statement occurs. The meaning of `X` in `import X` is context-dependent: which `X` package is loaded depends on what code the statement occurs in. The effect of `import X` depends on two questions: + +1. **What** package is `X` in this context? +2. **Where** can that `X` package be found? + +Understanding how Julia answers these questions is key to understanding package loading. + +## Federation of packages + +Julia supports federated management of packages. This means that multiple independent parties can maintain both public and private packages and registries of them, and that projects can depend on a mix of public and private packages from different registries. Packages from various registries are installed and managed using a common set of tools and workflows. The Pkg3 next-generation package manager [[docs](https://julialang.org/Pkg3.jl/latest/), [repo](https://github.com/JuliaLang/Pkg3.jl)] ships with Julia 0.7/1.0 and lets you install and manage dependencies of your projects, by creating and manipulating project files, which describe what your project depends on, and manifest files that snapshot exact versions of your project's complete dependency graph. + +One consequence of federation is that there cannot be a central authority for package naming. Different entities may use the same name to refer to unrelated packages. This possibility is unavoidable since these entities do not coordinate and may not even know about each other. Because of the lack of a central naming authority, a single project can quite possibly end up depending on different packages with the same name. Julia's package loading mechanism handles this by not requiring package names to be globally unique, even within the dependency graph of a single project. Instead, packages are identified by [universally unique identifiers](https://en.wikipedia.org/wiki/Universally_unique_identifier) (UUIDs) which are assigned to them before they are registered. The question *"what is `X`?"* is answered by determining the UUID of `X`. + +Since the decentralized naming problem is somewhat abstract, it may help to walk through a concrete scenario to understand the issue. Suppose you're developing an application called `App`, which uses two packages: `Pub` and `Priv`. `Priv` is a private package that you created, whereas `Pub` is a public package that you use but don't control. When you created `Priv`, there was no public package by that name. Subsequently, however, an unrelated package also named `Priv` has been published and become popular. In fact, the `Pub` package has started to use it. Therefore, when you next upgrade `Pub` to get the latest bug fixes and features, `App` will end up—through no action of yours other than upgrading—depending on two different packages named `Priv`. `App` has a direct dependency on your private `Priv` package, and an indirect dependency, through `Pub`, on the new public `Priv` package. Since these two `Priv` packages are different but both required for `App` to continue working correctly, the expression `import Priv` must refer to different `Priv` packages depending on whether it occurs in `App`'s code or in `Pub`'s code. Julia's package loading mechanism allows this by distinguishing the two `Priv` packages by context and UUID. How this distinction works is determined by environments, as explained in the following sections. + +## Environments + +An *environment* determines what `import X` and `using X` mean in various code contexts and what files these statements cause to be loaded. Julia understands three kinds of environments: + +1. **A project environment** is a directory with a project file and an optional manifest file. The project file determines what the names and identities of the direct dependencies of a project are. The manifest file, if present, gives a complete dependency graph, including all direct and indirect dependencies, exact versions of each dependency, and sufficient information to locate and load the correct version. +2. **A package directory** is a directory containing the source trees of a set of packages as subdirectories. This kind of environment was the only kind that existed in Julia 0.6 and earlier. If `X` is a subdirectory of a package directory and `X/src/X.jl` exists, then the package `X` is available in the package directory environment and `X/src/X.jl` is the source file by which it is loaded. +3. **A stacked environment** is an ordered set of project environments and package directories, overlaid to make a single composite environment in which all the packages available in its constituent environments are available. Julia's load path is a stacked environment, for example. + +These three kinds of environment each serve a different purpose: + +* Project environments provide **reproducibility.** By checking a project environment into version control—i.e. a git repository—along with the rest of the project's source code, you can reproduce the exact state of the project _and_ all of its dependencies since the manifest file captures the exact version of every dependency and can be rematerialized easily. +* Package directories provide low-overhead **convenience** when a project environment would be overkill: are handy when you have a set of packages and just want to put them somewhere and use them as they are without having to create and maintain a project environment for them. +* Stacked environments allow for **augmentation** of the primary environment with additional tools. You can push an environment including development tools onto the stack and they will be available from the REPL and scripts but not from inside of packages. + +As an abstraction, an environment provides three maps: `roots`, `graph` and `paths`. When resolving the meaning of `import X`, `roots` and `graph` are used to determine the identity of `X` and answer the question *"what is `X`?"*, while the `paths` map is used to locate the source code of `X` and answer the question *"where is `X`?"* The specific roles of the three maps are: + +- **roots:** `name::Symbol` ⟶ `uuid::UUID` + + An environment's `roots` map assigns package names to UUIDs for all the top-level dependencies that the environment makes available to the main project (i.e. the ones that can be loaded in `Main`). When Julia encounters `import X` in the main project, it looks up the identity of `X` as `roots[:X]`. + +- **graph:** `context::UUID` ⟶ `name::Symbol` ⟶ `uuid::UUID` + + An environment's `graph` is a multilevel map which assigns, for each `context` UUID, a map from names to UUIDs, similar to the `roots` map but specific to that `context`. When Julia sees `import X` in the code of the package whose UUID is `context`, it looks up the identity of `X` as `graph[context][:X]`. In particular, this means that `import X` can refer to different packages depending on `context`. + +- **paths:** `uuid::UUID` × `name::Symbol` ⟶ `path::String` + + The `paths` map assigns to each package UUID-name pair, the location of the entry-point source file of that package. After the identity of `X` in `import X` has been resolved to a UUID via `roots` or `graph` (depending on whether it is loaded from the main project or an dependency), Julia determines what file to load to acquire `X` by looking up `paths[uuid,:X]` in the environment. Including this file should create a module named `X`. After the first time this package is loaded, any import resolving to the same `uuid` will simply create a new binding to the same already-loaded package module. + +Each kind of environment defines these three maps differently, as detailed in the following sections. + +!!! note + For clarity of exposition, the examples throughout this chapter include fully materialized data structures for `roots`, `graph` and `paths`. However, these maps are really only abstractions—for efficiency, Julia's package loading code does not actually materialize them. Instead, it queries them through internal APIs and lazily computes only as much of each structure as is necessary to load a given package. + +### Project environments + +A project environment is determined by a directory containing a project file, `Project.toml`, and optionally a manifest file, `Manifest.toml`. These files can also be named `JuliaProject.toml` and `JuliaManifest.toml`, in which case `Project.toml` and `Manifest.toml` are ignored; this allows for coexistence with other tools that might consider files named `Project.toml` and `Manifest.toml` significant. For pure Julia projects, however, the names `Project.toml` and `Manifest.toml` should be preferred. The `roots`, `graph` and `paths` maps of a project environment are defined as follows. + +**The roots map** of the environment is determined by the contents of the project file, specifically, its top-level `name` and `uuid` entries and its `[deps]` section (all optional). Consider the following example project file for the hypothetical application, `App`, as described above: + +```toml +name = "App" +uuid = "8f986787-14fe-4607-ba5d-fbff2944afa9" + +[deps] +Priv = "ba13f791-ae1d-465a-978b-69c3ad90f72b" +Pub = "c07ecb7d-0dc9-4db7-8803-fadaaeaf08e1" +``` + +This project file implies the following `roots` map, if it were materialized as a Julia dictionary: + +```julia +roots = Dict( + :App => UUID("8f986787-14fe-4607-ba5d-fbff2944afa9"), + :Priv => UUID("ba13f791-ae1d-465a-978b-69c3ad90f72b"), + :Pub => UUID("c07ecb7d-0dc9-4db7-8803-fadaaeaf08e1"), +) +``` + +Given this `roots` map, in the code of `App` the statement `import Priv` will cause Julia to look up `roots[:Priv]`, which yields `ba13f791-ae1d-465a-978b-69c3ad90f72b`, the UUID of the `Priv` package that is to be loaded in that context. This UUID identifies which `Priv` package to load and use when the main application evaluates `import Priv`. + +**The dependency graph** of a project environment is determined by the contents of the manifest file, if present, or if there is no manifest file, `graph` is empty. A manifest file contains a stanza for each direct or indirect dependency of a project, including for each one, its UUID and exact version information and optionally an explicit path to its source code. Consider the following example manifest file for `App`: + +```toml +[[Priv]] # the private one +deps = ["Pub", "Zebra"] +uuid = "ba13f791-ae1d-465a-978b-69c3ad90f72b" +path = "deps/Priv" + +[[Priv]] # the public one +uuid = "2d15fe94-a1f7-436c-a4d8-07a9a496e01c" +git-tree-sha1 = "1bf63d3be994fe83456a03b874b409cfd59a6373" +version = "0.1.5" + +[[Pub]] +uuid = "ba13f791-ae1d-465a-978b-69c3ad90f72b" +git-tree-sha1 = "9ebd50e2b0dd1e110e842df3b433cb5869b0dd38" +version = "2.1.4" + + [Pub.deps] + Priv = "2d15fe94-a1f7-436c-a4d8-07a9a496e01c" + Zebra = "f7a24cb4-21fc-4002-ac70-f0e3a0dd3f62" + +[[Zebra]] +uuid = "f7a24cb4-21fc-4002-ac70-f0e3a0dd3f62" +git-tree-sha1 = "e808e36a5d7173974b90a15a353b564f3494092f" +version = "3.4.2" +``` + +This manifest file describes a possible complete dependency graph for the `App` project: + +- There are two different `Priv` packages that the application needs—a private one which is a direct dependency and a public one which is an indirect dependency through `Pub`: + * The private `Priv` depends on the `Pub` and `Zebra` packages. + * The public `Priv` has no dependencies. +- The application also depends on the `Pub` package, which in turn depends on the public `Priv ` and the same `Zebra` package which the private `Priv` package depends on. + +A materialized representation of this dependency `graph` looks like this: + +```julia +graph = Dict{UUID,Dict{Symbol,UUID}}( + # Priv – the private one: + UUID("ba13f791-ae1d-465a-978b-69c3ad90f72b") => Dict{Symbol,UUID}( + :Zebra => UUID("f7a24cb4-21fc-4002-ac70-f0e3a0dd3f62"), + ), + # Priv – the public one: + UUID("2d15fe94-a1f7-436c-a4d8-07a9a496e01c") => Dict{Symbol,UUID}(), + # Pub: + UUID("ba13f791-ae1d-465a-978b-69c3ad90f72b") => Dict{Symbol,UUID}( + :Priv => UUID("2d15fe94-a1f7-436c-a4d8-07a9a496e01c"), + :Zebra => UUID("f7a24cb4-21fc-4002-ac70-f0e3a0dd3f62"), + ), + # Zebra: + UUID("f7a24cb4-21fc-4002-ac70-f0e3a0dd3f62") => Dict{Symbol,UUID}(), +) +``` + +Given this dependency `graph`, when Julia sees `import Priv` in the `Pub` package—which has UUID `ba13f791-ae1d-465a-978b-69c3ad90f72b`—it looks up: + +```julia +graph[UUID("ba13f791-ae1d-465a-978b-69c3ad90f72b")][:Priv] +``` + +and gets `2d15fe94-a1f7-436c-a4d8-07a9a496e01c` , which indicates that in the context of the `Pub` package, `import Priv` refers to the public `Priv` package, rather than the private one which the app depends on directly. This is how the name `Priv` can refer to different packages in the main project than it does in one of the packages dependencies, which allows for name collisions in the package ecosystem. + +What happens if `import Zebra` is evaluated in the main `App` code base? Since `Zebra` does not appear in the project file, the import will fail even though `Zebra` *does* appear in the manifest file. Moreover, if `import Zebra` occurs in the public `Priv` package—the one with UUID `2d15fe94-a1f7-436c-a4d8-07a9a496e01c`—then that would also fail since that `Priv` package has no declared dependencies in the manifest file and therefore cannot load any packages. The `Zebra` package can only be loaded by packages for which it appear as an explicit dependency in the manifest file: the `Pub` package and one of the `Priv` packages. + +**The paths map** of a project environment is also determined by the manifest file if present and is empty if there is no manifest. The path of a package `uuid` named `X` is determined by these two rules: + +1. If the manifest stanza matching `uuid` has a `path` entry, use that path relative to the manifest file. +2. Otherwise, if the manifest stanza matching `uuid` has a `git-tree-sha1` entry, compute a deterministic hash function of `uuid` and `git-tree-sha1`—call it `slug`—and look for `packages/X/$slug` in each directory in the Julia `DEPOT_PATH` global array. Use the first such directory that exists. + +If applying these rules doesn't find a loadable path, the package should be considered not installed and the system should raise an error or prompt the user to install the appropriate package version. + +In the example manifest file above, to find the path of the first `Priv` package—the one with UUID `ba13f791-ae1d-465a-978b-69c3ad90f72b`—Julia looks for its stanza in the manifest file, sees that it has a `path` entry, looks at `deps/Priv` relative to the `App` project directory—let's suppose the `App` code lives in `/home/me/projects/App`—sees that `/home/me/projects/App/deps/Priv` exists and therefore loads `Priv` from there. + +If, on the other hand, Julia was loading the *other* `Priv` package—the one with UUID `2d15fe94-a1f7-436c-a4d8-07a9a496e01c`—it finds its stanza in the manifest, see that it does *not* have a `path` entry, but that it does have a `git-tree-sha1` entry. It then computes the `slug` for this UUID/SHA-1 pair, which is `HDkr` (the exact details of this computation aren't important, but it is consistent and deterministic). This means that the path to this `Priv` package will be `packages/Priv/HDkr/src/Priv.jl` in one of the package depots. Suppose the contents of `DEPOT_PATH` is `["/users/me/.julia", "/usr/local/julia"]`; then Julia will look at the following paths to see if they exist: + +1. `/home/me/.julia/packages/Priv/HDkr/src/Priv.jl` +2. `/usr/local/julia/packages/Priv/HDkr/src/Priv.jl` + +Julia uses the first of these that exists to load the public `Priv` package. + +Here is a materialized `paths` map for the `App` project environment: + +```julia +paths = Dict{Tuple{UUID,Symbol},String}( + # Priv – the private one: + (UUID("ba13f791-ae1d-465a-978b-69c3ad90f72b"), :Priv) => + # relative entry-point inside `App` repo: + "/home/me/projects/App/deps/Priv/src/Priv.jl", + # Priv – the public one: + (UUID("2d15fe94-a1f7-436c-a4d8-07a9a496e01c"), :Priv) => + # package installed in the user depot: + "/usr/local/julia/packages/Priv/HDkr/src/Priv.jl", + # Pub: + (UUID("ba13f791-ae1d-465a-978b-69c3ad90f72b"), :Pub) => + # package installed in the system depot: + "/home/me/.julia/packages/Pub/oKpw/src/Pub.jl", + # Zebra: + (UUID("f7a24cb4-21fc-4002-ac70-f0e3a0dd3f62"), :Zebra) => + # package installed in the user depot: + "/usr/local/julia/packages/Zebra/me9k/src/Zebra.jl", +) +``` + +This example map includes three different kinds of package locations: + +1. The private `Priv` package is "[vendored](https://stackoverflow.com/a/35109534/659248)" inside of `App` repository. +2. The public `Priv` and `Zebra` packages are in the system depot, where packages installed and managed by the system administrator live. These are available to all users on the system. +3. The `Pub` package is in the user depot, where packages installed by the user live. These are only available to the user who installed them. + +### Package directories + +Package directories provide a kind of environment that approximates package loading in Julia 0.6 and earlier, and which resembles package loading in many other dynamic languages. The set of packages available in a package directory corresponds to the set of subdirectories it contains that look like packages: if `X/src/X.jl` is a file in a package directory, then `X` is considered to be a package and `X/src/X.jl` is the file you load to get `X`. Which packages can "see" each other as dependencies depends on whether they contain project files or not and what appears in the `[deps]` sections of those project files. + +**The roots map** is determined by the subdirectories of a package directory for which `X/src/X.jl` exists and whether `X/Project.toml` exists and has a top-level `uuid` entry. Specifically `:X => uuid` goes in `roots` for each such `X` where `uuid` is defined as: + +1. If `X/Project.toml` exists and has a `uuid` entry, then `uuid` is that value. +2. If `X/Project.toml` exists and but does *not* have a top-level UUID entry, `uuid` is a dummy UUID generated by hashing the canonical path of `X/Project.toml`. +3. If `X/Project.toml` does not exist, then `uuid` is the all-zero [nil UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier#Nil_UUID). + +**The dependency graph** of a project directory is determined by the presence and contents of project files in the subdirectory of each package. The rules are: + +- If a package subdirectory has no project file, then it is omitted from `graph` and import statements in its code are treated as top-level, the same as the main project and REPL. +- If a package subdirectory has a project file, then the `graph` entry for its UUID is the `[deps]` map of the project file, which is considered to be empty if the section is absent. + +As an example, suppose a package directory has the following structure and content: + +``` +Aardvark/ + src/Aardvark.jl: + import Bobcat + import Cobra + +Bobcat/ + Project.toml: + [deps] + Cobra = "4725e24d-f727-424b-bca0-c4307a3456fa" + Dingo = "7a7925be-828c-4418-bbeb-bac8dfc843bc" + + src/Bobcat.jl: + import Cobra + import Dingo + +Cobra/ + Project.toml: + uuid = "4725e24d-f727-424b-bca0-c4307a3456fa" + [deps] + Dingo = "7a7925be-828c-4418-bbeb-bac8dfc843bc" + + src/Cobra.jl: + import Dingo + +Dingo/ + Project.toml: + uuid = "7a7925be-828c-4418-bbeb-bac8dfc843bc" + + src/Dingo.jl: + # no imports +``` + +Here is a corresponding `roots` structure, materialized as a dictionary: + +```julia +roots = Dict{Symbol,UUID}( + :Aardvark => UUID("00000000-0000-0000-0000-000000000000"), # no project file, nil UUID + :Bobcat => UUID("85ad11c7-31f6-5d08-84db-0a4914d4cadf"), # dummy UUID based on path + :Cobra => UUID("4725e24d-f727-424b-bca0-c4307a3456fa"), # UUID from project file + :Dingo => UUID("7a7925be-828c-4418-bbeb-bac8dfc843bc"), # UUID from project file +) +``` + +Here is the corresponding `graph` structure, materialized as a dictionary: + +```julia +graph = Dict{UUID,Dict{Symbol,UUID}}( + # Bobcat: + UUID("85ad11c7-31f6-5d08-84db-0a4914d4cadf") => Dict{Symbol,UUID}( + :Cobra => UUID("4725e24d-f727-424b-bca0-c4307a3456fa"), + :Dingo => UUID("7a7925be-828c-4418-bbeb-bac8dfc843bc"), + ), + # Cobra: + UUID("4725e24d-f727-424b-bca0-c4307a3456fa") => Dict{Symbol,UUID}( + :Dingo => UUID("7a7925be-828c-4418-bbeb-bac8dfc843bc"), + ), + # Dingo: + UUID("7a7925be-828c-4418-bbeb-bac8dfc843bc") => Dict{Symbol,UUID}(), +) +``` + +A few general rules to note: + +1. A package without a project file can depend on any top-level dependency, and since every package in a package directory is available at the top-level, it can import all packages in the environment. +2. A package with a project file cannot depend on one without a project file since packages with project files can only load packages in `graph` and packages without project files do not appear in `graph`. +3. A package with a project file but no explicit UUID can only be depended on by packages without project files since dummy UUIDs assigned to these packages are strictly internal. + +Observe the following specific instances of these rules in our example: + +* `Aardvark` can import on any of `Bobcat`, `Cobra` or `Dingo`; it does import `Bobcat` and `Cobra`. +* `Bobcat` can and does import both `Cobra` and `Dingo`, which both have project files with UUIDs and are declared as dependencies in `Bobcat`'s `[deps]` section. +* `Bobcat` cannot possibly depend on `Aardvark` since `Aardvark` does not have a project file. +* `Cobra` can and does import `Dingo`, which has a project file and UUID, and is declared as a dependency in `Cobra`'s `[deps]` section. +* `Cobra` cannot depend on `Aardvark` or `Bobcat` since neither have real UUIDs. +* `Dingo` cannot import anything because it has a project file without a `[deps]` section. + +**The paths map** in a package directory is simple: it maps subdirectory names to their corresponding entry-point paths. In other words, if the path to our example project directory is `/home/me/animals` then the `paths` map would be materialized as this dictionary: + +```julia +paths = Dict{Tuple{UUID,Symbol},String}( + (UUID("00000000-0000-0000-0000-000000000000"), :Aardvark) => + "/home/me/AnimalPackages/Aardvark/src/Aardvark.jl", + (UUID("85ad11c7-31f6-5d08-84db-0a4914d4cadf"), :Bobcat) => + "/home/me/AnimalPackages/Bobcat/src/Bobcat.jl", + (UUID("4725e24d-f727-424b-bca0-c4307a3456fa"), :Cobra) => + "/home/me/AnimalPackages/Cobra/src/Cobra.jl", + (UUID("7a7925be-828c-4418-bbeb-bac8dfc843bc"), :Dingo) => + "/home/me/AnimalPackages/Dingo/src/Dingo.jl", +) +``` + +Since all packages in a package directory environment are, by definition, subdirectories with the expected entry-point files, their `paths` map entries always have this form. + +### Environment stacks + +The third and final kind of environment is one that combines other environments by overlaying several of them, making the packages in each available in a single composite environment. These composite environments are called *environment stacks*. The Julia `LOAD_PATH` global defines an environment stack—the environment in which the Julia process operates. If you want your Julia process to have access only to the packages in one project or package directory, make it the only entry in `LOAD_PATH`. It is often quite useful, however, to have access to some of your favorite tools—standard libraries, profilers, debuggers, personal utilities, etc.—even if they are not dependencies of the project you're working on. By pushing an environment containing these tools onto the load path, you immediately have access to them in top-level code without needing to add them to your project. + +The mechanism for combining the `roots`, `graph` and `paths` data structures of the components of an environment stack is simple: they are simply merged as dictionaries, favoring earlier entries over later ones in the case of key collisions. In other words, if we have `stack = [env₁, env₂, …]` then we have: + +```julia +roots = reduce(merge, reverse([roots₁, roots₂, …])) +graph = reduce(merge, reverse([graph₁, graph₂, …])) +paths = reduce(merge, reverse([paths₁, paths₂, …])) +``` + +The subscripted `rootsᵢ`, `graphᵢ` and `pathsᵢ` variables correspond to the subscripted environments, `envᵢ`, contained `stack`. The `reverse` is present because `merge` favors the last argument rather than first when there are collisions between keys in its argument dictionaries. That's all there is to stacked environments. There are a couple of noteworthy features of this design: + +1. The *primary environment*—i.e.the first environment in a stack—is faithfully embedded in a stacked environment. The full dependency graph of the first environment in a stack is guaranteed to be included intact in the stacked environment including the same versions of all dependencies. +2. Packages in non-primary environments can end up using incompatible versions of their dependencies even if their own environments are entirely compatible. This can happen when one of their dependencies is shadowed by a version in an earlier environment in the stack. + +Since the primary environment is typically the environment of a project you're working on, while environments later in the stack contain additional tools, this is the right tradeoff: it's better to break your dev tools but keep the project working. When such incompatibilities occur, you'll typically want to upgrade your dev tools to versions that are compatible with the main project. + +## Conclusion + +Federated package management and precise software reproducibility are difficult but worthy goals in a package system. In combination, these goals lead to a more complex package loading mechanism than most dynamic languages have, but it also yields scalability and reproducibility that is more commonly associated with static languages. Fortunately, most Julia users can remain oblivious to the technical details of code loading and simply use the built-in package manager to add a package `X` to the appropriate project and manifest files and then write `import X` to load `X` without a further thought. diff --git a/doc/src/manual/environment-variables.md b/doc/src/manual/environment-variables.md index 79a16cde6c2c5..79bffccc70234 100644 --- a/doc/src/manual/environment-variables.md +++ b/doc/src/manual/environment-variables.md @@ -68,23 +68,13 @@ and a global configuration search path of ### `JULIA_LOAD_PATH` A separated list of absolute paths that are to be appended to the variable -[`LOAD_PATH`](@ref). (In Unix-like systems, the path separator is `:`; in Windows -systems, the path separator is `;`.) The `LOAD_PATH` variable is where -[`Base.require`](@ref) and `Base.load_in_path()` look for code; it defaults to the absolute -paths - -``` -$JULIA_HOME/../local/share/julia/site/v$(VERSION.major).$(VERSION.minor) -$JULIA_HOME/../share/julia/site/v$(VERSION.major).$(VERSION.minor) -``` - -so that, e.g., version 0.6 of Julia on a Linux system with a Julia executable at -`/bin/julia` will have a default `LOAD_PATH` of - -``` -/local/share/julia/site/v0.6 -/share/julia/site/v0.6 -``` +[`LOAD_PATH`](@ref). (In Unix-like systems, the path separator is `:`; in +Windows systems, the path separator is `;`.) The `LOAD_PATH` variable is where +[`Base.require`](@ref) and `Base.load_in_path()` look for code; it defaults to +the absolute path +`$JULIA_HOME/../share/julia/stdlib/v$(VERSION.major).$(VERSION.minor)` so that, +e.g., version 0.7 of Julia on a Linux system with a Julia executable at +`/bin/julia` will have a default `LOAD_PATH` of `/share/julia/stdlib/v0.7`. ### `JULIA_PKGDIR` diff --git a/doc/src/manual/noteworthy-differences.md b/doc/src/manual/noteworthy-differences.md index c87b30c05ac0e..20ba324c098e3 100644 --- a/doc/src/manual/noteworthy-differences.md +++ b/doc/src/manual/noteworthy-differences.md @@ -127,8 +127,9 @@ For users coming to Julia from R, these are some noteworthy differences: matrices, then `A * B` denotes a matrix multiplication in Julia, equivalent to R's `A %*% B`. In R, this same notation would perform an element-wise (Hadamard) product. To get the element-wise multiplication operation, you need to write `A .* B` in Julia. - * Julia performs matrix transposition using the `.'` operator and conjugated transposition using - the `'` operator. Julia's `A.'` is therefore equivalent to R's `t(A)`. + * Julia performs matrix transposition using the `transpose` function and conjugated transposition using + the `'` operator or the `adjoint` function. Julia's `transpose(A)` is therefore equivalent to R's `t(A)`. + Additionally a non-recursive transpose in Julia is provided by the `permutedims` function. * Julia does not require parentheses when writing `if` statements or `for`/`while` loops: use `for i in [1, 2, 3]` instead of `for (i in c(1, 2, 3))` and `if i == 1` instead of `if (i == 1)`. * Julia does not treat the numbers `0` and `1` as Booleans. You cannot write `if (1)` in Julia, @@ -149,7 +150,8 @@ For users coming to Julia from R, these are some noteworthy differences: * The [DataFrames package](https://github.com/JuliaStats/DataFrames.jl) provides data frames. * Generalized linear models are provided by the [GLM package](https://github.com/JuliaStats/GLM.jl). * Julia provides tuples and real hash tables, but not R-style lists. When returning multiple items, - you should typically use a tuple: instead of `list(a = 1, b = 2)`, use `(1, 2)`. + you should typically use a tuple or a named tuple: instead of `list(a = 1, b = 2)`, use `(1, 2)` + or `(a=1, b=2)`. * Julia encourages users to write their own types, which are easier to use than S3 or S4 objects in R. Julia's multiple dispatch system means that `table(x::TypeA)` and `table(x::TypeB)` act like R's `table.TypeA(x)` and `table.TypeB(x)`. @@ -167,13 +169,14 @@ For users coming to Julia from R, these are some noteworthy differences: * Julia's [`sum`](@ref), [`prod`](@ref), [`maximum`](@ref), and [`minimum`](@ref) are different from their counterparts in R. They all accept one or two arguments. The first argument is an iterable collection such as an array. If there is a second argument, then this argument indicates the - dimensions, over which the operation is carried out. For instance, let `A=[[1 2],[3 4]]` in Julia - and `B=rbind(c(1,2),c(3,4))` be the same matrix in R. Then `sum(A)` gives the same result as - `sum(B)`, but `sum(A, 1)` is a row vector containing the sum over each column and `sum(A, 2)` - is a column vector containing the sum over each row. This contrasts to the behavior of R, where - `sum(B,1)=11` and `sum(B,2)=12`. If the second argument is a vector, then it specifies all the - dimensions over which the sum is performed, e.g., `sum(A,[1,2])=10`. It should be noted that - there is no error checking regarding the second argument. + dimensions, over which the operation is carried out. For instance, let `A = [1 2; 3 4]` in Julia + and `B <- rbind(c(1,2),c(3,4))` be the same matrix in R. Then `sum(A)` gives the same result as + `sum(B)`, but `sum(A, dims=1)` is a row vector containing the sum over each column and `sum(A, dims=2)` + is a column vector containing the sum over each row. This contrasts to the behavior of R, where separate + `colSums(B)` and `rowSums(B)` functions provide these functionalities. If the `dims` keyword argument is a + vector, then it specifies all the dimensions over which the sum is performed, while retaining the + dimensions of the summed array, e.g. `sum(A, dims=(1,2)) == hcat(10)`. It should be noted that there is no + error checking regarding the second argument. * Julia has several functions that can mutate their arguments. For example, it has both [`sort`](@ref) and [`sort!`](@ref). * In R, performance requires vectorization. In Julia, almost the opposite is true: the best performing diff --git a/src/ast.scm b/src/ast.scm index d5016348c73c8..f5910933fc9c8 100644 --- a/src/ast.scm +++ b/src/ast.scm @@ -179,6 +179,13 @@ "")) "") (string.rep " " ilvl) "end")) + ((do) + (let ((call (cadr e)) + (args (cdr (cadr (caddr e)))) + (body (caddr (caddr e)))) + (deparse-block (string (deparse call) " do" (if (null? args) "" " ") + (deparse-arglist args)) + (cdr body) ilvl))) ((struct) (string (if (eq? (cadr e) 'true) "mutable " "") "struct " diff --git a/src/common_symbols2.inc b/src/common_symbols2.inc index a71c1b0d57958..0e6333c33209f 100644 --- a/src/common_symbols2.inc +++ b/src/common_symbols2.inc @@ -127,7 +127,6 @@ jl_symbol("pipe_writer"), jl_symbol("idxfloor"), jl_symbol("id"), jl_symbol("ComplexF32"), -jl_symbol("/home/jeff/src/julia/usr/share/julia/site/v0.7/Pkg/src/resolve/versionweight.jl"), jl_symbol("Int32"), jl_symbol("indices.jl"), jl_symbol("iobuffer.jl"), diff --git a/src/dump.c b/src/dump.c index a68163e6fb6fe..54135fc445cac 100644 --- a/src/dump.c +++ b/src/dump.c @@ -2300,7 +2300,8 @@ JL_DLLEXPORT jl_array_t *jl_compress_ast(jl_method_t *m, jl_code_info_t *code) size_t nf = jl_datatype_nfields(jl_code_info_type); for (i = 0; i < nf - 5; i++) { - jl_serialize_value_(&s, jl_get_nth_field((jl_value_t*)code, i), 1); + int copy = (i != 2); // don't copy contents of method_for_inference_limit_heuristics field + jl_serialize_value_(&s, jl_get_nth_field((jl_value_t*)code, i), copy); } ios_putc('\0', s.s); diff --git a/src/jltypes.c b/src/jltypes.c index 5d72efd4bf60b..94a113fb09df2 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -2023,20 +2023,21 @@ void jl_init_types(void) jl_code_info_type = jl_new_datatype(jl_symbol("CodeInfo"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(12, + jl_perm_symsvec(13, "code", "codelocs", - "signature_for_inference_heuristics", + "method_for_inference_limit_heuristics", "slottypes", "ssavaluetypes", "linetable", + "ssaflags", "slotflags", "slotnames", "inferred", "inlineable", "propagate_inbounds", "pure"), - jl_svec(12, + jl_svec(13, jl_array_any_type, jl_any_type, jl_any_type, @@ -2044,6 +2045,7 @@ void jl_init_types(void) jl_any_type, jl_any_type, jl_array_uint8_type, + jl_array_uint8_type, // Note: The following fields have special serialization. // If you change them, you'll have to adjust the // serializer @@ -2052,7 +2054,7 @@ void jl_init_types(void) jl_bool_type, jl_bool_type, jl_bool_type), - 0, 1, 12); + 0, 1, 13); jl_method_type = jl_new_datatype(jl_symbol("Method"), core, diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 99133c79f60d0..6b9a06863c795 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -862,6 +862,16 @@ (if (not (symbol? v)) (error (string "field name \"" (deparse v) "\" is not a symbol")))) field-names) + (for-each (lambda (t) + (if (expr-contains-p (lambda (e) + (and (pair? e) (eq? (car e) 'call) + (expr-contains-p (lambda (a) (memq a params)) + e))) + t) + (error (string "unsupported field type expression \"" + (deparse t) + "\"")))) + field-types) `(block (scope-block (block diff --git a/src/julia.h b/src/julia.h index 17c8d2075dbb9..6c8b26bd37e68 100644 --- a/src/julia.h +++ b/src/julia.h @@ -235,10 +235,16 @@ typedef struct _jl_llvm_functions_t { typedef struct _jl_code_info_t { jl_array_t *code; // Any array of statements jl_value_t *codelocs; // Int array of indicies into the line table - jl_value_t *signature_for_inference_heuristics; // optional method used during inference + jl_value_t *method_for_inference_limit_heuristics; // optional method used during inference jl_value_t *slottypes; // types of variable slots (or `nothing`) jl_value_t *ssavaluetypes; // types of ssa values (or count of them) jl_value_t *linetable; // Table of locations + jl_array_t *ssaflags; // flags associated with each statement: + // 0 = inbounds + // 1,2 = inlinehint,always-inline,noinline + // 3 = strict-ieee (strictfp) + // 4-6 = + // 7 = has out-of-band info jl_array_t *slotflags; // local var bit flags jl_array_t *slotnames; // names of local variables uint8_t inferred; diff --git a/src/llvm-late-gc-lowering.cpp b/src/llvm-late-gc-lowering.cpp index df95eb8521136..d429a7fbaef17 100644 --- a/src/llvm-late-gc-lowering.cpp +++ b/src/llvm-late-gc-lowering.cpp @@ -1749,7 +1749,14 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S) { old_attrs.getFnAttributes()); NewCall->setAttributes(attr); #endif - NewCall->setDebugLoc(CI->getDebugLoc()); +#if JL_LLVM_VERSION >= 40000 + NewCall->copyMetadata(CI); +#else + SmallVector, 1> MDs; + CI->getAllMetadata(MDs); + for (auto MD : MDs) + NewCall->setMetadata(MD.first, MD.second); +#endif CI->replaceAllUsesWith(NewCall); UpdatePtrNumbering(CI, NewCall, S); } else if (CI->getNumArgOperands() == CI->getNumOperands()) { @@ -1759,6 +1766,14 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S) { } else { CallInst *NewCall = CallInst::Create(CI, None, CI); NewCall->takeName(CI); +#if JL_LLVM_VERSION >= 40000 + NewCall->copyMetadata(CI); +#else + SmallVector, 1> MDs; + CI->getAllMetadata(MDs); + for (auto MD : MDs) + NewCall->setMetadata(MD.first, MD.second); +#endif CI->replaceAllUsesWith(NewCall); UpdatePtrNumbering(CI, NewCall, S); } diff --git a/src/method.c b/src/method.c index 45b9678250578..044dea93da60c 100644 --- a/src/method.c +++ b/src/method.c @@ -232,7 +232,7 @@ static void jl_code_info_set_ast(jl_code_info_t *li, jl_expr_t *ast) jl_array_del_end(meta, na - ins); } } - li->signature_for_inference_heuristics = jl_nothing; + li->method_for_inference_limit_heuristics = jl_nothing; jl_array_t *vinfo = (jl_array_t*)jl_exprarg(ast, 1); jl_array_t *vis = (jl_array_t*)jl_array_ptr_ref(vinfo, 0); size_t nslots = jl_array_len(vis); @@ -248,6 +248,7 @@ static void jl_code_info_set_ast(jl_code_info_t *li, jl_expr_t *ast) jl_gc_wb(li, li->ssavaluetypes); li->linetable = jl_nothing; li->codelocs = jl_nothing; + li->ssaflags = jl_alloc_array_1d(jl_array_uint8_type, 0); // Flags that need to be copied to slotflags const uint8_t vinfo_mask = 16 | 32 | 64; @@ -303,13 +304,14 @@ JL_DLLEXPORT jl_code_info_t *jl_new_code_info_uninit(void) (jl_code_info_t*)jl_gc_alloc(ptls, sizeof(jl_code_info_t), jl_code_info_type); src->code = NULL; - src->signature_for_inference_heuristics = NULL; + src->method_for_inference_limit_heuristics = NULL; src->slotnames = NULL; src->slotflags = NULL; src->slottypes = NULL; src->ssavaluetypes = NULL; src->codelocs = jl_nothing; src->linetable = jl_nothing; + src->ssaflags = NULL; src->inferred = 0; src->pure = 0; src->inlineable = 0; diff --git a/src/toplevel.c b/src/toplevel.c index e8f368b9c26d4..ea3e26992234e 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -615,9 +615,10 @@ static jl_code_info_t *expr_to_code_info(jl_value_t *expr) jl_gc_wb(src, src->slotflags); src->ssavaluetypes = jl_box_long(0); jl_gc_wb(src, src->ssavaluetypes); - src->signature_for_inference_heuristics = jl_nothing; + src->method_for_inference_limit_heuristics = jl_nothing; src->codelocs = jl_nothing; src->linetable = jl_nothing; + src->ssaflags = jl_alloc_array_1d(jl_array_uint8_type, 0); JL_GC_POP(); return src; diff --git a/stdlib/Dates/src/types.jl b/stdlib/Dates/src/types.jl index d6ea6a875926f..a21ef5de353ef 100644 --- a/stdlib/Dates/src/types.jl +++ b/stdlib/Dates/src/types.jl @@ -339,7 +339,6 @@ Base.typemin(::Union{Date, Type{Date}}) = Date(-252522163911150, 1, 1) Base.typemax(::Union{Time, Type{Time}}) = Time(23, 59, 59, 999, 999, 999) Base.typemin(::Union{Time, Type{Time}}) = Time(0) # Date-DateTime promotion, isless, == -Base.eltype(::Type{T}) where {T<:Period} = T Base.promote_rule(::Type{Date}, x::Type{DateTime}) = DateTime Base.isless(x::T, y::T) where {T<:TimeType} = isless(value(x), value(y)) Base.isless(x::TimeType, y::TimeType) = isless(promote(x, y)...) diff --git a/stdlib/Distributed/src/clusterserialize.jl b/stdlib/Distributed/src/clusterserialize.jl index e961b4c7a8614..dcc4f4721dba9 100644 --- a/stdlib/Distributed/src/clusterserialize.jl +++ b/stdlib/Distributed/src/clusterserialize.jl @@ -126,7 +126,7 @@ function syms_2b_sent(s::ClusterSerializer, identifier) for sym in check_syms v = getfield(Main, sym) - if isbits(v) + if isbitstype(typeof(v)) push!(lst, sym) else oid = objectid(v) @@ -146,7 +146,7 @@ function serialize_global_from_main(s::ClusterSerializer, sym) oid = objectid(v) record_v = true - if isbits(v) + if isbitstype(typeof(v)) record_v = false elseif !haskey(s.glbs_sent, oid) # set up a finalizer the first time this object is sent diff --git a/stdlib/LinearAlgebra/src/matmul.jl b/stdlib/LinearAlgebra/src/matmul.jl index a345c3d7aac80..9619a5b630987 100644 --- a/stdlib/LinearAlgebra/src/matmul.jl +++ b/stdlib/LinearAlgebra/src/matmul.jl @@ -526,7 +526,7 @@ function _generic_matmatmul!(C::AbstractVecOrMat{R}, tA, tB, A::AbstractVecOrMat end tile_size = 0 - if isbits(R) && isbits(T) && isbits(S) && (tA == 'N' || tB != 'N') + if isbitstype(R) && isbitstype(T) && isbitstype(S) && (tA == 'N' || tB != 'N') tile_size = floor(Int, sqrt(tilebufsize / max(sizeof(R), sizeof(S), sizeof(T)))) end @inbounds begin diff --git a/stdlib/Mmap/src/Mmap.jl b/stdlib/Mmap/src/Mmap.jl index 9db8d0af97a4b..993c689de379f 100644 --- a/stdlib/Mmap/src/Mmap.jl +++ b/stdlib/Mmap/src/Mmap.jl @@ -186,7 +186,7 @@ function mmap(io::IO, offset::Integer=position(io); grow::Bool=true, shared::Bool=true) where {T,N} # check inputs isopen(io) || throw(ArgumentError("$io must be open to mmap")) - isbits(T) || throw(ArgumentError("unable to mmap $T; must satisfy isbits(T) == true")) + isbitstype(T) || throw(ArgumentError("unable to mmap $T; must satisfy isbitstype(T) == true")) len = prod(dims) * sizeof(T) len >= 0 || throw(ArgumentError("requested size must be ≥ 0, got $len")) diff --git a/stdlib/Pkg3/bin/genstdlib.jl b/stdlib/Pkg3/bin/genstdlib.jl index 42d4dbea02689..03ed241b27bb0 100644 --- a/stdlib/Pkg3/bin/genstdlib.jl +++ b/stdlib/Pkg3/bin/genstdlib.jl @@ -6,7 +6,7 @@ prefix = joinpath(homedir(), ".julia", "registries", "Stdlib") # TODO: use Sys.STDLIBDIR instead once implemented let vers = "v$(VERSION.major).$(VERSION.minor)" -global stdlibdir = realpath(abspath(Sys.BINDIR, "..", "share", "julia", "site", vers)) +global stdlibdir = realpath(abspath(Sys.BINDIR, "..", "share", "julia", "stdlib", vers)) isdir(stdlibdir) || error("stdlib directory does not exist: $stdlibdir") end juliadir = dirname(stdlibdir) diff --git a/stdlib/Pkg3/src/API.jl b/stdlib/Pkg3/src/API.jl index 5ed67909c99aa..bc4d651a05bfc 100644 --- a/stdlib/Pkg3/src/API.jl +++ b/stdlib/Pkg3/src/API.jl @@ -352,7 +352,7 @@ function build(ctx::Context, pkgs::Vector{PackageSpec}; kwargs...) end init() = init(Context()) -init(path::String) = init(Context(), path) +init(path::String) = init(Context(env=EnvCache(path)), path) function init(ctx::Context, path::String=pwd()) print_first_command_header() Context!(ctx; env = EnvCache(joinpath(path, "Project.toml"))) diff --git a/stdlib/Pkg3/src/Operations.jl b/stdlib/Pkg3/src/Operations.jl index 6b8e8fbb9b476..b7c879c9a2496 100644 --- a/stdlib/Pkg3/src/Operations.jl +++ b/stdlib/Pkg3/src/Operations.jl @@ -753,9 +753,8 @@ function with_dependencies_loadable_at_toplevel(f, mainctx::Context, pkg::Packag end write_env(localctx, display_diff = false) will_resolve && build_versions(localctx, new) - withenv("JULIA_LOAD_PATH" => joinpath(tmpdir)) do - f() - end + sep = Sys.iswindows() ? ';' : ':' + withenv(f, "JULIA_LOAD_PATH" => "$tmpdir$sep$(Types.stdlib_dir())") end end @@ -823,7 +822,6 @@ function build_versions(ctx::Context, uuids::Vector{UUID}; might_need_to_resolve log_file = splitext(build_file)[1] * ".log" printpkgstyle(ctx, :Building, rpad(name * " ", max_name + 1, "─"), "→ ", Types.pathrepr(ctx, log_file)) - code = """ empty!(Base.DEPOT_PATH) append!(Base.DEPOT_PATH, $(repr(map(abspath, DEPOT_PATH)))) diff --git a/stdlib/Pkg3/src/Types.jl b/stdlib/Pkg3/src/Types.jl index dd81413fb5be8..186e544d4fe98 100644 --- a/stdlib/Pkg3/src/Types.jl +++ b/stdlib/Pkg3/src/Types.jl @@ -467,17 +467,33 @@ mutable struct EnvCache for entry in LOAD_PATH project_file = Base.find_env(entry) project_file isa String && !isdir(project_file) && break + project_file = nothing + end + if project_file == nothing + project_dir = nothing + for entry in LOAD_PATH + project_dir = Base.find_env(entry) + project_dir isa String && isdir(project_dir) && break + project_dir = nothing + end + project_dir == nothing && error("No Pkg3 environment found in LOAD_PATH") + project_file = joinpath(project_dir, Base.project_names[end]) end - project_file === nothing && error("No Pkg3 environment found in LOAD_PATH") elseif env isa AbstractEnv project_file = Base.find_env(env) project_file === nothing && error("package environment does not exist: $env") elseif env isa String - isdir(env) && error("environment is a package directory: $env") - project_file = endswith(env, ".toml") ? abspath(env) : - abspath(env, Base.project_names[end]) + if isdir(env) + isempty(readdir(env)) || error("environment is a package directory: $env") + project_file = joinpath(env, Base.project_names[end]) + else + project_file = endswith(env, ".toml") ? abspath(env) : + abspath(env, Base.project_names[end]) + end end - @assert project_file isa String && (isfile(project_file) || !ispath(project_file)) + @assert project_file isa String && + (isfile(project_file) || !ispath(project_file) || + isdir(project_file) && isempty(readdir(project_file))) project_dir = dirname(project_file) git = ispath(joinpath(project_dir, ".git")) ? LibGit2.GitRepo(project_dir) : nothing @@ -529,7 +545,7 @@ is_project_uuid(env::EnvCache, uuid::UUID) = ########### # Context # ########### -stdlib_dir() = joinpath(Sys.BINDIR, "..", "share", "julia", "site", "v$(VERSION.major).$(VERSION.minor)") +stdlib_dir() = joinpath(Sys.BINDIR, "..", "share", "julia", "stdlib", "v$(VERSION.major).$(VERSION.minor)") stdlib_path(stdlib::String) = joinpath(stdlib_dir(), stdlib) function gather_stdlib_uuids() stdlibs = Dict{UUID,String}() diff --git a/stdlib/Pkg3/test/utils.jl b/stdlib/Pkg3/test/utils.jl index 0a072405bf74b..077bad14663ef 100644 --- a/stdlib/Pkg3/test/utils.jl +++ b/stdlib/Pkg3/test/utils.jl @@ -13,8 +13,7 @@ function temp_pkg_dir(fn::Function) pushfirst!(DEPOT_PATH, depot_dir) # Add the standard library paths back vers = "v$(VERSION.major).$(VERSION.minor)" - push!(LOAD_PATH, abspath(Sys.BINDIR, "..", "local", "share", "julia", "site", vers)) - push!(LOAD_PATH, abspath(Sys.BINDIR, "..", "share", "julia", "site", vers)) + push!(LOAD_PATH, abspath(Sys.BINDIR, "..", "share", "julia", "stdlib", vers)) fn(env_dir) end end @@ -34,4 +33,4 @@ function cd_tempdir(f) end end -isinstalled(pkg) = Base.locate_package(Base.PkgId(pkg.uuid, pkg.name)) !== nothing \ No newline at end of file +isinstalled(pkg) = Base.locate_package(Base.PkgId(pkg.uuid, pkg.name)) !== nothing diff --git a/stdlib/Random/src/RNGs.jl b/stdlib/Random/src/RNGs.jl index f4e4fda97210d..2c2da8134932a 100644 --- a/stdlib/Random/src/RNGs.jl +++ b/stdlib/Random/src/RNGs.jl @@ -437,7 +437,7 @@ end # fills up A reinterpreted as an array of Float64 with n64 values function _rand!(r::MersenneTwister, A::Array{T}, n64::Int, I::FloatInterval_64) where T # n64 is the length in terms of `Float64` of the target - @assert sizeof(Float64)*n64 <= sizeof(T)*length(A) && isbits(T) + @assert sizeof(Float64)*n64 <= sizeof(T)*length(A) && isbitstype(T) GC.@preserve A rand!(r, UnsafeView{Float64}(pointer(A), n64), SamplerTrivial(I)) A end diff --git a/stdlib/Serialization/src/Serialization.jl b/stdlib/Serialization/src/Serialization.jl index 062fdccf93a68..dfda8bd9b1410 100644 --- a/stdlib/Serialization/src/Serialization.jl +++ b/stdlib/Serialization/src/Serialization.jl @@ -259,7 +259,7 @@ function serialize(s::AbstractSerializer, a::Array) else serialize(s, length(a)) end - if isbits(elty) + if isbitstype(elty) serialize_array_data(s.io, a) else sizehint!(s.table, div(length(a),4)) # prepare for lots of pointers @@ -918,7 +918,7 @@ function deserialize_array(s::AbstractSerializer) elty = UInt8 end if isa(d1, Integer) - if elty !== Bool && isbits(elty) + if elty !== Bool && isbitstype(elty) a = Vector{elty}(undef, d1) s.table[slot] = a return read!(s.io, a) @@ -927,7 +927,7 @@ function deserialize_array(s::AbstractSerializer) else dims = convert(Dims, d1)::Dims end - if isbits(elty) + if isbitstype(elty) n = prod(dims)::Int if elty === Bool && n > 0 A = Array{Bool, length(dims)}(undef, dims) @@ -1135,7 +1135,7 @@ function deserialize(s::AbstractSerializer, t::DataType) end if nf == 0 return ccall(:jl_new_struct, Any, (Any,Any...), t) - elseif isbits(t) + elseif isbitstype(t) if nf == 1 f1 = deserialize(s) return ccall(:jl_new_struct, Any, (Any,Any...), t, f1) diff --git a/stdlib/SharedArrays/src/SharedArrays.jl b/stdlib/SharedArrays/src/SharedArrays.jl index 6af20641df60e..6874c4dc27235 100644 --- a/stdlib/SharedArrays/src/SharedArrays.jl +++ b/stdlib/SharedArrays/src/SharedArrays.jl @@ -104,7 +104,7 @@ beginning of the file. SharedArray function SharedArray{T,N}(dims::Dims{N}; init=false, pids=Int[]) where {T,N} - isbits(T) || throw(ArgumentError("type of SharedArray elements must be bits types, got $(T)")) + isbitstype(T) || throw(ArgumentError("type of SharedArray elements must be bits types, got $(T)")) pids, onlocalhost = shared_pids(pids) @@ -178,7 +178,7 @@ function SharedArray{T,N}(filename::AbstractString, dims::NTuple{N,Int}, offset: if !isabspath(filename) throw(ArgumentError("$filename is not an absolute path; try abspath(filename)?")) end - if !isbits(T) + if !isbitstype(T) throw(ArgumentError("type of SharedArray elements must be bits types, got $(T)")) end diff --git a/test/ccall.jl b/test/ccall.jl index 10cabf4580be6..9b1464e061004 100644 --- a/test/ccall.jl +++ b/test/ccall.jl @@ -1424,3 +1424,12 @@ macro cglobal26297(sym) end cglobal26297() = @cglobal26297(:global_var) @test cglobal26297() != C_NULL + +# issue #26607 +noop_func_26607 = () -> nothing +function callthis_26607(args) + @cfunction(noop_func_26607, Cvoid, ()) + return nothing +end +@test callthis_26607(Int64(0)) === nothing +@test callthis_26607(Int32(0)) === nothing diff --git a/test/choosetests.jl b/test/choosetests.jl index 6a46f2db4f81c..9760e663b15f4 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -2,7 +2,7 @@ using Random, Sockets -const STDLIB_DIR = joinpath(Sys.BINDIR, "..", "share", "julia", "site", "v$(VERSION.major).$(VERSION.minor)") +const STDLIB_DIR = joinpath(Sys.BINDIR, "..", "share", "julia", "stdlib", "v$(VERSION.major).$(VERSION.minor)") const STDLIBS = readdir(STDLIB_DIR) """ diff --git a/test/compiler/compiler.jl b/test/compiler/compiler.jl index f93325681d53a..2d576b07339ef 100644 --- a/test/compiler/compiler.jl +++ b/test/compiler/compiler.jl @@ -1310,73 +1310,119 @@ function _generated_stub(gen::Symbol, args::Vector{Any}, params::Vector{Any}, li return Expr(:meta, :generated, stub) end -f24852_kernel(x, y) = x * y - -function f24852_kernel_cinfo(x, y) - sig, spvals, method = Base._methods_by_ftype(Tuple{typeof(f24852_kernel),x,y}, -1, typemax(UInt))[1] +f24852_kernel1(x, y::Tuple) = x * y[1][1][1] +f24852_kernel2(x, y::Tuple) = f24852_kernel1(x, (y,)) +f24852_kernel3(x, y::Tuple) = f24852_kernel2(x, (y,)) +f24852_kernel(x, y::Number) = f24852_kernel3(x, (y,)) + +function f24852_kernel_cinfo(fsig::Type) + world = typemax(UInt) # FIXME + sig, spvals, method = Base._methods_by_ftype(fsig, -1, world)[1] + isdefined(method, :source) || return (nothing, :(f(x, y))) code_info = Base.uncompressed_ast(method) body = Expr(:block, code_info.code...) - Base.Core.Compiler.substitute!(body, 0, Any[], sig, Any[spvals...], 0, :propagate) + Base.Core.Compiler.substitute!(body, 0, Any[], sig, Any[spvals...], 1, :propagate) + if startswith(String(method.name), "f24852") + for a in body.args + if a isa Expr && a.head == :(=) + a = a.args[2] + end + if a isa Expr && length(a.args) === 3 && a.head === :call + pushfirst!(a.args, Core.SlotNumber(1)) + end + end + end + pushfirst!(code_info.slotnames, Symbol("#self#")) + pushfirst!(code_info.slotflags, 0x00) return method, code_info end -function f24852_gen_cinfo_uninflated(X, Y, f, x, y) - _, code_info = f24852_kernel_cinfo(x, y) +function f24852_gen_cinfo_uninflated(X, Y, _, f, x, y) + _, code_info = f24852_kernel_cinfo(Tuple{f, x, y}) return code_info end -function f24852_gen_cinfo_inflated(X, Y, f, x, y) - method, code_info = f24852_kernel_cinfo(x, y) - code_info.signature_for_inference_heuristics = Core.Compiler.svec(f, (x, y), typemax(UInt)) +function f24852_gen_cinfo_inflated(X, Y, _, f, x, y) + method, code_info = f24852_kernel_cinfo(Tuple{f, x, y}) + code_info.method_for_inference_limit_heuristics = method return code_info end -function f24852_gen_expr(X, Y, f, x, y) - return :(f24852_kernel(x::$X, y::$Y)) +function f24852_gen_expr(X, Y, _, f, x, y) # deparse f(x::X, y::Y) where {X, Y} + if f === typeof(f24852_kernel) + f2 = :f24852_kernel3 + elseif f === typeof(f24852_kernel3) + f2 = :f24852_kernel2 + elseif f === typeof(f24852_kernel2) + f2 = :f24852_kernel1 + elseif f === typeof(f24852_kernel1) + return :((x::$X) * (y::$Y)[1][1][1]) + else + return :(error(repr(f))) + end + return :(f24852_late_expr($f2, x::$X, (y::$Y,))) end @eval begin - function f24852_late_expr(x::X, y::Y) where {X, Y} - $(_generated_stub(:f24852_gen_expr, Any[:f24852_late_expr, :x, :y], + function f24852_late_expr(f, x::X, y::Y) where {X, Y} + $(_generated_stub(:f24852_gen_expr, Any[:self, :f, :x, :y], Any[:X, :Y], @__LINE__, QuoteNode(Symbol(@__FILE__)), false)) + $(Expr(:meta, :generated_only)) + #= no body =# end - function f24852_late_inflated(x::X, y::Y) where {X, Y} - $(_generated_stub(:f24852_gen_cinfo_inflated, Any[:f24852_late_inflated, :x, :y], + function f24852_late_inflated(f, x::X, y::Y) where {X, Y} + $(_generated_stub(:f24852_gen_cinfo_inflated, Any[:self, :f, :x, :y], Any[:X, :Y], @__LINE__, QuoteNode(Symbol(@__FILE__)), false)) + $(Expr(:meta, :generated_only)) + #= no body =# end - function f24852_late_uninflated(x::X, y::Y) where {X, Y} - $(_generated_stub(:f24852_gen_cinfo_uninflated, Any[:f24852_late_uninflated, :x, :y], + function f24852_late_uninflated(f, x::X, y::Y) where {X, Y} + $(_generated_stub(:f24852_gen_cinfo_uninflated, Any[:self, :f, :x, :y], Any[:X, :Y], @__LINE__, QuoteNode(Symbol(@__FILE__)), false)) + $(Expr(:meta, :generated_only)) + #= no body =# end end @eval begin - function f24852_early_expr(x::X, y::Y) where {X, Y} - $(_generated_stub(:f24852_gen_expr, Any[:f24852_early_expr, :x, :y], + function f24852_early_expr(f, x::X, y::Y) where {X, Y} + $(_generated_stub(:f24852_gen_expr, Any[:self, :f, :x, :y], Any[:X, :Y], @__LINE__, QuoteNode(Symbol(@__FILE__)), true)) + $(Expr(:meta, :generated_only)) + #= no body =# end - function f24852_early_inflated(x::X, y::Y) where {X, Y} - $(_generated_stub(:f24852_gen_cinfo_inflated, Any[:f24852_early_inflated, :x, :y], + function f24852_early_inflated(f, x::X, y::Y) where {X, Y} + $(_generated_stub(:f24852_gen_cinfo_inflated, Any[:self, :f, :x, :y], Any[:X, :Y], @__LINE__, QuoteNode(Symbol(@__FILE__)), true)) + $(Expr(:meta, :generated_only)) + #= no body =# end - function f24852_early_uninflated(x::X, y::Y) where {X, Y} - $(_generated_stub(:f24852_gen_cinfo_uninflated, Any[:f24852_early_uninflated, :x, :y], + function f24852_early_uninflated(f, x::X, y::Y) where {X, Y} + $(_generated_stub(:f24852_gen_cinfo_uninflated, Any[:self, :f, :x, :y], Any[:X, :Y], @__LINE__, QuoteNode(Symbol(@__FILE__)), true)) + $(Expr(:meta, :generated_only)) + #= no body =# end end x, y = rand(), rand() result = f24852_kernel(x, y) -@test result === f24852_late_expr(x, y) -@test result === f24852_late_uninflated(x, y) -@test result === f24852_late_inflated(x, y) - -@test result === f24852_early_expr(x, y) -@test result === f24852_early_uninflated(x, y) -@test result === f24852_early_inflated(x, y) - -# TODO: test that `expand_early = true` + inflated `signature_for_inference_heuristics` +@test result === f24852_late_expr(f24852_kernel, x, y) +@test Base.return_types(f24852_late_expr, typeof((f24852_kernel, x, y))) == Any[Any] +@test result === f24852_late_uninflated(f24852_kernel, x, y) +@test Base.return_types(f24852_late_uninflated, typeof((f24852_kernel, x, y))) == Any[Any] +@test result === f24852_late_uninflated(f24852_kernel, x, y) +@test Base.return_types(f24852_late_uninflated, typeof((f24852_kernel, x, y))) == Any[Any] + +@test result === f24852_early_expr(f24852_kernel, x, y) +@test Base.return_types(f24852_early_expr, typeof((f24852_kernel, x, y))) == Any[Any] +@test result === f24852_early_uninflated(f24852_kernel, x, y) +@test Base.return_types(f24852_early_uninflated, typeof((f24852_kernel, x, y))) == Any[Any] +@test result === @inferred f24852_early_inflated(f24852_kernel, x, y) +@test Base.return_types(f24852_early_inflated, typeof((f24852_kernel, x, y))) == Any[Float64] + +# TODO: test that `expand_early = true` + inflated `method_for_inference_limit_heuristics` # can be used to tighten up some inference result. # Test that Conditional doesn't get widened to Bool too quickly diff --git a/test/compiler/ssair.jl b/test/compiler/ssair.jl index a1e429dcd3149..695219a5c3090 100644 --- a/test/compiler/ssair.jl +++ b/test/compiler/ssair.jl @@ -22,4 +22,4 @@ let code = Any[ )) Compiler.run_passes(ci, 1, Compiler.LineInfoNode[Compiler.NullLineInfo]) -end \ No newline at end of file +end diff --git a/test/core.jl b/test/core.jl index cf00afbad5661..668221ed7bf0b 100644 --- a/test/core.jl +++ b/test/core.jl @@ -1720,7 +1720,7 @@ mutable struct SIQ{A,B} <: Number end import Base: promote_rule promote_rule(A::Type{SIQ{T,T2}},B::Type{SIQ{S,S2}}) where {T,T2,S,S2} = SIQ{promote_type(T,S)} -@test_throws ErrorException promote_type(SIQ{Int},SIQ{Float64}) +@test promote_type(SIQ{Int},SIQ{Float64}) == SIQ f4731(x::T...) where {T} = "" f4731(x...) = 0 g4731() = f4731() @@ -3350,7 +3350,7 @@ end @noinline throw_error() = error() foo11904(x::Int) = x @inline function foo11904(x::Nullable11904{S}) where S - if isbits(S) + if isbitstype(S) Nullable11904(foo11904(x.value), x.hasvalue) else throw_error() diff --git a/test/enums.jl b/test/enums.jl index 9652ba804aaab..b91a8cc200de9 100644 --- a/test/enums.jl +++ b/test/enums.jl @@ -22,7 +22,8 @@ end @enum Fruit apple orange kiwi @test typeof(Fruit) == DataType -@test isbits(Fruit) +@test isbitstype(Fruit) +@test isbits(apple) @test typeof(apple) <: Fruit <: Enum @test Int(apple) == 0 @test Int(orange) == 1 diff --git a/test/int.jl b/test/int.jl index 2c1a22ed8334e..9ed384e561b1c 100644 --- a/test/int.jl +++ b/test/int.jl @@ -280,3 +280,10 @@ end @test_throws ArgumentError big"1_0_0_0_" @test_throws ArgumentError big"_1_0_0_0" end + +# issue #26779 +struct MyInt26779 <: Integer + x::Int +end +@test promote_type(MyInt26779, Int) == Integer +@test_throws ErrorException MyInt26779(1) + 1 diff --git a/test/reflection.jl b/test/reflection.jl index 3d899dfd66905..fc3e23822e226 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -42,17 +42,21 @@ test_code_reflections(test_ast_reflection, code_typed) end # module ReflectionTest -# isbits - -@test !isbits(Array{Int}) -@test isbits(Float32) -@test isbits(Int) -@test !isbits(AbstractString) -@test isbits(Tuple{Int, Vararg{Int, 2}}) -@test !isbits(Tuple{Int, Vararg{Int}}) -@test !isbits(Tuple{Integer, Vararg{Int, 2}}) -@test isbits(Tuple{Int, Vararg{Any, 0}}) -@test isbits(Tuple{Vararg{Any, 0}}) +# isbits, isbitstype + +@test !isbitstype(Array{Int}) +@test isbitstype(Float32) +@test isbitstype(Int) +@test !isbitstype(AbstractString) +@test isbitstype(Tuple{Int, Vararg{Int, 2}}) +@test !isbitstype(Tuple{Int, Vararg{Int}}) +@test !isbitstype(Tuple{Integer, Vararg{Int, 2}}) +@test isbitstype(Tuple{Int, Vararg{Any, 0}}) +@test isbitstype(Tuple{Vararg{Any, 0}}) +@test isbits(1) +@test isbits((1,2)) +@test !isbits([1]) +@test isbits(nothing) # issue #16670 @test isconcretetype(Int) diff --git a/test/regex.jl b/test/regex.jl index ab800c04ad24f..bd0e87e9a8f87 100644 --- a/test/regex.jl +++ b/test/regex.jl @@ -25,6 +25,14 @@ target = """71.163.72.113 - - [30/Jul/2014:16:40:55 -0700] "GET emptymind.org/th pat = r"""([\d\.]+) ([\w.-]+) ([\w.-]+) (\[.+\]) "([^"\r\n]*|[^"\r\n\[]*\[.+\][^"]+|[^"\r\n]+.[^"]+)" (\d{3}) (\d+|-) ("(?:[^"]|\")+)"? ("(?:[^"]|\")+)"?""" match(pat, target) +# issue #26829 +@test map(m -> m.match, eachmatch(r"^$|\S", "ö")) == ["ö"] + +# issue #26199 +@test map(m -> m.match, eachmatch(r"(\p{L}+)", "Tú")) == ["Tú"] +@test map(m -> m.match, eachmatch(r"(\p{L}+)", "Tú lees.")) == ["Tú", "lees"] +@test map(m -> m.match, eachmatch(r"(\p{L}+)", "¿Cuál es tu pregunta?")) == ["Cuál", "es", "tu", "pregunta"] + # Issue 9545 (32 bit) buf = PipeBuffer() show(buf, r"") diff --git a/test/show.jl b/test/show.jl index d849901deade5..546c1eb94fdb6 100644 --- a/test/show.jl +++ b/test/show.jl @@ -1189,3 +1189,10 @@ end @test repr("text/plain", context=:compact=>true) == "\"text/plain\"" @test repr(MIME("text/plain"), context=:compact=>true) == "MIME type text/plain" end + +@testset "#26799 BigInt summary" begin + @test Base.dims2string(tuple(BigInt(10))) == "10-element" + @test Base.inds2string(tuple(BigInt(10))) == "10" + @test summary(BigInt(1):BigInt(10)) == "10-element UnitRange{BigInt}" + @test summary(Base.OneTo(BigInt(10))) == "10-element Base.OneTo{BigInt}" +end diff --git a/test/syntax.jl b/test/syntax.jl index d8e2c96674cda..9f9f0d9687dde 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -1386,3 +1386,23 @@ end # issue #26717 @test Meta.lower(@__MODULE__, :( :(:) = 2 )) == Expr(:error, "invalid assignment location \":(:)\"") + +# issue #17781 +let ex = Meta.lower(@__MODULE__, Meta.parse(" + A = function (s, o...) + f(a, b) do + end + end, + B = function (s, o...) + f(a, b) do + end + end")) + @test isa(ex, Expr) && ex.head === :error + @test ex.args[1] == """ +invalid assignment location "function (s, o...) + # none, line 3 + f(a, b) do + # none, line 4 + end +end\"""" +end diff --git a/test/threads.jl b/test/threads.jl index 8e20887696877..d41611ac7cb4e 100644 --- a/test/threads.jl +++ b/test/threads.jl @@ -171,10 +171,27 @@ end end # Ensure only LLVM-supported types can be atomic -@test_throws TypeError Atomic{Bool} @test_throws TypeError Atomic{BigInt} @test_throws TypeError Atomic{ComplexF64} +function test_atomic_bools() + x = Atomic{Bool}(false) + # Arithmetic functions are not defined. + @test_throws MethodError atomic_add!(x, true) + @test_throws MethodError atomic_sub!(x, true) + # All the rest are: + for v in [true, false] + @test x[] == atomic_xchg!(x, v) + @test v == atomic_cas!(x, v, !v) + end + x = Atomic{Bool}(false) + @test false == atomic_max!(x, true); @test x[] == true + x = Atomic{Bool}(true) + @test true == atomic_and!(x, false); @test x[] == false +end + +test_atomic_bools() + # Test atomic memory ordering with load/store mutable struct CommBuf var1::Atomic{Int}