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

Avoid broken kind type handling when compiling isa #27736

Merged
merged 11 commits into from
Jul 7, 2018

Conversation

jrevels
Copy link
Member

@jrevels jrevels commented Jun 22, 2018

This fixes #27078, JuliaLabs/Cassette.jl#42, and some other bugs with tuples.

I'm not at all a fan of my replacements for the tuple handling code here - I just need to get the aforementioned bugs fixed. It should be "more correct" than it was before (modulo any bugs I introduced...), but it feels like it's going to be slower w.r.t. to compilation, runtime, or both. I haven't done benchmarking yet, though. I would be extremely grateful for some tips on faster/cleaner alternatives (as long as they don't reintroduce the bugs I'm trying to fix). This might be another case in favor of making ntuple an intrinsic...EDIT: see comment below, moved the tuple changes out of this PR

@nanosoldier runbenchmarks(ALL, vs = ":master")

src/cgutils.cpp Outdated
//
// Optional<bool> known_isa;
// if (x.constant)
// known_isa = jl_isa(x.constant, type);
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this case correct? The result of an actual call to jl_isa seems like it should be correct by definition. I agree this optimization is probably redundant though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, makes sense. I'll uncomment this branch out and see if it still passes tests (I assume it will).

base/tuple.jl Outdated
(convert(tuple_type_head(T), y[1]), _totuple(tuple_type_tail(T), itr, y[2])...)
end

_totuple(::Type{Tuple{Vararg{E}}}, itr, s...) where {E} = (collect(E, Iterators.rest(itr,s...))...,)
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This definition could be combined with the next by adding an @isdefined(E) check.

convert(::Type{T}, x::T) where {T<:Tuple{Any, Vararg{Any}}} = x
convert(::Type{Tuple{}}, x::Tuple{Any, Vararg{Any}}) = throw(MethodError(convert, (Tuple{}, x)))
convert(::Type{T}, x::Tuple{Any, Vararg{Any}}) where {T<:Tuple} =
(convert(tuple_type_head(T), x[1]), convert(tuple_type_tail(T), tail(x))...)
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have test cases where these fail?

Copy link
Member Author

@jrevels jrevels Jun 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actual problem that I'm trying to workaround with this PR's fieldtype-based implementation is e.g.:

julia> using Cassette

julia> Cassette.@context Ctx

julia> Cassette.@overdub(Ctx(), convert(Tuple{Vararg{Float64}}, (1,2)))
ERROR: type DataType has no field body
Stacktrace:
 [1] execution at /Users/jarrettrevels/.julia/dev/Cassette/src/overdub.jl:9 [inlined]
 [2] macro expansion at /Users/jarrettrevels/.julia/dev/Cassette/src/overdub.jl:171 [inlined]
 [3] overdub_recurse at /Users/jarrettrevels/.julia/dev/Cassette/src/overdub.jl:201 [inlined]
 [4] overdub_execute at /Users/jarrettrevels/.julia/dev/Cassette/src/overdub.jl:35 [inlined]
 [5] getproperty at ./sysimg.jl:15 [inlined]
 [6] overdub_recurse(::Cassette.Context{getfield(Main, Symbol("##CtxName#350")),Nothing,Cassette.NoPass,Nothing,Nothing}, ::typeof(getproperty), ::Type{Vararg{Float64,N} where N}, ::Symbol) at /Users/jarrettrevels/.julia/dev/Cassette/src/overdub.jl:0
 [7] overdub_execute at /Users/jarrettrevels/.julia/dev/Cassette/src/overdub.jl:35 [inlined]
 [8] unwrap_unionall at ./essentials.jl:179 [inlined]
 [9] overdub_recurse(::Cassette.Context{getfield(Main, Symbol("##CtxName#350")),Nothing,Cassette.NoPass,Nothing,Nothing}, ::typeof(Base.unwrap_unionall), ::Type{Vararg{Float64,N} where N}) at /Users/jarrettrevels/.julia/dev/Cassette/src/overdub.jl:0
 [10] overdub_execute at /Users/jarrettrevels/.julia/dev/Cassette/src/overdub.jl:35 [inlined]
 [11] unwrapva at ./essentials.jl:224 [inlined]
 [12] overdub_recurse(::Cassette.Context{getfield(Main, Symbol("##CtxName#350")),Nothing,Cassette.NoPass,Nothing,Nothing}, ::typeof(Base.unwrapva), ::Type{Vararg{Float64,N} where N}) at /Users/jarrettrevels/.julia/dev/Cassette/src/overdub.jl:0
 [13] overdub_execute at /Users/jarrettrevels/.julia/dev/Cassette/src/overdub.jl:35 [inlined]
 [14] tuple_type_head at ./essentials.jl:154 [inlined]
 [15] overdub_recurse(::Cassette.Context{getfield(Main, Symbol("##CtxName#350")),Nothing,Cassette.NoPass,Nothing,Nothing}, ::typeof(Base.tuple_type_head), ::Type{Tuple{Vararg{Float64,N} where N}}) at /Users/jarrettrevels/.julia/dev/Cassette/src/overdub.jl:0
 [16] overdub_execute at /Users/jarrettrevels/.julia/dev/Cassette/src/overdub.jl:35 [inlined]
 [17] convert at ./essentials.jl:248 [inlined]
 [18] overdub_recurse(::Cassette.Context{getfield(Main, Symbol("##CtxName#350")),Nothing,Cassette.NoPass,Nothing,Nothing}, ::typeof(convert), ::Type{Tuple{Vararg{Float64,N} where N}}, ::Tuple{Int64,Int64}) at /Users/jarrettrevels/.julia/dev/Cassette/src/overdub.jl:0
 [19] overdub_execute at /Users/jarrettrevels/.julia/dev/Cassette/src/overdub.jl:35 [inlined]
 [20] #19 at /Users/jarrettrevels/.julia/dev/Cassette/src/macros.jl:94 [inlined]
 [21] overdub_recurse(::Cassette.Context{getfield(Main, Symbol("##CtxName#350")),Nothing,Cassette.NoPass,Nothing,Nothing}, ::getfield(Main, Symbol("##19#20"))) at /Users/jarrettrevels/.julia/dev/Cassette/src/overdub.jl:0
 [22] top-level scope at none:0

