Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PartialStruct tmerge fails to converge #43784

Closed
vtjnash opened this issue Jan 12, 2022 · 3 comments · Fixed by #43812 or #44404
Closed

PartialStruct tmerge fails to converge #43784

vtjnash opened this issue Jan 12, 2022 · 3 comments · Fixed by #43812 or #44404
Labels
bug Indicates an unexpected problem or unintended behavior compiler:inference Type inference
Milestone

Comments

@vtjnash
Copy link
Member

vtjnash commented Jan 12, 2022

Original text:

I just opened a PR to Measurements.jl and found that CI is hanging on Julia nightly: https://github.com/JuliaPhysics/Measurements.jl/runs/4649844099?check_suite_focus=true. I just recompiled latest master of Julia and found that simple things like

julia> using Measurements

julia> (1 ± 2) + (3 ± 4)
^C^C^C^C^C^CWARNING: Force throwing a SIGINT
Internal error: encountered unexpected error in runtime:
InterruptException()
jl_is_tuple_type at /Users/mose/repo/julia/src/./julia.h:1287 [inlined]
jl_tuple1_isa at /Users/mose/repo/julia/src/subtype.c:2006
jl_typemap_entry_assoc_exact at /Users/mose/repo/julia/src/typemap.c:976
jl_typemap_assoc_exact at /Users/mose/repo/julia/src/./julia_internal.h:1300 [inlined]
jl_lookup_generic_ at /Users/mose/repo/julia/src/gf.c:2426 [inlined]
ijl_apply_generic at /Users/mose/repo/julia/src/gf.c:2482
is_lattice_equal at ./compiler/typelattice.jl:263
is_lattice_equal at ./compiler/typelattice.jl:265
is_lattice_equal at ./compiler/typelattice.jl:265
is_lattice_equal at ./compiler/typelattice.jl:265
is_lattice_equal at ./compiler/typelattice.jl:265
is_lattice_equal at ./compiler/typelattice.jl:265
is_lattice_equal at ./compiler/typelattice.jl:265
is_lattice_equal at ./compiler/typelattice.jl:265
is_lattice_equal at ./compiler/typelattice.jl:265
is_lattice_equal at ./compiler/typelattice.jl:265
is_lattice_equal at ./compiler/typelattice.jl:265
is_lattice_equal at ./compiler/typelattice.jl:265
is_lattice_equal at ./compiler/typelattice.jl:265
is_lattice_equal at ./compiler/typelattice.jl:265
is_lattice_equal at ./compiler/typelattice.jl:265
is_lattice_equal at ./compiler/typelattice.jl:265
is_lattice_equal at ./compiler/typelattice.jl:265
is_lattice_equal at ./compiler/typelattice.jl:265
is_lattice_equal at ./compiler/typelattice.jl:265
is_lattice_equal at ./compiler/typelattice.jl:265
is_lattice_equal at ./compiler/typelattice.jl:265
is_lattice_equal at ./compiler/typelattice.jl:265
is_lattice_equal at ./compiler/typelattice.jl:265
is_lattice_equal at ./compiler/typelattice.jl:265
is_argtype_match at ./compiler/inferenceresult.jl:7 [inlined]
cache_lookup at ./compiler/inferenceresult.jl:213
abstract_call_method_with_const_args at ./compiler/abstractinterpretation.jl:598
abstract_call_gf_by_type at ./compiler/abstractinterpretation.jl:126
abstract_call_known at ./compiler/abstractinterpretation.jl:1487
abstract_call at ./compiler/abstractinterpretation.jl:1543
abstract_call at ./compiler/abstractinterpretation.jl:1525
abstract_eval_statement at ./compiler/abstractinterpretation.jl:1664
typeinf_local at ./compiler/abstractinterpretation.jl:2053
typeinf_nocycle at ./compiler/abstractinterpretation.jl:2149
_typeinf at ./compiler/typeinfer.jl:226
typeinf at ./compiler/typeinfer.jl:209
abstract_call_method_with_const_args at ./compiler/abstractinterpretation.jl:622
abstract_call_gf_by_type at ./compiler/abstractinterpretation.jl:126
abstract_call_known at ./compiler/abstractinterpretation.jl:1487
abstract_call at ./compiler/abstractinterpretation.jl:1543
abstract_call at ./compiler/abstractinterpretation.jl:1525
abstract_eval_statement at ./compiler/abstractinterpretation.jl:1664
typeinf_local at ./compiler/abstractinterpretation.jl:2053
typeinf_nocycle at ./compiler/abstractinterpretation.jl:2149
_typeinf at ./compiler/typeinfer.jl:226
typeinf at ./compiler/typeinfer.jl:209
abstract_call_method_with_const_args at ./compiler/abstractinterpretation.jl:622
abstract_call_gf_by_type at ./compiler/abstractinterpretation.jl:126
abstract_call_known at ./compiler/abstractinterpretation.jl:1487
abstract_call at ./compiler/abstractinterpretation.jl:1543
abstract_call at ./compiler/abstractinterpretation.jl:1525
abstract_eval_statement at ./compiler/abstractinterpretation.jl:1664
typeinf_local at ./compiler/abstractinterpretation.jl:2053
typeinf_nocycle at ./compiler/abstractinterpretation.jl:2149
_typeinf at ./compiler/typeinfer.jl:226
typeinf at ./compiler/typeinfer.jl:209
abstract_call_method_with_const_args at ./compiler/abstractinterpretation.jl:622
abstract_call_gf_by_type at ./compiler/abstractinterpretation.jl:126
abstract_call_known at ./compiler/abstractinterpretation.jl:1487
abstract_call at ./compiler/abstractinterpretation.jl:1543
abstract_call at ./compiler/abstractinterpretation.jl:1525
abstract_eval_statement at ./compiler/abstractinterpretation.jl:1664
typeinf_local at ./compiler/abstractinterpretation.jl:2053
typeinf_nocycle at ./compiler/abstractinterpretation.jl:2149
_typeinf at ./compiler/typeinfer.jl:226
typeinf at ./compiler/typeinfer.jl:209
typeinf_edge at ./compiler/typeinfer.jl:826 [inlined]
abstract_call_method at ./compiler/abstractinterpretation.jl:570
abstract_call_gf_by_type at ./compiler/abstractinterpretation.jl:117
abstract_call_known at ./compiler/abstractinterpretation.jl:1487
abstract_call at ./compiler/abstractinterpretation.jl:1543
abstract_call at ./compiler/abstractinterpretation.jl:1525
abstract_eval_statement at ./compiler/abstractinterpretation.jl:1664
typeinf_local at ./compiler/abstractinterpretation.jl:2053
typeinf_nocycle at ./compiler/abstractinterpretation.jl:2149
_typeinf at ./compiler/typeinfer.jl:226
typeinf at ./compiler/typeinfer.jl:209
typeinf_ext at ./compiler/typeinfer.jl:907
typeinf_ext_toplevel at ./compiler/typeinfer.jl:940
typeinf_ext_toplevel at ./compiler/typeinfer.jl:936
jfptr_typeinf_ext_toplevel_10110 at /Users/mose/repo/julia/usr/lib/julia/sys.dylib (unknown line)
_jl_invoke at /Users/mose/repo/julia/src/gf.c:0 [inlined]
ijl_apply_generic at /Users/mose/repo/julia/src/gf.c:2486
jl_apply at /Users/mose/repo/julia/src/./julia.h:1789 [inlined]
jl_type_infer at /Users/mose/repo/julia/src/gf.c:295
jl_generate_fptr_impl at /Users/mose/repo/julia/src/jitlayers.cpp:301
jl_compile_method_internal at /Users/mose/repo/julia/src/gf.c:2020
_jl_invoke at /Users/mose/repo/julia/src/gf.c:2296 [inlined]
ijl_apply_generic at /Users/mose/repo/julia/src/gf.c:2486
jl_apply at /Users/mose/repo/julia/src/./julia.h:1789 [inlined]
do_call at /Users/mose/repo/julia/src/interpreter.c:126
eval_body at /Users/mose/repo/julia/src/interpreter.c:0
jl_interpret_toplevel_thunk at /Users/mose/repo/julia/src/interpreter.c:744
jl_toplevel_eval_flex at /Users/mose/repo/julia/src/toplevel.c:888
jl_toplevel_eval_flex at /Users/mose/repo/julia/src/toplevel.c:832
ijl_toplevel_eval at /Users/mose/repo/julia/src/toplevel.c:897 [inlined]
ijl_toplevel_eval_in at /Users/mose/repo/julia/src/toplevel.c:947
eval at ./boot.jl:368 [inlined]
eval_user_input at /Users/mose/repo/julia/usr/share/julia/stdlib/v1.8/REPL/src/REPL.jl:151
repl_backend_loop at /Users/mose/repo/julia/usr/share/julia/stdlib/v1.8/REPL/src/REPL.jl:245
start_repl_backend at /Users/mose/repo/julia/usr/share/julia/stdlib/v1.8/REPL/src/REPL.jl:230
#run_repl#47 at /Users/mose/repo/julia/usr/share/julia/stdlib/v1.8/REPL/src/REPL.jl:367
run_repl at /Users/mose/repo/julia/usr/share/julia/stdlib/v1.8/REPL/src/REPL.jl:354
jfptr_run_repl_55691 at /Users/mose/repo/julia/usr/lib/julia/sys.dylib (unknown line)
_jl_invoke at /Users/mose/repo/julia/src/gf.c:0 [inlined]
ijl_apply_generic at /Users/mose/repo/julia/src/gf.c:2486
#934 at ./client.jl:403
jfptr_YY.934_34804 at /Users/mose/repo/julia/usr/lib/julia/sys.dylib (unknown line)
_jl_invoke at /Users/mose/repo/julia/src/gf.c:0 [inlined]
ijl_apply_generic at /Users/mose/repo/julia/src/gf.c:2486
jl_apply at /Users/mose/repo/julia/src/./julia.h:1789 [inlined]
jl_f__call_latest at /Users/mose/repo/julia/src/builtins.c:757
#invokelatest#2 at ./essentials.jl:731 [inlined]
invokelatest at ./essentials.jl:729 [inlined]
run_main_repl at ./client.jl:388
exec_options at ./client.jl:318
_start at ./client.jl:506
jfptr__start_46562 at /Users/mose/repo/julia/usr/lib/julia/sys.dylib (unknown line)
_jl_invoke at /Users/mose/repo/julia/src/gf.c:0 [inlined]
ijl_apply_generic at /Users/mose/repo/julia/src/gf.c:2486
jl_apply at /Users/mose/repo/julia/src/./julia.h:1789 [inlined]
true_main at /Users/mose/repo/julia/src/jlapi.c:562
jl_repl_entrypoint at /Users/mose/repo/julia/src/jlapi.c:706
4.0 ± 4.5

