diff --git a/Compiler/src/Compiler.jl b/Compiler/src/Compiler.jl index 2c68729ee1dc2..d29f227c79e6b 100644 --- a/Compiler/src/Compiler.jl +++ b/Compiler/src/Compiler.jl @@ -67,7 +67,8 @@ using Base: @_foldable_meta, @_gc_preserve_begin, @_gc_preserve_end, @nospeciali partition_restriction, quoted, rename_unionall, rewrap_unionall, specialize_method, structdiff, tls_world_age, unconstrain_vararg_length, unionlen, uniontype_layout, uniontypes, unsafe_convert, unwrap_unionall, unwrapva, vect, widen_diagonal, - _uncompressed_ir, maybe_add_binding_backedge! + _uncompressed_ir, maybe_add_binding_backedge!, datatype_min_ninitialized, + partialstruct_undef_length, partialstruct_init_undef using Base.Order import Base: ==, _topmod, append!, convert, copy, copy!, findall, first, get, get!, diff --git a/Compiler/src/abstractinterpretation.jl b/Compiler/src/abstractinterpretation.jl index ebfdaba45d34b..3039d17bd9ba1 100644 --- a/Compiler/src/abstractinterpretation.jl +++ b/Compiler/src/abstractinterpretation.jl @@ -2148,23 +2148,13 @@ function form_partially_defined_struct(@nospecialize(obj), @nospecialize(name)) isabstracttype(objt) && return nothing fldidx = try_compute_fieldidx(objt, name.val) fldidx === nothing && return nothing + isa(obj, PartialStruct) && return define_field(obj, fldidx) nminfld = datatype_min_ninitialized(objt) - if ismutabletype(objt) - # A mutable struct can have non-contiguous undefined fields, but `PartialStruct` cannot - # model such a state. So here `PartialStruct` can be used to represent only the - # objects where the field following the minimum initialized fields is also defined. - if fldidx β‰  nminfld+1 - # if it is already represented as a `PartialStruct`, we can add one more - # `isdefined`-field information on top of those implied by its `fields` - if !(obj isa PartialStruct && fldidx == length(obj.fields)+1) - return nothing - end - end - else - fldidx > nminfld || return nothing - end - return PartialStruct(fallback_lattice, objt0, Any[obj isa PartialStruct && i≀length(obj.fields) ? - obj.fields[i] : fieldtype(objt0,i) for i = 1:fldidx]) + fldidx > nminfld || return nothing + undef = partialstruct_init_undef(objt, fldidx; all_defined = false) + undef[fldidx] = false + fields = Any[fieldtype(objt0, i) for i = 1:fldidx] + return PartialStruct(fallback_lattice, objt0, undef, fields) end function abstract_call_unionall(interp::AbstractInterpreter, argtypes::Vector{Any}, call::CallMeta) @@ -3725,8 +3715,7 @@ end @nospecializeinfer function widenreturn_partials(𝕃ᡒ::PartialsLattice, @nospecialize(rt), info::BestguessInfo) if isa(rt, PartialStruct) fields = copy(rt.fields) - anyrefine = !isvarargtype(rt.fields[end]) && - length(rt.fields) > datatype_min_ninitialized(rt.typ) + anyrefine = refines_definedness_information(rt) 𝕃 = typeinf_lattice(info.interp) ⊏ = strictpartialorder(𝕃) for i in 1:length(fields) @@ -3738,7 +3727,7 @@ end end fields[i] = a end - anyrefine && return PartialStruct(𝕃ᡒ, rt.typ, fields) + anyrefine && return PartialStruct(𝕃ᡒ, rt.typ, rt.undef, fields) end if isa(rt, PartialOpaque) return rt # XXX: this case was missed in #39512 diff --git a/Compiler/src/tfuncs.jl b/Compiler/src/tfuncs.jl index cacee7a7d29ac..ff57495f1848f 100644 --- a/Compiler/src/tfuncs.jl +++ b/Compiler/src/tfuncs.jl @@ -439,7 +439,7 @@ end end elseif isa(arg1, PartialStruct) if !isvarargtype(arg1.fields[end]) - if 1 ≀ idx ≀ length(arg1.fields) + if is_field_initialized(arg1, idx) return Const(true) end end @@ -1141,8 +1141,8 @@ end sty = unwrap_unionall(s)::DataType if isa(name, Const) nv = _getfield_fieldindex(sty, name) - if isa(nv, Int) && 1 <= nv <= length(s00.fields) - return unwrapva(s00.fields[nv]) + if isa(nv, Int) && is_field_initialized(s00, nv) + return unwrapva(partialstruct_getfield(s00, nv)) end end s00 = s diff --git a/Compiler/src/typeinfer.jl b/Compiler/src/typeinfer.jl index 49c756af2dd2f..f50a42e7096bc 100644 --- a/Compiler/src/typeinfer.jl +++ b/Compiler/src/typeinfer.jl @@ -515,7 +515,7 @@ function finishinfer!(me::InferenceState, interp::AbstractInterpreter) rettype_const = result_type.parameters[1] const_flags = 0x2 elseif isa(result_type, PartialStruct) - rettype_const = result_type.fields + rettype_const = (result_type.undef, result_type.fields) const_flags = 0x2 elseif isa(result_type, InterConditional) rettype_const = result_type @@ -959,8 +959,9 @@ function cached_return_type(code::CodeInstance) rettype_const = code.rettype_const # the second subtyping/egal conditions are necessary to distinguish usual cases # from rare cases when `Const` wrapped those extended lattice type objects - if isa(rettype_const, Vector{Any}) && !(Vector{Any} <: rettype) - return PartialStruct(fallback_lattice, rettype, rettype_const) + if isa(rettype_const, Tuple{BitVector, Vector{Any}}) && !(Tuple{BitVector, Vector{Any}} <: rettype) + undef, fields = rettype_const + return PartialStruct(fallback_lattice, rettype, undef, fields) elseif isa(rettype_const, PartialOpaque) && rettype <: Core.OpaqueClosure return rettype_const elseif isa(rettype_const, InterConditional) && rettype !== InterConditional diff --git a/Compiler/src/typelattice.jl b/Compiler/src/typelattice.jl index 6f7612b836c89..5c3ff95366d59 100644 --- a/Compiler/src/typelattice.jl +++ b/Compiler/src/typelattice.jl @@ -318,15 +318,15 @@ end fields = vartyp.fields thenfields = thentype === Bottom ? nothing : copy(fields) elsefields = elsetype === Bottom ? nothing : copy(fields) - for i in 1:length(fields) - if i == fldidx - thenfields === nothing || (thenfields[i] = thentype) - elsefields === nothing || (elsefields[i] = elsetype) - end + undef = copy(vartyp.undef) + if 1 ≀ fldidx ≀ length(fields) + thenfields === nothing || (thenfields[fldidx] = thentype) + elsefields === nothing || (elsefields[fldidx] = elsetype) + undef[fldidx] = false end return Conditional(slot, - thenfields === nothing ? Bottom : PartialStruct(fallback_lattice, vartyp.typ, thenfields), - elsefields === nothing ? Bottom : PartialStruct(fallback_lattice, vartyp.typ, elsefields)) + thenfields === nothing ? Bottom : PartialStruct(fallback_lattice, vartyp.typ, undef, thenfields), + elsefields === nothing ? Bottom : PartialStruct(fallback_lattice, vartyp.typ, undef, elsefields)) else vartyp_widened = widenconst(vartyp) thenfields = thentype === Bottom ? nothing : Any[] @@ -431,10 +431,14 @@ end return false end end - for i in 1:length(b.fields) - af = a.fields[i] - bf = b.fields[i] - if i == length(b.fields) + na = length(a.fields) + nb = length(b.fields) + nmax = max(na, nb) + for i in 1:nmax + is_field_initialized(a, i) β‰₯ is_field_initialized(b, i) || return false + af = partialstruct_getfield(a, i) + bf = partialstruct_getfield(b, i) + if i == na || i == nb if isvarargtype(af) # If `af` is vararg, so must bf by the <: above @assert isvarargtype(bf) @@ -464,13 +468,14 @@ end nfields(a.val) == length(b.fields) || return false else widea <: wideb || return false - # for structs we need to check that `a` has more information than `b` that may be partially initialized - n_initialized(a) β‰₯ length(b.fields) || return false + # for structs we need to check that `a` does not have less information than `b` that may be partially initialized + n_initialized(a) β‰₯ n_initialized(b) || return false end nf = nfields(a.val) for i in 1:nf isdefined(a.val, i) || continue # since βˆ€ T Union{} βŠ‘ T i > length(b.fields) && break # `a` has more information than `b` that is partially initialized struct + is_field_initialized(b, i) || continue # `a` gives a decisive answer as to whether the field is defined or undefined bfα΅’ = b.fields[i] if i == nf bfα΅’ = unwrapva(bfα΅’) @@ -541,6 +546,7 @@ end if isa(a, PartialStruct) isa(b, PartialStruct) || return false length(a.fields) == length(b.fields) || return false + a.undef == b.undef || return false widenconst(a) == widenconst(b) || return false a.fields === b.fields && return true # fast path for i in 1:length(a.fields) @@ -747,9 +753,15 @@ end # The ::AbstractLattice argument is unused and simply serves to disambiguate # different instances of the compiler that may share the `Core.PartialStruct` # type. -function Core.PartialStruct(::AbstractLattice, @nospecialize(typ), fields::Vector{Any}) + +function Core.PartialStruct(𝕃::AbstractLattice, @nospecialize(typ), fields::Vector{Any}; all_defined::Bool = true) + undef = partialstruct_init_undef(typ, fields; all_defined) + return PartialStruct(𝕃, typ, undef, fields) +end + +function Core.PartialStruct(::AbstractLattice, @nospecialize(typ), undef::BitVector, fields::Vector{Any}) for i = 1:length(fields) assert_nested_slotwrapper(fields[i]) end - return Core._PartialStruct(typ, fields) + return PartialStruct(typ, undef, fields) end diff --git a/Compiler/src/typelimits.jl b/Compiler/src/typelimits.jl index 536b5fb34d1b1..a30b630b70bd7 100644 --- a/Compiler/src/typelimits.jl +++ b/Compiler/src/typelimits.jl @@ -326,6 +326,62 @@ function n_initialized(t::Const) return something(findfirst(i::Int->!isdefined(t.val,i), 1:nf), nf+1)-1 end +is_field_initialized(t::Const, i) = isdefined(t.val, i) + +function n_initialized(pstruct::PartialStruct) + i = findfirst(pstruct.undef) + nmin = datatype_min_ninitialized(pstruct.typ) + i === nothing && return max(length(pstruct.undef), nmin) + n = i::Int - 1 + @assert n β‰₯ nmin + n +end + +function is_field_initialized(pstruct::PartialStruct, fi) + fi β‰₯ 1 || return false + fi ≀ length(pstruct.undef) && return !pstruct.undef[fi] + fi ≀ datatype_min_ninitialized(pstruct.typ) +end + +function partialstruct_getfield(pstruct::PartialStruct, fi::Integer) + @assert fi > 0 + fi ≀ length(pstruct.fields) && return pstruct.fields[fi] + fieldtype(pstruct.typ, fi) +end + +function refines_definedness_information(pstruct::PartialStruct) + nflds = length(pstruct.undef) + something(findfirst(pstruct.undef), nflds + 1) - 1 > datatype_min_ninitialized(pstruct.typ) +end + +function define_field(pstruct::PartialStruct, fi::Int) + if is_field_initialized(pstruct, fi) + # no new information to be gained + return nothing + end + + new = expand_partialstruct(pstruct, fi) + if new === nothing + new = PartialStruct(fallback_lattice, pstruct.typ, copy(pstruct.undef), copy(pstruct.fields)) + end + new.undef[fi] = false + return new +end + +function expand_partialstruct(pstruct::PartialStruct, until::Int) + n = length(pstruct.undef) + until ≀ n && return nothing + + undef = partialstruct_init_undef(pstruct.typ, until; all_defined = false) + for i in 1:n + undef[i] &= pstruct.undef[i] + end + nf = length(pstruct.fields) + typ = pstruct.typ + fields = Any[i ≀ nf ? pstruct.fields[i] : fieldtype(typ, i) for i in 1:until] + return PartialStruct(fallback_lattice, typ, undef, fields) +end + # A simplified type_more_complex query over the extended lattice # (assumes typeb βŠ‘ typea) @nospecializeinfer function issimplertype(𝕃::AbstractLattice, @nospecialize(typea), @nospecialize(typeb)) @@ -333,10 +389,11 @@ end typea === typeb && return true if typea isa PartialStruct aty = widenconst(typea) - if typeb isa Const - @assert length(typea.fields) ≀ n_initialized(typeb) "typeb βŠ‘ typea is assumed" + if typeb isa Const || typeb isa PartialStruct + @assert n_initialized(typea) ≀ n_initialized(typeb) "typeb βŠ‘ typea is assumed" elseif typeb isa PartialStruct - @assert length(typea.fields) ≀ length(typeb.fields) "typeb βŠ‘ typea is assumed" + @assert n_initialized(typea) ≀ n_initialized(typeb) && + all(b < a for (a, b) in zip(typea.undef, typeb.undef)) "typeb βŠ‘ typea is assumed" else return false end @@ -591,17 +648,24 @@ end if typea isa PartialStruct if typeb isa PartialStruct nflds = min(length(typea.fields), length(typeb.fields)) + nundef = nflds - (isvarargtype(typea.fields[end]) && isvarargtype(typeb.fields[end])) else nflds = min(length(typea.fields), n_initialized(typeb::Const)) + nundef = nflds end elseif typeb isa PartialStruct nflds = min(n_initialized(typea::Const), length(typeb.fields)) + nundef = nflds else nflds = min(n_initialized(typea::Const), n_initialized(typeb::Const)) + nundef = nflds end nflds == 0 && return nothing + _undef = partialstruct_init_undef(aty, nundef; all_defined = false) fields = Vector{Any}(undef, nflds) - anyrefine = nflds > datatype_min_ninitialized(aty) + fldmin = datatype_min_ninitialized(aty) + n_initialized_merged = min(n_initialized(typea::Union{Const, PartialStruct}), n_initialized(typeb::Union{Const, PartialStruct})) + anyrefine = n_initialized_merged > fldmin for i = 1:nflds ai = getfield_tfunc(𝕃, typea, Const(i)) bi = getfield_tfunc(𝕃, typeb, Const(i)) @@ -633,12 +697,16 @@ end end end fields[i] = tyi + if i ≀ nundef + _undef[i] = !is_field_initialized(typea, i) || !is_field_initialized(typeb, i) + end if !anyrefine anyrefine = has_nontrivial_extended_info(𝕃, tyi) || # extended information - β‹€(𝕃, tyi, ft) # just a type-level information, but more precise than the declared type + β‹€(𝕃, tyi, ft) || # just a type-level information, but more precise than the declared type + !get(_undef, i, true) && i > fldmin # possibly uninitialized field is known to be initialized end end - anyrefine && return PartialStruct(𝕃, aty, fields) + anyrefine && return PartialStruct(𝕃, aty, _undef, fields) end return nothing end diff --git a/Compiler/src/typeutils.jl b/Compiler/src/typeutils.jl index 50b3dc6b0c6f5..df2f160989b3a 100644 --- a/Compiler/src/typeutils.jl +++ b/Compiler/src/typeutils.jl @@ -61,39 +61,6 @@ function isknownlength(t::DataType) return isdefined(va, :N) && va.N isa Int end -# Compute the minimum number of initialized fields for a particular datatype -# (therefore also a lower bound on the number of fields) -function datatype_min_ninitialized(@nospecialize t0) - t = unwrap_unionall(t0) - t isa DataType || return 0 - isabstracttype(t) && return 0 - if t.name === _NAMEDTUPLE_NAME - names, types = t.parameters[1], t.parameters[2] - if names isa Tuple - return length(names) - end - t = argument_datatype(types) - t isa DataType || return 0 - t.name === Tuple.name || return 0 - end - if t.name === Tuple.name - n = length(t.parameters) - n == 0 && return 0 - va = t.parameters[n] - if isvarargtype(va) - n -= 1 - if isdefined(va, :N) - va = va.N - if va isa Int - n += va - end - end - end - return n - end - return length(t.name.names) - t.name.n_uninitialized -end - has_concrete_subtype(d::DataType) = d.flags & 0x0020 == 0x0020 # n.b. often computed only after setting the type and layout fields # determine whether x is a valid lattice element diff --git a/Compiler/test/inference.jl b/Compiler/test/inference.jl index b77c99513a8b6..ce6e38bfe1d76 100644 --- a/Compiler/test/inference.jl +++ b/Compiler/test/inference.jl @@ -4727,7 +4727,7 @@ end c = a βŠ” b @test a βŠ‘ c && b βŠ‘ c @test c isa PartialStruct - @test length(c.fields) == 1 + @test length(c.fields) == 1 && c.undef == [0] end let T = Base.ImmutableDict{Number,Number} a = PartialStruct(𝕃, T, Any[T]) @@ -4783,10 +4783,44 @@ end @test a == Tuple end +module _Partials_inference + mutable struct Partial + x::String + y::Integer + z::Any + Partial() = new() + end + + struct Partial2 + x::String + y::Integer + z::Any + Partial2(x) = new(x) + end + + struct Partial3 + x::Int + y::String + z::Float64 + Partial3(x, y) = new(x, y) + end + + struct Partial4 + x::Int + y::String + z::Float64 + Partial4(x) = new(x) + end +end + let βŠ‘ = Compiler.partialorder(Compiler.fallback_lattice) + β‹’ = !βŠ‘ βŠ” = Compiler.join(Compiler.fallback_lattice) 𝕃 = Compiler.fallback_lattice Const, PartialStruct = Core.Const, Core.PartialStruct + form_partially_defined_struct = Compiler.form_partially_defined_struct + M = _Partials_inference + Partial, Partial2, Partial3, Partial4 = M.Partial, M.Partial2, M.Partial3, M.Partial4 @test (Const((1,2)) βŠ‘ PartialStruct(𝕃, Tuple{Int,Int}, Any[Const(1),Int])) @test !(Const((1,2)) βŠ‘ PartialStruct(𝕃, Tuple{Int,Int,Int}, Any[Const(1),Int,Int])) @@ -4807,6 +4841,70 @@ let βŠ‘ = Compiler.partialorder(Compiler.fallback_lattice) @test t isa PartialStruct && length(t.fields) == 2 && t.fields[1] === Const(false) t = t βŠ” Const((false, false, 0)) @test t βŠ‘ Union{Tuple{Bool,Bool},Tuple{Bool,Bool,Int}} + + t = PartialStruct(𝕃, Tuple{Int, Int}, Any[Const(1)]) + @test t.undef == [false] + @test Compiler.is_field_initialized(t, 2) + @test Compiler.n_initialized(t) == 2 + t = PartialStruct(𝕃, Partial, Any[String, Const(2)]) + @test t.undef == [false, false] + @test t.fields == Any[String, Const(2)] + @test t βŠ‘ t && t βŠ” t === t + + t1 = PartialStruct(𝕃, Partial, Any[String, Const(3)]) + t2 = PartialStruct(𝕃, Partial, Any[Const("x")]) + @test t1 β‹’ t2 && t2 β‹’ t1 + t3 = t1 βŠ” t2 + @test t3.fields == Any[String] + + t1 = PartialStruct(𝕃, Partial, BitVector([true, false, false]), Any[String, Int, Const(3)]) + @test Compiler.n_initialized(t1) == 0 + @test t1 βŠ‘ t1 && t1 βŠ” t1 === t1 + t2 = PartialStruct(𝕃, Partial, BitVector([false, true]), Any[Const("x"), Int]) + @test Compiler.n_initialized(t2) == 1 + t3 = t1 βŠ” t2 + @test t3 === Partial + + t1 = PartialStruct(𝕃, Tuple, Any[Int, String, Vararg]) + @test t1.undef == [false, false] + @test t1 βŠ‘ t1 && t1 βŠ” t1 == t1 + t2 = PartialStruct(𝕃, Tuple, Any[Int, Any]) + @test t1 β‹’ t2 && t2 β‹’ t1 + t3 = t1 βŠ” t2 + @test t3.undef == [false, false] && t3.fields == Any[Int, Any] + t2 = PartialStruct(𝕃, Tuple, Any[Int, Any, Vararg]) + @test t1 βŠ‘ t2 + @test t1 βŠ” t2 === t2 + + t = PartialStruct(𝕃, Partial, Any[Const("x")]) + @test form_partially_defined_struct(t, Const(:x)) === nothing + tβ€² = form_partially_defined_struct(t, Const(:z)) + @test tβ€² == PartialStruct(𝕃, Partial, BitVector([false, true, false]), Any[Const("x"), Integer, Any]) + t = PartialStruct(𝕃, Partial, Any[String, Const(2)]) + @test form_partially_defined_struct(t, Const(:x)) === nothing + tβ€² = form_partially_defined_struct(t, Const(:z)) + @test tβ€² == PartialStruct(𝕃, Partial, Any[String, Const(2), Any]) + + t = PartialStruct(𝕃, Partial2, Any[String, Const(2)]) + @test form_partially_defined_struct(t, Const(:x)) === nothing + tβ€² = form_partially_defined_struct(t, Const(:z)) + @test tβ€² == PartialStruct(𝕃, Partial2, Any[String, Const(2), Any]) + + @test form_partially_defined_struct(Partial3, Const(:x)) === nothing + @test form_partially_defined_struct(Partial3, Const(:y)) === nothing + t = form_partially_defined_struct(Partial3, Const(:z)) + @test t == PartialStruct(𝕃, Partial3, Any[Int, String, Float64]) + t = PartialStruct(𝕃, Partial3, Any[Int, String]) + tβ€² = form_partially_defined_struct(t, Const(:z)) + @test tβ€² == PartialStruct(𝕃, Partial3, Any[Int, String, Float64]) + + t1 = PartialStruct(𝕃, Partial4, Any[Int, String]) + t2 = PartialStruct(𝕃, Partial4, Any[Const(1)]) + @test t1 β‹’ t2 && t2 β‹’ t1 + c = Const(Partial4(1)) + @test c β‹’ t1 && t1 β‹’ c && c βŠ‘ t2 && t2 β‹’ c + t3 = PartialStruct(𝕃, Partial4, Any[Const(1), Const("x")]) + @test c β‹’ t3 && t3 β‹’ c end # Test that a function-wise `@max_methods` works as expected diff --git a/Compiler/test/irpasses.jl b/Compiler/test/irpasses.jl index 445ad10307807..f746e4a1d8b9b 100644 --- a/Compiler/test/irpasses.jl +++ b/Compiler/test/irpasses.jl @@ -2043,6 +2043,26 @@ let src = code_typed1(()) do @test count(iscall((src, setfield!)), src.code) == 1 end +module _Partials_irpasses + mutable struct Partial + x::String + y::Integer + z::Any + Partial() = new() + end +end + +# once `isdefined(p, name)` holds, this information should be kept +# as a `PartialStruct` over `p` for subsequent constant propagation. +let src = code_typed1(()) do + p = _Partials_irpasses.Partial() + invokelatest(identity, p) + isdefined(p, :z) && isdefined(p, :x) || return nothing + isdefined(p, :x) & isdefined(p, :z) + end + @test count(iscall((src, isdefined)), src.code) == 2 +end + # optimize `isdefined` away in the presence of a dominating `setfield!` let src = code_typed1(()) do a = Ref{Any}() diff --git a/base/boot.jl b/base/boot.jl index 93d73679d65ed..0d59156b24645 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -542,7 +542,7 @@ eval(Core, quote UpsilonNode(@nospecialize(val)) = $(Expr(:new, :UpsilonNode, :val)) UpsilonNode() = $(Expr(:new, :UpsilonNode)) Const(@nospecialize(v)) = $(Expr(:new, :Const, :v)) - _PartialStruct(@nospecialize(typ), fields::Array{Any, 1}) = $(Expr(:new, :PartialStruct, :typ, :fields)) + _PartialStruct(@nospecialize(typ), undef, fields::Array{Any, 1}) = $(Expr(:new, :PartialStruct, :typ, :undef, :fields)) PartialOpaque(@nospecialize(typ), @nospecialize(env), parent::MethodInstance, source) = $(Expr(:new, :PartialOpaque, :typ, :env, :parent, :source)) InterConditional(slot::Int, @nospecialize(thentype), @nospecialize(elsetype)) = $(Expr(:new, :InterConditional, :slot, :thentype, :elsetype)) MethodMatch(@nospecialize(spec_types), sparams::SimpleVector, method::Method, fully_covers::Bool) = $(Expr(:new, :MethodMatch, :spec_types, :sparams, :method, :fully_covers)) diff --git a/base/coreir.jl b/base/coreir.jl index 59422afb44add..f36617be5fba4 100644 --- a/base/coreir.jl +++ b/base/coreir.jl @@ -14,7 +14,8 @@ Core.Const """ struct PartialStruct typ - fields::Vector{Any} # elements are other type lattice members + undef::BitVector # represents whether a given field may be undefined + fields::Vector{Any} # i-th element describes the lattice element for the i-th defined field end This extended lattice element is introduced when we have information about an object's @@ -23,19 +24,51 @@ some elements are known to be constants or a struct whose `Any`-typed field is i with `Int` values. - `typ` indicates the type of the object -- `fields` holds the lattice elements corresponding to each field of the object +- `undef` records which fields are possibly undefined +- `fields` holds the lattice elements corresponding to each defined field of the object -If `typ` is a struct, `fields` represents the fields of the struct that are guaranteed to be -initialized. For instance, if the length of `fields` of `PartialStruct` representing a -struct with 4 fields is 3, the 4th field may not be initialized. If the length is 4, all -fields are guaranteed to be initialized. +If `typ` is a struct, `undef` represents whether the corresponding field of the struct is guaranteed to be +initialized. For any defined field, there is a corresponding `fields` element which provides information +about the type of the defined field. If `typ` is a tuple, the last element of `fields` may be `Vararg`. In this case, it is guaranteed that the number of elements in the tuple is at least `length(fields)-1`, but the -exact number of elements is unknown. +exact number of elements is unknown (`undef` then has a length of `length(fields)-1`). """ Core.PartialStruct +function Core.PartialStruct(typ::Type, undef::BitVector, fields::Vector{Any}) + @assert length(undef) == length(fields) - isvarargtype(fields[end]) + return Core._PartialStruct(typ, undef, fields) +end + +function Core.PartialStruct(@nospecialize(typ), fields::Vector{Any}) + return Core.PartialStruct(typ, partialstruct_init_undef(typ, fields), fields) +end + +partialstruct_undef_length(fields) = length(fields) - isvarargtype(fields[end]) + +function partialstruct_init_undef(@nospecialize(typ), fields; all_defined = true) + n = partialstruct_undef_length(fields) + return partialstruct_init_undef(typ, n; all_defined) +end + +function partialstruct_init_undef(@nospecialize(typ), n::Integer; all_defined = true) + all_defined && return falses(n) + undef = trues(n) + for i in 1:min(datatype_min_ninitialized(typ), n) + undef[i] = false + end + return undef +end + +(==)(a::PartialStruct, b::PartialStruct) = a.typ === b.typ && a.undef == b.undef && a.fields == b.fields + +function Base.getproperty(pstruct::Core.PartialStruct, name::Symbol) + name === :undef && return getfield(pstruct, :undef)::BitVector + return getfield(pstruct, name) +end + """ struct InterConditional slot::Int diff --git a/base/essentials.jl b/base/essentials.jl index 795e499889582..66c79843a1453 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -593,6 +593,39 @@ function unconstrain_vararg_length(va::Core.TypeofVararg) return Vararg{unwrapva(va)} end +# Compute the minimum number of initialized fields for a particular datatype +# (therefore also a lower bound on the number of fields) +function datatype_min_ninitialized(@nospecialize t0) + t = unwrap_unionall(t0) + t isa DataType || return 0 + isabstracttype(t) && return 0 + if t.name === _NAMEDTUPLE_NAME + names, types = t.parameters[1], t.parameters[2] + if names isa Tuple + return length(names) + end + t = argument_datatype(types) + t isa DataType || return 0 + t.name === Tuple.name || return 0 + end + if t.name === Tuple.name + n = length(t.parameters) + n == 0 && return 0 + va = t.parameters[n] + if isvarargtype(va) + n -= 1 + if isdefined(va, :N) + va = va.N + if va isa Int + n += va + end + end + end + return n + end + return length(t.name.names) - t.name.n_uninitialized +end + import Core: typename _tuple_error(T::Type, x) = (@noinline; throw(MethodError(convert, (T, x)))) diff --git a/src/jltypes.c b/src/jltypes.c index ba519e0334e9e..9a0d1c2549431 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3693,9 +3693,9 @@ void jl_init_types(void) JL_GC_DISABLED jl_emptysvec, 0, 0, 1); jl_partial_struct_type = jl_new_datatype(jl_symbol("PartialStruct"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(2, "typ", "fields"), - jl_svec2(jl_any_type, jl_array_any_type), - jl_emptysvec, 0, 0, 2); + jl_perm_symsvec(3, "typ", "undef", "fields"), + jl_svec(3, jl_any_type, jl_any_type, jl_array_any_type), + jl_emptysvec, 0, 0, 3); jl_interconditional_type = jl_new_datatype(jl_symbol("InterConditional"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(3, "slot", "thentype", "elsetype"),