which seems to be from tuple_type_head/tuple_type_tail hitting some kindtype subtyping/inference issues that I don't fully understand. With this PR (EDIT: specifically, with this change to convert), I get the correct answer e.g.

julia> Cassette.@overdub(Ctx(), convert(Tuple{Vararg{Float64}}, (1,2)))
(1.0, 2.0)

@@ -141,33 +141,6 @@ end
argtail(x, rest...) = rest
tail(x::Tuple) = argtail(x...)

# TODO: a better / more infer-able definition would pehaps be
# tuple_type_head(T::Type) = fieldtype(T::Type{<:Tuple}, 1)
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should instate this definition of tuple_type_head, and add a correct implementation of tuple_type_tail.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm....what would a correct implementation of tuple_type_tail look like when we have covariance + diagonal constraints?

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, you seem content with fieldtype((Tuple{T,T} where T<:AbstractFloat), 1) == AbstractFloat, so it might not be a problem.

tuple_type_tail(Tuple{T,T} where T) == Tuple{T} where T seems correct to me. Do you have a counterexample?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's an example that seems wrong to me for master's tuple_type_tail:

julia> t = (Val(Any), 1, 1.0)
(Val{Any}(), 1, 1.0)

julia> typeof(t) <: Tuple{Val{T},T,T} where T
true

julia> typeof(Base.tail(t)) <: Base.tuple_type_tail(Tuple{Val{T},T,T} where T)
false

That's what I meant by covariance + diagonal constraints mucking things up, but I could totally be ignorant about the correct meaning of tuple_type_tail - apologies if so!

Well, you seem content with fieldtype((Tuple{T,T} where T<:AbstractFloat), 1) == AbstractFloat, so it might not be a problem.

Yeah, @vtjnash told me fieldtype usually gets the right answers in these cases, which is why I replaced all the usages of tuple_type_head/tuple_type_tail with it. I'm just not sure how to use fieldtype to compute tuple_type_tail in general e.g. on vatuple types with unconstrained lengths.

if isvatuple(T)
throw(ArgumentError("Cannot compute IteratorSize for ProductIterator{$T}"))
end
return reduce(prod_iteratorsize, HasShape{0}(), ntuple(i -> IteratorSize(fieldtype(T, i))), fieldcount(T))
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, using ntuple and reduce in cases like this is pretty hard on the compiler. We should try to make tuple_type_tail work, or manually recur over field indices.

@nanosoldier
Copy link
Collaborator

Something went wrong when running your job:

NanosoldierError: failed to run benchmarks against primary commit: failed process: Process(`sudo cset shield -e su nanosoldier -- -c ./benchscript.sh`, ProcessExited(1)) [1]

Logs and partial data can be found here
cc @ararslan

@jrevels jrevels force-pushed the jr/avoidbrokenkindtypes branch 2 times, most recently from 198b618 to 6e0120d Compare June 25, 2018 18:34
@jrevels
Copy link
Member Author

jrevels commented Jun 25, 2018

I've removed the more controversial tuple stuff from this PR, leaving only the isa fixes. I'll make a follow-up PR to try tackle the tuple stuff in a saner way

@jrevels jrevels changed the title Avoid broken kind type handling when compiling isa and in tuple handling code Avoid broken kind type handling when compiling isa Jun 25, 2018
@jrevels
Copy link
Member Author

jrevels commented Jun 25, 2018

So improving the isa tfunc just exposed a much sillier bug in the subtyping tests. I just pushed a fix for it in this PR, but here's the behavior on master for giggles:

julia> issubstrict(x, y) = (x <: y) && !(y <: x)
issubstrict (generic function with 1 method)

julia> f() = issubstrict(Int where T, Integer where T<:Integer)
f (generic function with 1 method)

# the SSA values that should be inferred as Type{T}s are inferred as Ts!!!
# but it doesn't matter because the `isa` tfunc still had a correct fallback of `Bool`
julia> code_typed(f, optimize = false)
1-element Array{Any,1}:
 CodeInfo(
1 1 ─      T@_3 = (Core.TypeVar)(:T)                                        │
  │   %2 = Core.UnionAll(:(_3::Core.Compiler.PartialTypeVar(T, true, true)), Main.Int)::Int64
  │        T@_2 = (Core.TypeVar)(:T, Main.Integer)                          │
  │   %4 = Core.UnionAll(:(_2::Core.Compiler.PartialTypeVar(T<:Integer, true, true)), Main.Integer)::Integer%5 = Main.issubstrict(%2, %4)::Bool                                   │
  └──      return %5                                                        │
) => Bool