hang

Originally posted by @giordano in #43561 (comment)

Initial analysis and MWE:

The problem here is that this tmerge may fail to converge for recursive structs (e.g. if mayinlinealloc is false):

julia> init = Base.ImmutableDict{Any,Any}()

julia> a = Core.Const(init)
Core.Const(Base.ImmutableDict{Any, Any}())

julia> b = Core.PartialStruct(typeof(init), Any[
         Core.Const(init),
         Any,
         Any])
Core.PartialStruct(Base.ImmutableDict{Any, Any}, Any[Core.Const(Base.ImmutableDict{Any, Any}()), Any, Any])

julia> c = Core.Compiler.tmerge(a, b) # == b
Core.PartialStruct(Base.ImmutableDict{Any, Any}, Any[Core.Const(Base.ImmutableDict{Any, Any}()), Any, Any])

julia> Core.Compiler.is_lattice_equal(b, c)
true

julia> ⊑(a, c)
false

Then in a while loop, we keep wrapping it such that we never converge this lattice:

julia> let init = Base.ImmutableDict{Any,Any}()
       global function f()
         g = init
         while true
             g = Base.ImmutableDict(g, 1=>2)
         end
       end
       end

julia> @code_typed optimize=false f() # this goes on forever
@vtjnash vtjnash added this to the 1.8 milestone Jan 12, 2022
@vtjnash vtjnash added the compiler:inference Type inference label Jan 12, 2022
@JeffBezanson JeffBezanson added the bug Indicates an unexpected problem or unintended behavior label Jan 12, 2022
martinholters added a commit that referenced this issue Jan 14, 2022
When `tmerge` is applied to `Const`/`PartialStruct` and a field is
`Const` in one of the types and `Union{}` (i.e. undef) and the other,
the resulting field type must not be `Const` (as the resursively called
`tmerge` produces).

