Skip to content

Commit

Permalink
inference: propagate partially initialized mutable structs more
Browse files Browse the repository at this point in the history
Following up #55297.
A mutable struct can have undefined fields in a non-contiguous manner,
but `PartialStruct` cannot model such a state. So in #55297
`PartialStruct` was used to represent only the mutable objects where the
field following the minimum initialized fields is also defined.

As a follow-up for the PR, this commit implements a minor improvement
that, in cases when the mutable object is already represented as a
`PartialStruct`, allows inference to add one more `isdefined`-field
information on top of those implied by its `fields`.

```julia
mutable struct PartiallyInitialized2
    a; b; c
    PartiallyInitialized2(a) = (@nospecialize; new(a))
    PartiallyInitialized2(a, b) = (@nospecialize; new(a, b))
    PartiallyInitialized2(a, b, c) = (@nospecialize; new(a, b, c))
end

@test Base.infer_effects((PartiallyInitialized2,); optimize=false) do x
    if isdefined(x, :b)
        if isdefined(x, :c)
            return x.c
        end
        return x.b
    end
    return nothing
end |> Core.Compiler.is_nothrow
```
  • Loading branch information
aviatesk committed Aug 20, 2024
1 parent 9650510 commit 2871e43
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 4 deletions.
16 changes: 13 additions & 3 deletions base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1027,7 +1027,7 @@ collect_const_args(arginfo::ArgInfo, start::Int) = collect_const_args(arginfo.ar
function collect_const_args(argtypes::Vector{Any}, start::Int)
return Any[ let a = widenslotwrapper(argtypes[i])
isa(a, Const) ? a.val :
isconstType(a) ? (a::DataType).parameters[1] :
isconstType(a) ? a.parameters[1] :
(a::DataType).instance
end for i = start:length(argtypes) ]
end
Expand Down Expand Up @@ -2057,11 +2057,21 @@ function form_partially_defined_struct(@nospecialize(obj), @nospecialize(name))
fldidx === nothing && return nothing
nminfld = datatype_min_ninitialized(objt)
if ismutabletype(objt)
fldidx == nminfld+1 || return nothing
# 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(objt0, Any[fieldtype(objt0, i) for i = 1:fldidx])
return PartialStruct(objt0, Any[obj isa PartialStruct && ilength(obj.fields) ?
obj.fields[i] : fieldtype(objt0,i) for i = 1:fldidx])
end

function abstract_call_unionall(interp::AbstractInterpreter, argtypes::Vector{Any}, call::CallMeta)
Expand Down
2 changes: 1 addition & 1 deletion base/compiler/typeutils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function hasuniquerep(@nospecialize t)
iskindtype(typeof(t)) || return true # non-types are always compared by egal in the type system
isconcretetype(t) && return true # these are also interned and pointer comparable
if isa(t, DataType) && t.name !== Tuple.name && !isvarargtype(t) # invariant DataTypes
return _all(hasuniquerep, t.parameters)
return all(hasuniquerep, t.parameters)
end
return false
end
Expand Down
16 changes: 16 additions & 0 deletions test/compiler/inference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6033,6 +6033,22 @@ end |> Core.Compiler.is_nothrow
return x.b
end
end |> !Core.Compiler.is_nothrow
@test Base.infer_effects((PartiallyInitialized2,); optimize=false) do x
if isdefined(x, :b)
if isdefined(x, :c)
return x.c
end
return x.b
end
return nothing
end |> Core.Compiler.is_nothrow
@test Base.infer_effects((Bool,Int,); optimize=false) do c, b
x = c ? PartiallyInitialized1(true) : PartiallyInitialized1(true, b)
if isdefined(x, :b)
return Val(x.a), x.b
end
return nothing
end |> Core.Compiler.is_nothrow

# End to end test case for the partially initialized struct with `PartialStruct`
@noinline broadcast_noescape1(a) = (broadcast(identity, a); nothing)
Expand Down

0 comments on commit 2871e43

Please sign in to comment.