@jrevels jrevels added compiler:codegen Generation of LLVM IR and native code kind:bugfix This change fixes an existing bug compiler:inference Type inference labels Jun 25, 2018
@jrevels
Copy link
Member Author

jrevels commented Jun 27, 2018

Okay, narrowed down the latest bug to this test case:

@noinline function g(f, a::T, ::Type{S} = Any) where {T, S}
    cf = @cfunction $f Ref{T} (Ref{T},)
    GC.gc()
    @assert cf === Base.cconvert(Ptr{Cvoid}, cf)
    GC.@preserve cf begin
        fptr = Base.unsafe_convert(Ptr{Cvoid}, cf)
        b = ccall(fptr, Ref{T}, (Ref{T},), a)
    end
end

g(identity, 1)

Note that you may need to try this several times (restarting Julia each time) to see errors here - sometimes it just outputs 1 as expected. When it does error, it seems that one of several different errors pops up, and which one you get is somewhat arbitrary...

julia> g(identity, 1)
ERROR: TypeError: in cfunction, in cfunction, expected Union{LinearAlgebra.Adjoint{T,Array{T,1}}, LinearAlgebra.RowVector{T,Array{T,1}}, LinearAlgebra.Transpose{T,Array{T,1}}, LinearAlgebra.AbstractTriangular{T,A} where A<:(Array{T,2} where T), LinearAlgebra.Hermitian{T,A} where A<:(Array{T,2} where T), LinearAlgebra.Symmetric{T,A} where A<:(Array{T,2} where T)}, got Int64
Stacktrace:
 [1] macro expansion at ./REPL[1]:89 [inlined]
 [2] macro expansion at ./gcutils.jl:87 [inlined]
 [3] g(::Function, ::Int64, ::Type{Any}) at ./REPL[1]:5
 [4] g(::Function, ::Int64) at ./REPL[1]:2
 [5] top-level scope at none:0
julia> g(identity, 1)
ERROR: TypeError: in cfunction, in cfunction, expected Core.Compiler.UseRef(nothing, 0), got Int64
Stacktrace:
 [1] macro expansion at ./REPL[1]:89 [inlined]
 [2] macro expansion at ./gcutils.jl:87 [inlined]
 [3] g(::Function, ::Int64, ::Type{Any}) at ./REPL[1]:5
 [4] g(::Function, ::Int64) at ./REPL[1]:2
 [5] top-level scope at none:0
julia> g(identity, 1)
ERROR: TypeError: in cfunction, in cfunction, expected Core.Compiler.UseRef(Core.UpsilonNode(Core.Compiler.Argument(2)), 2), got Int64
Stacktrace:
 [1] macro expansion at ./REPL[1]:89 [inlined]
 [2] macro expansion at ./gcutils.jl:87 [inlined]
 [3] g(::Function, ::Int64, ::Type{Any}) at ./REPL[1]:5
 [4] g(::Function, ::Int64) at ./REPL[1]:2
 [5] top-level scope at none:0
julia> g(identity, 1)
ERROR: TypeError: in cfunction, in cfunction, expected Core.Compiler.Pair{Int64,Any}(2, :((Base.arraylen)(Core.SSAValue(1)))), got Int64
Stacktrace:
 [1] macro expansion at ./REPL[1]:89 [inlined]
 [2] macro expansion at ./gcutils.jl:87 [inlined]
 [3] g(::Function, ::Int64, ::Type{Any}) at ./REPL[1]:5
 [4] g(::Function, ::Int64) at ./REPL[1]:2
 [5] top-level scope at none:0

...etc.

@jrevels
Copy link
Member Author

jrevels commented Jun 29, 2018

Bump, any thoughts on the above?

This PR works around one of the last correctness bugs that kills Cassette, if we can get it merged there will only be one last perf issue blocking the closure of #25492, and it would be wonderful if we could close that before v0.7 is released.

@maleadt
Copy link
Member

maleadt commented Jun 29, 2018

Looks like a missing GC root:

               _
   _       _ _(_)_     |  A fresh approach to technical computing
  (_)     | (_) (_)    |  Documentation: https://docs.julialang.org
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 0.7.0-beta.73 (2018-06-28 14:58 UTC)
 _/ |\__'_|_|_|\__'_|  |  jr/avoidbrokenkindtypes/e181dfe63e* (fork: 7 commits, 1 day)
|__/                   |  x86_64-unknown-linux-gnu

julia> @noinline function g(f, a::T, ::Type{S} = Any) where {T, S}
           cf = @cfunction $f Ref{T} (Ref{T},)
           GC.gc()
           @assert cf === Base.cconvert(Ptr{Cvoid}, cf)
           GC.@preserve cf begin
               fptr = Base.unsafe_convert(Ptr{Cvoid}, cf)
               b = ccall(fptr, Ref{T}, (Ref{T},), a)
           end
       end
g (generic function with 2 methods)

julia> g(identity, 1)
=================================================================
==13366==ERROR: AddressSanitizer: heap-use-after-free on address 0x610009f99278 at pc 0x7f8eb592b7e2 bp 0x7fff564f6600 sp 0x7fff564f65f8
READ of size 8 at 0x610009f99278 thread T0
    #0 0x7f8eb592b7e1 in jl_is_concrete_type julia/src/julia.h:1087:12
    #1 0x7f8eb592b17b in jl_isa julia/src/subtype.c:1248:9