Fixes #43784.
martinholters added a commit that referenced this issue Jan 14, 2022
When `tmerge` is applied to `Const`/`PartialStruct` and a field is
`Const` in one of the types and `Union{}` (i.e. undef) in the other,
the resulting field type must not be `Const` (as the resursively called
`tmerge` produces).

Fixes #43784.
martinholters added a commit that referenced this issue Jan 15, 2022
Co-authored-by: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com>
@Keno
Copy link
Member

Keno commented Jan 17, 2022

Sigh ... this issue just caused me half my Sunday in a slightly different presentation ... let's get it fixed!

Keno pushed a commit that referenced this issue Jan 18, 2022
Co-authored-by: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com>
aviatesk pushed a commit that referenced this issue Jan 19, 2022
When `tmerge` is applied to `Const`/`PartialStruct` and a field is
`Const` in one of the types and `Union{}` (i.e. undef) in the other,
the resulting field type must not be `Const` (as the resursively called
`tmerge` produces).

Fixes #43784.
@Keno
Copy link
Member

Keno commented Jan 19, 2022

I see #43812 is merged now, which is probably good since it fixed the immediate issue, but from discussion with @vtjnash this morning, we're not convinced, it's the correct solution, so re-opening this to track hopefully coming up with something better (cc @martinholters @aviatesk).

Notes from our discussion this morning:

Point 1: A complicating issue here is that currently we have !⊑(a, b), i.e. we don't consider Union{} in a PartialStruct field to be a refinement of Const (or anything) else. @vtjnash is convinced this is wrong. I think it is a valid choice, but I agree that it is less consistent with the way we do things elsewhere. The relevant change here would be changing https://github.com/JuliaLang/julia/blob/master/base/compiler/typelattice.jl#L203 to continue.

Point 2: Because of (1), prior to #43812, tmerge as violating its spec. It should always produce a valid upper bound over the inference lattice, but currently it did not.

Point 3: #43784 fixed this specific case, but it is not generally sufficient as the same issue can happen with PartialStructs that don't have Union{} fieldtypes. For regular types we limit the complexity growth. We need to do something similar here.

Point 4: Because of Point 1 and Point 3, if we were to fix the thing identified in Point 1, the fix from #43812 would become ineffective, since tmerge would always return the larger of the two types.

@Keno Keno reopened this Jan 19, 2022
@vtjnash
Copy link
Member Author

vtjnash commented Jan 20, 2022

For point 3, I propose we could use here a simplified tmerge concept, where they are either equal, or we aggressively widen them (to the narrower of their shared wrapper type, or the declared fieldtype):

function tmerge_fast(fielda, fieldb, reftype)
    is_lattice_equal(fielda, fieldb) && return fielda
    tn = _typename(widentype(fielda))
    if tn isa Const && tn === _typename(widentype(fieldb))
        return typeintersect(reftype, (tn.val::TypeName).wrapper)
    end
    return reftype
end

......
    fieldx = tmerge_fast(fielda, fieldb, fieldtype(ty, n))
......

This is not monotonic (since we completely ignore the possibility of them being subtypes), but neither is tmerge, and nor do we expect–or require–it to be. (This is simply among the reasons why the return_type function is known not to be :invariant in the general case.)

LilithHafner pushed a commit to LilithHafner/julia that referenced this issue Feb 22, 2022
…#43812)

When `tmerge` is applied to `Const`/`PartialStruct` and a field is
`Const` in one of the types and `Union{}` (i.e. undef) in the other,
the resulting field type must not be `Const` (as the resursively called
`tmerge` produces).