LLVMSymbolizer: error reading file: No such file or directory.
    #2 0x7f8e9b81e6b7  (/memfd:julia-codegen (deleted)+0x216b7)
LLVMSymbolizer: error reading file: No such file or directory.
    #3 0x7f8e9b81e52a  (/memfd:julia-codegen (deleted)+0x2152a)
LLVMSymbolizer: error reading file: No such file or directory.
    #4 0x7f8e9b81e406  (/memfd:julia-codegen (deleted)+0x21406)
LLVMSymbolizer: error reading file: No such file or directory.
    #5 0x7f8e9b81e427  (/memfd:julia-codegen (deleted)+0x21427)
    #6 0x7f8eb57b5cde in jl_fptr_trampoline julia/src/gf.c:1813:12
    #7 0x7f8eb57cfe0a in jl_apply_generic julia/src/gf.c:2151:23
    #8 0x7f8eb5dc4fb7 in do_call julia/src/interpreter.c:324:26
    #9 0x7f8eb5dc102b in eval_value julia/src/interpreter.c:428:16
    #10 0x7f8eb5dc2e3c in eval_stmt_value julia/src/interpreter.c:363:23
    #11 0x7f8eb5dbea90 in eval_body julia/src/interpreter.c:671:21
    #12 0x7f8eb5dbf8b5 in jl_interpret_toplevel_thunk_callback julia/src/interpreter.c:788:21
    #13 0x7f8eb581252b in enter_interpreter_frame (julia/build/sanitize/usr/bin/../lib/libjulia-debug.so.0.7+0x18852b)
    #14 0x7f8eb5dbfa90 in jl_interpret_toplevel_thunk julia/src/interpreter.c:797:26
    #15 0x7f8eb5883dc2 in jl_toplevel_eval_flex julia/src/toplevel.c:814:18
    #16 0x7f8eb5887b20 in jl_toplevel_eval julia/src/toplevel.c:823:12
    #17 0x7f8eb57fd4f3 in jl_toplevel_eval_in julia/src/builtins.c:633:13
    #18 0x7f8e9d3e2e0c in japi1_eval_3916 boot.jl:319
    #19 0x7f8eb57b90b6 in jl_fptr_args julia/src/gf.c:1823:12
    #20 0x7f8eb57cfe0a in jl_apply_generic julia/src/gf.c:2151:23
LLVMSymbolizer: error reading file: No such file or directory.
    #21 0x7f8e9b80597b  (/memfd:julia-codegen (deleted)+0x897b)
LLVMSymbolizer: error reading file: No such file or directory.
    #22 0x7f8e9b8050da  (/memfd:julia-codegen (deleted)+0x80da)
    #23 0x7f8eb57b90b6 in jl_fptr_args julia/src/gf.c:1823:12
    #24 0x7f8eb57b5cde in jl_fptr_trampoline julia/src/gf.c:1813:12
    #25 0x7f8eb57cfe0a in jl_apply_generic julia/src/gf.c:2151:23
    #26 0x7f8eb5824e87 in jl_apply julia/src/julia.h:1532:12
    #27 0x7f8eb5824c83 in start_task julia/src/task.c:268:19

0x610009f99278 is located 56 bytes inside of 128-byte region [0x610009f99240,0x610009f992c0)
freed by thread T0 here:
    #0 0x4c602b in free julia/deps/srccache/llvm-6.0.0/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:68:3
    #1 0x7f8eb58fb161 in jl_free_aligned julia/src/julia_internal.h:845:5
    #2 0x7f8eb58fb56b in sweep_big_list julia/src/gc.c:793:13
    #3 0x7f8eb58fac27 in sweep_big julia/src/gc.c:805:9
    #4 0x7f8eb58f99ac in gc_sweep_other julia/src/gc.c:1221:5
    #5 0x7f8eb58f2d29 in _jl_gc_collect julia/src/gc.c:2562:5
    #6 0x7f8eb58f1db2 in jl_gc_collect julia/src/gc.c:2633:13
LLVMSymbolizer: error reading file: No such file or directory.
    #7 0x7f8e9b81e4ff  (/memfd:julia-codegen (deleted)+0x214ff)
    #8 0x7f8eb57cfe0a in jl_apply_generic julia/src/gf.c:2151:23
    #9 0x7f8eb5dc4fb7 in do_call julia/src/interpreter.c:324:26
    #10 0x7f8eb5dc102b in eval_value julia/src/interpreter.c:428:16
    #11 0x7f8eb5dc2e3c in eval_stmt_value julia/src/interpreter.c:363:23
    #12 0x7f8eb5dbea90 in eval_body julia/src/interpreter.c:671:21
    #13 0x7f8eb5dbf8b5 in jl_interpret_toplevel_thunk_callback julia/src/interpreter.c:788:21
    #14 0x7f8eb581252b in enter_interpreter_frame (julia/build/sanitize/usr/bin/../lib/libjulia-debug.so.0.7+0x18852b)
    #15 0x7f8eb5883dc2 in jl_toplevel_eval_flex julia/src/toplevel.c:814:18
    #16 0x7f8eb5887b20 in jl_toplevel_eval julia/src/toplevel.c:823:12
    #17 0x7f8eb57fd4f3 in jl_toplevel_eval_in julia/src/builtins.c:633:13
    #18 0x7f8e9d3e2e0c in japi1_eval_3916 boot.jl:319
    #19 0x7f8eb57cfe0a in jl_apply_generic julia/src/gf.c:2151:23
LLVMSymbolizer: error reading file: No such file or directory.
    #20 0x7f8e9b80597b  (/memfd:julia-codegen (deleted)+0x897b)
LLVMSymbolizer: error reading file: No such file or directory.
    #21 0x7f8e9b8050da  (/memfd:julia-codegen (deleted)+0x80da)
    #22 0x7f8eb57b90b6 in jl_fptr_args julia/src/gf.c:1823:12
    #23 0x7f8eb57b5cde in jl_fptr_trampoline julia/src/gf.c:1813:12
    #24 0x7f8eb57cfe0a in jl_apply_generic julia/src/gf.c:2151:23
    #25 0x7f8eb5824e87 in jl_apply julia/src/julia.h:1532:12
    #26 0x7f8eb5824c83 in start_task julia/src/task.c:268:19

previously allocated by thread T0 here:
    #0 0x4c7125 in __interceptor_posix_memalign julia/deps/srccache/llvm-6.0.0/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:157:3
    #1 0x7f8eb58e667a in jl_malloc_aligned julia/src/julia_internal.h:825:9
    #2 0x7f8eb58e63c9 in jl_gc_big_alloc julia/src/gc.c:742:30
    #3 0x7f8eb58e6c78 in jl_gc_pool_alloc julia/src/gc.c:948:12
    #4 0x7f8eb58e62c1 in jl_gc_alloc_ julia/src/julia_internal.h:274:13
    #5 0x7f8eb58f3cb1 in jl_gc_alloc julia/src/gc.c:2668:12
    #6 0x7f8eb589788c in jl_new_struct julia/src/datatype.c:761:22
    #7 0x7f8eb57a4161 in jl_rewrap_unionall julia/src/jltypes.c:963:9
    #8 0x7f8eb5a9eedf in emit_cfunction(jl_codectx_t&, _jl_value_t*, jl_cgval_t const&, _jl_value_t*, jl_svec_t*) julia/src/codegen.cpp:4784:22
    #9 0x7f8eb5a5844d in emit_expr(jl_codectx_t&, _jl_value_t*, long) julia/src/codegen.cpp:3962:16
    #10 0x7f8eb5af3815 in emit_ssaval_assign(jl_codectx_t&, long, _jl_value_t*) julia/src/codegen.cpp:3616:16
    #11 0x7f8eb5a610c9 in emit_stmtpos(jl_codectx_t&, _jl_value_t*, int) julia/src/codegen.cpp:3853:9
    #12 0x7f8eb599d02b in emit_function(_jl_method_instance_t*, _jl_code_info_t*, unsigned long, _jl_llvm_functions_t*, jl_cgparams_t const*) julia/src/codegen.cpp:6305:13
    #13 0x7f8eb598b878 in jl_compile_linfo julia/src/codegen.cpp:1177:17
    #14 0x7f8eb5a96990 in emit_invoke(jl_codectx_t&, jl_expr_t*, _jl_value_t*) julia/src/codegen.cpp:3101:37
    #15 0x7f8eb5a57def in emit_expr(jl_codectx_t&, _jl_value_t*, long) julia/src/codegen.cpp:3939:16
    #16 0x7f8eb5af3815 in emit_ssaval_assign(jl_codectx_t&, long, _jl_value_t*) julia/src/codegen.cpp:3616:16
    #17 0x7f8eb5a610c9 in emit_stmtpos(jl_codectx_t&, _jl_value_t*, int) julia/src/codegen.cpp:3853:9
    #18 0x7f8eb599d02b in emit_function(_jl_method_instance_t*, _jl_code_info_t*, unsigned long, _jl_llvm_functions_t*, jl_cgparams_t const*) julia/src/codegen.cpp:6305:13
    #19 0x7f8eb598b878 in jl_compile_linfo julia/src/codegen.cpp:1177:17
    #20 0x7f8eb57c882e in jl_compile_method_internal julia/src/gf.c:1778:21
    #21 0x7f8eb57b5c9c in jl_fptr_trampoline julia/src/gf.c:1812:25
    #22 0x7f8eb57cfe0a in jl_apply_generic julia/src/gf.c:2151:23
    #23 0x7f8eb5dc4fb7 in do_call julia/src/interpreter.c:324:26
    #24 0x7f8eb5dc102b in eval_value julia/src/interpreter.c:428:16
    #25 0x7f8eb5dc2e3c in eval_stmt_value julia/src/interpreter.c:363:23
    #26 0x7f8eb5dbea90 in eval_body julia/src/interpreter.c:671:21
    #27 0x7f8eb5dbf8b5 in jl_interpret_toplevel_thunk_callback julia/src/interpreter.c:788:21
    #28 0x7f8eb581252b in enter_interpreter_frame (julia/build/sanitize/usr/bin/../lib/libjulia-debug.so.0.7+0x18852b)
    #29 0x7f8eb5883dc2 in jl_toplevel_eval_flex julia/src/toplevel.c:814:18