Fixes JuliaLang#43784.
vtjnash added a commit that referenced this issue Mar 2, 2022
Be more aggressive about merging fields to greatly accelerate
convergence, but also compute anyrefine more correctly as we do now
elsewhere (since #42831, a121721)

Move the tmeet algorithm, without changes, since it is a precise lattice
operation, not a heuristic limit like tmerge.

Close #43784
vtjnash added a commit that referenced this issue Mar 2, 2022
Be more aggressive about merging fields to greatly accelerate
convergence, but also compute anyrefine more correctly as we do now
elsewhere (since #42831, a121721)

Move the tmeet algorithm, without changes, since it is a precise lattice
operation, not a heuristic limit like tmerge.

Close #43784
vtjnash added a commit that referenced this issue Mar 2, 2022
Previously we assumed only union type could have complexity that
violated the tmerge lattice requirements, but other types can have that
too. This lets us fix an issue with the PartialStruct comparison failing
for undefined fields, mentioned in #43784.
vtjnash added a commit that referenced this issue Mar 2, 2022
Be more aggressive about merging fields to greatly accelerate
convergence, but also compute anyrefine more correctly as we do now
elsewhere (since #42831, a121721)

Move the tmeet algorithm, without changes, since it is a precise lattice
operation, not a heuristic limit like tmerge.

Close #43784
LilithHafner pushed a commit to LilithHafner/julia that referenced this issue Mar 8, 2022
…#43812)

When `tmerge` is applied to `Const`/`PartialStruct` and a field is
`Const` in one of the types and `Union{}` (i.e. undef) in the other,
the resulting field type must not be `Const` (as the resursively called
`tmerge` produces).

Fixes JuliaLang#43784.
vtjnash added a commit that referenced this issue Mar 10, 2022
Previously we assumed only union type could have complexity that
violated the tmerge lattice requirements, but other types can have that
too. This lets us fix an issue with the PartialStruct comparison failing
for undefined fields, mentioned in #43784.
vtjnash added a commit that referenced this issue Mar 10, 2022
Be more aggressive about merging fields to greatly accelerate
convergence, but also compute anyrefine more correctly as we do now
elsewhere (since #42831, a121721)

Move the tmeet algorithm, without changes, since it is a precise lattice
operation, not a heuristic limit like tmerge.

Close #43784
vtjnash added a commit that referenced this issue Mar 11, 2022
Previously we assumed only union type could have complexity that
violated the tmerge lattice requirements, but other types can have that
too. This lets us fix an issue with the PartialStruct comparison failing
for undefined fields, mentioned in #43784.
vtjnash added a commit that referenced this issue Mar 11, 2022
Be more aggressive about merging fields to greatly accelerate
convergence, but also compute anyrefine more correctly as we do now
elsewhere (since #42831, a121721)

Move the tmeet algorithm, without changes, since it is a precise lattice
operation, not a heuristic limit like tmerge.

Close #43784
DilumAluthge pushed a commit that referenced this issue Mar 13, 2022
* inference: fix tmerge lattice over issimpleenoughtype

Previously we assumed only union type could have complexity that
violated the tmerge lattice requirements, but other types can have that
too. This lets us fix an issue with the PartialStruct comparison failing
for undefined fields, mentioned in #43784.

* inference: refine PartialStruct lattice tmerge

Be more aggressive about merging fields to greatly accelerate
convergence, but also compute anyrefine more correctly as we do now
elsewhere (since #42831, a121721)

Move the tmeet algorithm, without changes, since it is a precise lattice
operation, not a heuristic limit like tmerge.

Close #43784
KristofferC pushed a commit that referenced this issue Mar 13, 2022
* inference: fix tmerge lattice over issimpleenoughtype

Previously we assumed only union type could have complexity that
violated the tmerge lattice requirements, but other types can have that
too. This lets us fix an issue with the PartialStruct comparison failing
for undefined fields, mentioned in #43784.

* inference: refine PartialStruct lattice tmerge

Be more aggressive about merging fields to greatly accelerate
convergence, but also compute anyrefine more correctly as we do now
elsewhere (since #42831, a121721)

Move the tmeet algorithm, without changes, since it is a precise lattice
operation, not a heuristic limit like tmerge.

Close #43784

(cherry picked from commit ff88fa4)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Indicates an unexpected problem or unintended behavior compiler:inference Type inference
Projects
None yet
3 participants