SUMMARY: AddressSanitizer: heap-use-after-free julia/src/julia.h:1087:12 in jl_is_concrete_type
Shadow bytes around the buggy address:
  0x0c20813eb1f0: fd fd fd fd fd fd fd fd fa fa fa fa fa fa fa fa
  0x0c20813eb200: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
  0x0c20813eb210: fd fd fd fd fd fd fd fd fa fa fa fa fa fa fa fa
  0x0c20813eb220: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
  0x0c20813eb230: fd fd fd fd fd fd fd fd fa fa fa fa fa fa fa fa
=>0x0c20813eb240: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd[fd]
  0x0c20813eb250: fd fd fd fd fd fd fd fd fa fa fa fa fa fa fa fa
  0x0c20813eb260: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
  0x0c20813eb270: fd fd fd fd fd fd fd fd fa fa fa fa fa fa fa fa
  0x0c20813eb280: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
  0x0c20813eb290: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==13366==ABORTING

Looks like declrt doesn't get properly rooted after emit_cfunction returns?

src/cgutils.cpp Outdated
// else if (jl_subtype(x.typ, type))
// known_isa = true;
// else {
// intersected_type = jl_type_intersection(x.typ, type);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it likely/possible that this bug was caused by commenting this branch out? It seems like commenting this out might change the result of the jl_is_concrete_type(intersected_type) check below for non-constants.

I'm making a test build with this uncommented now, but if anybody happens to know what's actually going on I'd appreciate it...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, doesn't fix the MWE above. I did push the commit to uncomment the branch, though, since it seems more correct to my naive eyes and doesn't hurt the test cases I'm trying to fix with this PR.

src/cgutils.cpp Outdated
else {
else if (jl_subtype(x.typ, type)) {
// known_isa = true;
} else {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, the minimal change that my tests hinge on seems to be here. Here is a gist of the tests I'm using to check this PR.

Before commenting out known_isa = true, some of the tests fail.

After commenting out known_isa = true, all of them pass except for the g(identity, 1) MWE reported earlier.

Does anybody know (or can help figure out) how to get all of these tests passing so this can be merged?

@jrevels jrevels force-pushed the jr/avoidbrokenkindtypes branch 3 times, most recently from 02b9db1 to cc82c07 Compare July 2, 2018 14:27
@jrevels
Copy link
Member Author

jrevels commented Jul 2, 2018

Rebased to avoid going stale. Is there anything extra I should be doing to get responses to the questions in this PR? If I should do anything before requesting more attention on this, please tell me and I'll do it.

Sorry for the repeat bumping, I just fear that this PR will end up like #24852, which was waiting on review for months that never came.

@fisiognomico
Copy link
Contributor

fisiognomico commented Jul 3, 2018

Probably you know this, but maybe it is interesting.
On Julia master, without the patch, @code_warntype returns

julia> @code_warntype g(identity,1)
Variables:
  #self#::typeof(g)
  f::typeof(identity)
  a::Int64

Body:
  begin
      $(Expr(:invoke, MethodInstance for g(::Function, ::Int64, ::Type{Any}), :(#self#), :(f), 
:(a), :(Main.Any)))::Int64
      return Core.SSAValue(1)
  end::Int64

while after the patch,

julia> @code_warntype g(identity,1)
Body::Int64
2 1 ─ %1 = invoke %%#self#(%%f::Function, %%a::Int64, Main.Any::Type{Any})::Int64                   
│
  └──      return %1                                                                                
│

Also looking at @code_llvm, it seems obvious that f misses the optimization pipeline, and is not called directly as a method :

julia> @code_llvm g(identity,1)

; Function g
; Location: REPL[1]:2
; Function Attrs: sspstrong
define i64 @julia_g_28568(i64) #0 {
top:
  %1 = call i64 @julia_g_28477(%jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr 
(i64 140493739219072 to %jl_value_t*) to %jl_value_t addrspace(10)*), i64 %0, %jl_value_t 
addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 140493624934976 to %jl_value_t*) to 
%jl_value_t addrspace(10)*))
  ret i64 %1
}

with respect to what seems a recursive call of boxed values :

julia> @code_llvm g(identity,1)

; Function g
; Location: REPL[1]:2
; Function Attrs: sspstrong
define i64 @julia_g_32922(i64) #0 {
top:
  %1 = alloca %jl_value_t addrspace(10)*, i32 4
  %gcframe = alloca %jl_value_t addrspace(10)*, i32 3
  %2 = bitcast %jl_value_t addrspace(10)** %gcframe to i8*
  call void @llvm.memset.p0i8.i32(i8* %2, i8 0, i32 24, i32 0, i1 false)
  %thread_ptr = call i8* asm "movq %fs:0, $0", "=r"()
  %ptls_i8 = getelementptr i8, i8* %thread_ptr, i64 -10920
  %ptls = bitcast i8* %ptls_i8 to %jl_value_t***
  %3 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 0
  %4 = bitcast %jl_value_t addrspace(10)** %3 to i64*
  store i64 2, i64* %4
  %5 = getelementptr %jl_value_t**, %jl_value_t*** %ptls, i32 0
  %6 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 1
  %7 = bitcast %jl_value_t addrspace(10)** %6 to %jl_value_t***
  %8 = load %jl_value_t**, %jl_value_t*** %5
  store %jl_value_t** %8, %jl_value_t*** %7
  %9 = bitcast %jl_value_t*** %5 to %jl_value_t addrspace(10)***
  store %jl_value_t addrspace(10)** %gcframe, %jl_value_t addrspace(10)*** %9
  %10 = call %jl_value_t addrspace(10)* @jl_box_int64(i64 signext %0)
  %11 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 2
  store %jl_value_t addrspace(10)* %10, %jl_value_t addrspace(10)** %11
  %12 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %1, i32 0
  store %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 140256939622552 to 
%jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %12
  %13 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %1, i32 1
  store %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 140257079738496 to 
%jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %13
  %14 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %1, i32 2
  store %jl_value_t addrspace(10)* %10, %jl_value_t addrspace(10)** %14
  %15 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %1, i32 3
  store %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 140256939557440 to 
%jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %15
  %16 = call nonnull %jl_value_t addrspace(10)* @jl_invoke(%jl_value_t addrspace(10)* 
addrspacecast (%jl_value_t* inttoptr (i64 140256955877904 to %jl_value_t*) to %jl_value_t 
addrspace(10)*), %jl_value_t addrspace(10)** %1, i32 4)
  %17 = bitcast %jl_value_t addrspace(10)* %16 to i64 addrspace(10)*
  %18 = load i64, i64 addrspace(10)* %17, align 8
  %19 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 1
  %20 = load %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %19
  %21 = getelementptr %jl_value_t**, %jl_value_t*** %ptls, i32 0
  %22 = bitcast %jl_value_t*** %21 to %jl_value_t addrspace(10)**
  store %jl_value_t addrspace(10)* %20, %jl_value_t addrspace(10)** %22
  ret i64 %18
}

Probably is totally unrelated, but the heavy reliance on bitcastand recursively loaded pointers reminds me of clojure lowering. Anyway this seems intriguing, tomorrow if I have some spare time I will try to plant some breakpoints around, and see how it messes with the method cache.

@pablosanjose
Copy link
Contributor

pablosanjose commented Jul 6, 2018

I cannot reproduce on MacOS. I tried the test case on more than 30 freshly launched REPLs and it didn't error a single time.

julia> versioninfo()
Julia Version 0.7.0-beta.152
Commit ee07b8cb23 (2018-07-04 17:35 UTC)
Platform Info:
  OS: macOS (x86_64-apple-darwin17.6.0)
  CPU: Intel(R) Core(TM) i7-7567U CPU @ 3.50GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-6.0.0 (ORCJIT, skylake)

@jrevels
Copy link
Member Author

jrevels commented Jul 6, 2018

Gah tests are failing now on the merge commit with master due to #27912

jrevels added 10 commits July 6, 2018 20:54
…oken

Justification for allowing this test to remain broken for now:
- benchmarking the expression (including downstream toy calculations on the output, e.g. broadcast sin) using BenchmarkTools reveals no actual performance difference
- inference returning an optimal result before was probably reliant on the broken subtyping behavior; correctness >>> performance
- inference is still returning a fairly tightly bounded, correct Union type
@jrevels
Copy link
Member Author

jrevels commented Jul 6, 2018

Man, this was so close. Finally got tests passing, and then got sniped at the finish line by changes to master...

I've rebased and will investigate.

@jrevels
Copy link
Member Author

jrevels commented Jul 6, 2018

@Keno did #27912 depend on constant propagation occurring upstream/downstream from the optimizer? This PR disables constant propagation for certain cases where it could yield incorrect results, and after rebasing, the tests from #27912 are failing here. Any tips/insights?

@Keno
Copy link
Member

Keno commented Jul 6, 2018

Which tests are you seeing fail?

@jrevels
Copy link
Member Author

jrevels commented Jul 6, 2018

The ones added in #27912:

# Issue 27910
f27910() = ((),)[2]
@test_throws BoundsError f27910()

# Issue 9765
f9765(::Bool) = 1
g9765() = f9765(isa(1, 1))
@test_throws TypeError g9765()

With this PR, it seems they're behaving the way they would before #27912, i.e.

# no BoundsError
julia> f27910()
()

# no TypeError
julia> g9765()
1

All other tests are passing.

@Keno
Copy link
Member

Keno commented Jul 6, 2018

Are you sure you didn't just accidentally forget to rebuild the sysimage?

@jrevels
Copy link
Member Author

jrevels commented Jul 6, 2018

Are you sure you didn't just accidentally forget to rebuild the sysimage?

I'm not sure of anything these days :p failures are happening on CI here: https://freebsdci.julialang.org/#/builders/1/builds/11024/steps/8/logs/stdio

@jrevels
Copy link
Member Author

jrevels commented Jul 6, 2018

Errr actually it looks like only g9765() is problematic (returns 1), and f27910() is correctly throwing a BoundsError

@jrevels
Copy link
Member Author

jrevels commented Jul 6, 2018

Reason might be because here:

julia> isa_tfunc(Int, Int)
Core.Compiler.Const(false, false)

while on master:

julia> isa_tfunc(Int, Int)
Bool

(where isa_tfunc = Core.Compiler.T_FFUNC_VAL[findfirst(x->x===isa, Core.Compiler.T_FFUNC_KEY)][3])

@Keno
Copy link
Member

Keno commented Jul 6, 2018

Yep, that'd do it (though of course Union{} would be a better answer from the tfunc here). I'll look into rewriting that part of the inliner.

@@ -391,15 +394,17 @@ add_tfunc(isa, 2, 2,
if t === Bottom
return Const(false)
Copy link
Member Author

@jrevels jrevels Jul 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

though of course Union{} would be a better answer from the tfunc here

Changing this to return Bottom seems to fix g9765(), and I think is correct based on this PR's instanceof_tfunc implementation. Unsure, though, this stuff hurts my brain 😛

I'll run tests and see how it goes.

EDIT:

Actually, that's maybe too naive - now trying

diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl
index 22ed40a662..4dca7c05b9 100644
--- a/base/compiler/tfuncs.jl
+++ b/base/compiler/tfuncs.jl
@@ -389,10 +389,17 @@ add_tfunc(typeassert, 2, 2,
           end, 4)
 add_tfunc(isa, 2, 2,
           function (@nospecialize(v), @nospecialize(t))
+              _t = t
               t, isexact = instanceof_tfunc(t)
               if !has_free_typevars(t)
                   if t === Bottom
-                      return Const(false)
+                      # we make this check because `t` could be `Bottom` when `_t` is e.g.
+                      # `Type{Union{}}`, in which case we want to return `Const(false)`
+                      if typeintersect(_t, Type) === Bottom && _t !== typeof(Bottom)
+                          return Bottom
+                      else
+                          return Const(false)
+                      end
                   elseif v ⊑ t
                       if isexact && isnotbrokensubtype(v, t)
                           return Const(true)

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't look quite right. Here's the patch I'm using over in #27972 which needed this same fix:

diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl
index 161e26e984..a7b9d5fa58 100644
--- a/base/compiler/tfuncs.jl
+++ b/base/compiler/tfuncs.jl
@@ -388,12 +390,18 @@ add_tfunc(typeassert, 2, 2,
               return typeintersect(v, t)
           end, 4)
 add_tfunc(isa, 2, 2,
-          function (@nospecialize(v), @nospecialize(t))
-              t, isexact = instanceof_tfunc(t)
+          function (@nospecialize(v), @nospecialize(tt))
+              t, isexact = instanceof_tfunc(tt)
+              if t === Bottom
+                  # check if t could be equivalent to typeof(Bottom), since that's valid in `isa`, but the set of `v` is empty
+                  # if `t` cannot have instances, it's also invalid on the RHS of isa
+                  if typeintersect(widenconst(tt), Type) === Union{}
+                      return Union{}
+                  end
+                  return Const(false)
+              end
               if !has_free_typevars(t)
-                  if t === Bottom
-                      return Const(false)
-                  elseif v ⊑ t
+                  if v ⊑ t
                       if isexact
                           return Const(true)
                       end
@@ -401,7 +409,7 @@ add_tfunc(isa, 2, 2,
                       # this tests for knowledge of a leaftype appearing on the LHS
                       # (ensuring the isa is precise)
                       return Const(false)
-                  elseif isexact && typeintersect(v, t) === Bottom
+                  elseif typeintersect(v, t) === Bottom
                       if !iskindtype(v) #= subtyping currently intentionally answers this query incorrectly for kinds =#
                           return Const(false)
                       end
diff --git a/test/compiler/compiler.jl b/test/compiler/compiler.jl
index 9c6d32d529..e22b12f216 100644
--- a/test/compiler/compiler.jl
+++ b/test/compiler/compiler.jl
@@ -1189,7 +1189,7 @@ let isa_tfunc = Core.Compiler.T_FFUNC_VAL[
     @test isa_tfunc(typeof(Union{}), Const(Int)) === Const(false) # any result is ok
     @test isa_tfunc(typeof(Union{}), Const(Union{})) === Const(false)
     @test isa_tfunc(typeof(Union{}), typeof(Union{})) === Const(false)
-    @test isa_tfunc(typeof(Union{}), Union{}) === Const(false) # any result is ok
+    @test isa_tfunc(typeof(Union{}), Union{}) === Union{}
     @test isa_tfunc(typeof(Union{}), Type{typeof(Union{})}) === Const(true)
     @test isa_tfunc(typeof(Union{}), Const(typeof(Union{}))) === Const(true)
     let c = Conditional(Core.SlotNumber(0), Const(Union{}), Const(Union{}))
@@ -1204,7 +1204,7 @@ let isa_tfunc = Core.Compiler.T_FFUNC_VAL[
     @test isa_tfunc(Val{1}, Type{Val{T}} where T) === Bool
     @test isa_tfunc(Val{1}, DataType) === Bool
     @test isa_tfunc(Any, Const(Any)) === Const(true)
-    @test isa_tfunc(Any, Union{}) === Const(false) # any result is ok
+    @test isa_tfunc(Any, Union{}) === Union{}
     @test isa_tfunc(Any, Type{Union{}}) === Const(false)
     @test isa_tfunc(Union{Int64, Float64}, Type{Real}) === Const(true)
     @test isa_tfunc(Union{Int64, Float64}, Type{Integer}) === Bool

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, nice - this fixes the isa_tfunc(typeof(Union{}), Const(Union{})) that I broke with my version of the fix (it should indeed return Const(false), but my patch was making it return Union{}.

Thanks!

@nanosoldier
Copy link
Collaborator

Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @ararslan

@jrevels
Copy link
Member Author

jrevels commented Jul 7, 2018

Perf changes are spurious, the one CI failure is from curl failing to download something.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler:codegen Generation of LLVM IR and native code compiler:inference Type inference kind:bugfix This change fixes an existing bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

isa_tfunc not working around broken subtyping in the way I hoped it would
8 participants