# This file is a part of Julia. License is MIT: https://julialang.org/license

# tests for codegen and optimizations

using Random
using InteractiveUtils
using InteractiveUtils: code_llvm, code_native
using Libdl
using Test

include("setup_Compiler.jl")

const opt_level = Base.JLOptions().opt_level
const coverage = (Base.JLOptions().code_coverage > 0) || (Base.JLOptions().malloc_log > 0)
const Iptr = sizeof(Int) == 8 ? "i64" : "i32"

const is_debug_build = Base.isdebugbuild()
function libjulia_codegen_name()
    is_debug_build ? "libjulia-codegen-debug" : "libjulia-codegen"
end

# The tests below assume a certain format and safepoint_on_entry=true breaks that.
function get_llvm(@nospecialize(f), @nospecialize(t), raw=true, dump_module=false, optimize=true)
    params = Base.CodegenParams(safepoint_on_entry=false, gcstack_arg = false, debug_info_level=Cint(2))
    d = InteractiveUtils._dump_function(InteractiveUtils.ArgInfo(f, t), false, false, raw, dump_module, :att, optimize, :none, false, "", params)
    sprint(print, d)
end

# Some tests assume calls should be stripped out,
# so strip out the calls to debug intrinsics that
# are not actually materialized as call instructions.
strip_debug_calls(ir) = replace(ir, r"call void @llvm\.dbg\.declare.*\n" => "", r"call void @llvm\.dbg\.value.*\n" => "")

if !is_debug_build && opt_level > 0
    # Make sure getptls call is removed at IR level with optimization on
    @test !occursin(" call ", strip_debug_calls(get_llvm(identity, Tuple{String})))
end

jl_string_ptr(s::String) = ccall(:jl_string_ptr, Ptr{UInt8}, (Any,), s)
core_sizeof(o) = Core.sizeof(o)
function test_loads_no_call(ir, load_types)
    in_function = false
    load_idx = 1
    for line in eachline(IOBuffer(ir))
        if !in_function
            if startswith(line, "define ")
                in_function = true
            end
            continue
        end
        @test !occursin(" call ", line)
        load_split = split(line, " load ", limit=2)
        if !coverage && length(load_split) >= 2
            @test load_idx <= length(load_types)
            if load_idx <= length(load_types)
                @test startswith(load_split[2], "$(load_types[load_idx]),")
            end
            load_idx += 1
        end
        if startswith(line, "}")
            break
        end
    end
    if !coverage
        @test load_idx == length(load_types) + 1
    end
end

# This function tests if functions are output when compiled if jl_dump_compiles is enabled.
# Have to go through pains with recursive function (eval probably not required) to make sure
# that inlining won't happen. (Tests SnoopCompile.jl's @snoopc.)
function test_jl_dump_compiles()
    mktemp() do tfile, io
        @eval(test_jl_dump_compiles_internal(x) = x)
        ccall(:jl_dump_compiles, Cvoid, (Ptr{Cvoid},), io.handle)
        @eval test_jl_dump_compiles_internal(1)
        ccall(:jl_dump_compiles, Cvoid, (Ptr{Cvoid},), C_NULL)
        close(io)
        tstats = stat(tfile)
        tempty = tstats.size == 0
        @test tempty == false
    end
end

# This function tests if a toplevel thunk is output if jl_dump_compiles is enabled.
# The eval statement creates the toplevel thunk. (Tests SnoopCompile.jl's @snoopc.)
function test_jl_dump_compiles_toplevel_thunks()
    mktemp() do tfile, io
        # Make sure to cause compilation of the eval function
        # before calling it below.
        Core.eval(Main, Any[:(nothing)][1])
        GC.enable(false)  # avoid finalizers to be compiled
        topthunk = Meta.lower(Main, :(for i in 1:10; end))
        ccall(:jl_dump_compiles, Cvoid, (Ptr{Cvoid},), io.handle)
        Core.eval(Main, topthunk)
        ccall(:jl_dump_compiles, Cvoid, (Ptr{Cvoid},), C_NULL)
        close(io)
        GC.enable(true)
        tstats = stat(tfile)
        tempty = tstats.size == 0
        @test tempty == true
    end
end

# This function tests if LLVM optimization info is dumped when enabled (Tests
# SnoopCompile.jl's @snoopl.)
function test_jl_dump_llvm_opt()
    mktemp() do func_file, func_io
        mktemp() do llvm_file, llvm_io
            @eval(test_jl_dump_compiles_internal(x) = x)
            ccall(:jl_dump_emitted_mi_name, Cvoid, (Ptr{Cvoid},), func_io.handle)
            ccall(:jl_dump_llvm_opt, Cvoid, (Ptr{Cvoid},), llvm_io.handle)
            @eval test_jl_dump_compiles_internal(1)
            ccall(:jl_dump_emitted_mi_name, Cvoid, (Ptr{Cvoid},), C_NULL)
            ccall(:jl_dump_llvm_opt, Cvoid, (Ptr{Cvoid},), C_NULL)
            close(func_io)
            close(llvm_io)
            @test stat(func_file).size !== 0
            @test stat(llvm_file).size !== 0
        end
    end
end

if !is_debug_build && opt_level > 0
    # Make sure `jl_string_ptr` is inlined
    @test !occursin(" call ", strip_debug_calls(get_llvm(jl_string_ptr, Tuple{String})))
    # Make sure `Core.sizeof` call is inlined
    s = "aaa"
    @test jl_string_ptr(s) == pointer_from_objref(s) + sizeof(Int)
    # String
    test_loads_no_call(strip_debug_calls(get_llvm(core_sizeof, Tuple{String})), [Iptr])
    # String
    test_loads_no_call(strip_debug_calls(get_llvm(core_sizeof, Tuple{Core.SimpleVector})), [Iptr])
    # Array
    test_loads_no_call(strip_debug_calls(get_llvm(sizeof, Tuple{Vector{Int}})), [Iptr])
    # As long as the eltype is known we don't need to load the elsize, but do need to check isvector
    @test_skip test_loads_no_call(strip_debug_calls(get_llvm(sizeof, Tuple{Array{Any}})), ["atomic volatile $Iptr", "ptr", "ptr", Iptr, Iptr, "ptr",  Iptr])
    # Memory
    test_loads_no_call(strip_debug_calls(get_llvm(core_sizeof, Tuple{Memory{Int}})), [Iptr])
    # As long as the eltype is known we don't need to load the elsize
    test_loads_no_call(strip_debug_calls(get_llvm(core_sizeof, Tuple{Memory{Any}})), [Iptr])
    # Check that we load the elsize and isunion from the typeof layout
    test_loads_no_call(strip_debug_calls(get_llvm(core_sizeof, Tuple{Memory})), [Iptr, "atomic volatile $Iptr", "ptr", "i32", "i16"])
    test_loads_no_call(strip_debug_calls(get_llvm(core_sizeof, Tuple{Memory})), [Iptr, "atomic volatile $Iptr", "ptr", "i32", "i16"])
    # Primitive Type size should be folded to a constant
    test_loads_no_call(strip_debug_calls(get_llvm(core_sizeof, Tuple{Ptr})), String[])

    test_jl_dump_compiles()
    test_jl_dump_compiles_toplevel_thunks()
    test_jl_dump_llvm_opt()
end

# Make sure we will not elide the allocation
@noinline create_ref1() = Ref(1)
function pointer_not_safepoint()
    a = create_ref1()
    unsafe_store!(Ptr{Int}(pointer_from_objref(a)), 3)
    return a[]
end
@test pointer_not_safepoint() == 3

# The current memcmp threshold is 512bytes, make sure this struct has the same size on
# 32bits and 64bits
struct LargeStruct
    x::NTuple{1024,Int8}
    LargeStruct() = new()
end

const large_struct = LargeStruct()
@noinline create_ref_struct() = Ref(large_struct)
function compare_large_struct(a)
    b = create_ref_struct()
    if a[] === b[]
        b[].x[1]
    else
        a[].x[2]
    end
end

mutable struct MutableStruct
    a::Int
    MutableStruct() = new()
end

breakpoint_mutable(a::MutableStruct) = ccall(:jl_breakpoint, Cvoid, (Ref{MutableStruct},), a)

# Allocation with uninitialized field as gcroot
mutable struct BadRefMutableStruct
    x::MutableStruct
    y::MutableStruct
    BadRefMutableStruct(x) = new(x)
end
Base.cconvert(::Type{Ptr{BadRefMutableStruct}}, a::MutableStruct) = BadRefMutableStruct(a)
Base.unsafe_convert(::Type{Ptr{BadRefMutableStruct}}, ar::BadRefMutableStruct) = Ptr{BadRefMutableStruct}(pointer_from_objref(ar.x))

breakpoint_badref(a::MutableStruct) = ccall(:jl_breakpoint, Cvoid, (Ptr{BadRefMutableStruct},), a)

struct PtrStruct
    a::Ptr{Cvoid}
    b::Int
end

mutable struct RealStruct
    a::Float64
    b::Int
end

function Base.cconvert(::Type{Ref{PtrStruct}}, a::RealStruct)
    (a, Ref(PtrStruct(pointer_from_objref(a), a.b)))
end
Base.unsafe_convert(::Type{Ref{PtrStruct}}, at::Tuple) =
    Base.unsafe_convert(Ref{PtrStruct}, at[2])

breakpoint_ptrstruct(a::RealStruct) =
    ccall(:jl_breakpoint, Cvoid, (Ref{PtrStruct},), a)

@noinline r_typeassert(c) = c ? (1,1) : nothing
function f_typeassert(c)
    r_typeassert(c)::Tuple
end
@test !occursin("jl_subtype", get_llvm(f_typeassert, Tuple{Bool}))

if opt_level > 0
    @test !occursin("%gcframe", get_llvm(pointer_not_safepoint, Tuple{}))
    compare_large_struct_ir = get_llvm(compare_large_struct, Tuple{typeof(create_ref_struct())})
    @test occursin("call i32 @memcmp(", compare_large_struct_ir) || occursin("call i32 @bcmp(", compare_large_struct_ir)
    @test !occursin("%gcframe", compare_large_struct_ir)

    @test occursin("jl_gc_small_alloc", get_llvm(MutableStruct, Tuple{}))
    breakpoint_mutable_ir = get_llvm(breakpoint_mutable, Tuple{MutableStruct})
    @test !occursin("%gcframe", breakpoint_mutable_ir)
    @test !occursin("jl_gc_small_alloc", breakpoint_mutable_ir)

    breakpoint_badref_ir = get_llvm(breakpoint_badref, Tuple{MutableStruct})
    @test !occursin("%gcframe", breakpoint_badref_ir)
    @test !occursin("jl_gc_small_alloc", breakpoint_badref_ir)

    breakpoint_ptrstruct_ir = get_llvm(breakpoint_ptrstruct, Tuple{RealStruct})
    @test !occursin("%gcframe", breakpoint_ptrstruct_ir)
    @test !occursin("jl_gc_small_alloc", breakpoint_ptrstruct_ir)
end

function two_breakpoint(a::Float64)
    ccall(:jl_breakpoint, Cvoid, (Ref{Float64},), a)
    ccall(:jl_breakpoint, Cvoid, (Ref{Float64},), a)
end

function load_dummy_ref(x::Int)
    r = Ref{Int}(x)
    GC.@preserve r begin
        unsafe_load(Ptr{Int}(pointer_from_objref(r)))
    end
end

if opt_level > 0
    breakpoint_f64_ir = get_llvm((a)->ccall(:jl_breakpoint, Cvoid, (Ref{Float64},), a),
                                 Tuple{Float64})
    @test !occursin("jl_gc_small_alloc", breakpoint_f64_ir)
    breakpoint_any_ir = get_llvm((a)->ccall(:jl_breakpoint, Cvoid, (Ref{Any},), a),
                                 Tuple{Float64})
    @test occursin("jl_gc_small_alloc", breakpoint_any_ir)
    two_breakpoint_ir = get_llvm(two_breakpoint, Tuple{Float64})
    @test !occursin("jl_gc_small_alloc", two_breakpoint_ir)
    @test occursin("llvm.lifetime.end", two_breakpoint_ir)

    @test load_dummy_ref(1234) === 1234
    load_dummy_ref_ir = get_llvm(load_dummy_ref, Tuple{Int})
    @test !occursin("jl_gc_small_alloc", load_dummy_ref_ir)
    # Hopefully this is reliable enough. LLVM should be able to optimize this to a direct return.
    @test occursin("ret $Iptr %\"x::$(Int)\"", load_dummy_ref_ir)
end

# Issue JuliaLang/julia#22770
let was_gced = false
    @noinline make_tuple(x) = tuple(x)
    @noinline use(x) = ccall(:jl_breakpoint, Cvoid, ())
    @noinline assert_not_gced() = @test !was_gced

    function foo22770()
        b = Ref(2)
        finalizer(x -> was_gced = true, b)
        y = make_tuple(b)
        x = y[1]
        a = Ref(1)
        use(x); use(a); use(y)
        c = Ref(3)
        GC.gc()
        assert_not_gced()
        use(x)
        use(c)
    end
    foo22770()
    GC.gc()
    @test was_gced
end

function egal_svecs()
    a = Core.svec(:a, :b)
    b = Core.svec(:a, :b)
    a === b
end
@test egal_svecs()
@test Core.svec(:a, :b) === Core.svec(:a, :b)

# issue #22582
function issue22582!(a::AbstractArray, b)
    len = length(a)
    if b
        ccall(:jl_array_grow_end, Cvoid, (Any, Csize_t), a, 1)
    end
    return len
end
let c = [1,2,3]
    len1 = length(c)
    len2 = issue22582!(c, true)
    @test len1 == len2
end

# PR #23595
@generated f23595(g, args...) = Expr(:call, :g, Expr(:(...), :args))
x23595 = rand(1).ref
@test f23595(Core.memoryrefget, x23595, :not_atomic, true) == x23595[]

# Issue #22421
@noinline f22421_1(x) = x[] + 1
@noinline f22421_2(x) = x[] + 2
@noinline f22421_3(x, y, z, v) = x[] + y[] + z[] + v
function g22421_1(x, y, b)
    # Most likely generates a branch with phi node
    if b
        z = x
        v = f22421_1(y)
    else
        z = y
        v = f22421_2(x)
    end
    return f22421_3(x, y, z, v)
end
function g22421_2(x, y, b)
    # Most likely generates a select
    return f22421_3(x, y, b ? x : y, 1)
end

struct A24108
    x::Vector{Int}
end
struct B24108
    x::A24108
end
@noinline f24108(x) = length(x)
# Test no gcframe is allocated for `x.x.x` even though `x.x` isn't live at the call site
g24108(x::B24108) = f24108(x.x.x)

@test g22421_1(Ref(1), Ref(2), true) === 7
@test g22421_1(Ref(3), Ref(4), false) === 16
@test g22421_2(Ref(5), Ref(6), true) === 17
@test g22421_2(Ref(7), Ref(8), false) === 24

if opt_level > 0
    @test !occursin("%gcframe",
                    get_llvm(g22421_1, Tuple{Base.RefValue{Int},Base.RefValue{Int},Bool}))
    @test !occursin("%gcframe",
                    get_llvm(g22421_2, Tuple{Base.RefValue{Int},Base.RefValue{Int},Bool}))
    @test !occursin("%gcframe", get_llvm(g24108, Tuple{B24108}))
end

str_22330 = """
Base.convert(::Type{Array{T,n}}, a::Array) where {T<:Number,n} =
             copyto!(Array{T,n}(undef, size(a)), a)

empty(Dict(),  Pair{Union{},Union{}})
"""
mktemp() do f_22330, _
    write(f_22330, str_22330)
    @test success(`$(Base.julia_cmd()) --startup-file=no $f_22330`)
end

# Alias scope
function foo31018!(a, b)
    @Base.Experimental.aliasscope for i in eachindex(a, b)
        a[i] = Base.Experimental.Const(b)[i]
    end
end
io = IOBuffer()
code_llvm(io, foo31018!, Tuple{Vector{Int}, Vector{Int}}, optimize=false, raw=true, dump_module=true)
str = String(take!(io))
@test occursin("alias.scope", str)
@test occursin("aliasscope", str)
@test occursin("noalias", str)

# Issue #10208 - Unnecessary boxing for calling objectid
struct FooDictHash{T}
    x::T
end

function f_dict_hash_alloc()
    d = Dict{FooDictHash{Int},Int}()
    for i in 1:10000
        d[FooDictHash(i)] = i+1
    end
    d
end

function g_dict_hash_alloc()
    d = Dict{Int,Int}()
    for i in 1:10000
        d[i] = i+1
    end
    d
end
# Warm up
f_dict_hash_alloc(); g_dict_hash_alloc();
@test abs((@allocated f_dict_hash_alloc()) / (@allocated g_dict_hash_alloc()) - 1) < 0.3

# returning an argument shouldn't alloc a new box
@noinline f33829(x) = (global called33829 = true; x)
g33829() = @allocated Base.inferencebarrier(f33829)(1.1,)
g33829() # warm up
@test (@allocated g33829()) == 0
@test called33829 # make sure there was a global side effect so it's hard for this call to simply be removed
let src = get_llvm(f33829, Tuple{Float64}, true, true)
    @test occursin(r"call [^(]*double @", src)
    @test !occursin(r"call [^(]*\{}", src)
end

# Base.vect prior to PR 41696
function oldvect(X...)
    T = Base.promote_typeof(X...)
    return copyto!(Vector{T}(undef, length(X)), X)
end

let io = IOBuffer()
    # Test for the f(args...) = g(args...) generic codegen optimization
    code_llvm(io, oldvect, Tuple{Vararg{Union{Float64, Int64}}})
    @test !occursin("__apply", String(take!(io)))
end

function f1_30093(r)
    while r[]>0
        try
        finally
        end
    end
end

@test f1_30093(Ref(0)) === nothing

# issue 33590
function f33590(b, x)
    y = b ? nothing : (x[1] + 1,)
    return something(ifelse(b, x, y))
end
@test f33590(true, (3,)) == (3,)
@test f33590(false, (3,)) == (4,)

# issue 29864
const c29864 = VecElement{Union{Int,Nothing}}(2)
@noinline f29864() = c29864
@noinline g29864() = VecElement{Union{Int,Nothing}}(3)
@test f29864().value === 2
@test g29864().value === 3

# test sret pointing into a struct containing a tracked pointer
# reduced from TerminalLoggers/ProgressLogging
const _PROGRESS_LOGGING_UUID_NS_test = Base.UUID("1e962757-ea70-431a-b9f6-aadf988dcb7f")
_asuuid_test(id) = Base.uuid5(_PROGRESS_LOGGING_UUID_NS_test, repr(id))
@noinline _handle_progress_test(progress) = progress
function _handle_message_test()
    progress = (_asuuid_test(:id), "name")
    return _handle_progress_test(progress)
end
@test _handle_message_test() isa Tuple{Base.UUID, String}

@testset "#30739" begin
    ifelsetuple(n::Integer, k::Integer, f, g) = ntuple(i -> (i <= k ? f : g), n)
    f(x) = x^2; g(x) = x^3;
    a = [1]; b = [2]
    @test ifelsetuple(5, 3, a, b) == ([1], [1], [1], [2], [2])
end

@testset "#36422" begin
    str_36422 = "using InteractiveUtils; code_llvm(Base.ht_keyindex, (Dict{NTuple{65,Int64},Nothing}, NTuple{65,Int64}))"
    @test success(`$(Base.julia_cmd()) --startup-file=no -e $str_36422`)
end

@noinline g37262(x) = (x ? error("intentional") : (0x1, "v", "1", ".", "2"))
function f37262(x)
    try
        GC.safepoint()
    catch
        GC.safepoint()
    end
    local a
    try
        GC.gc()
        a = g37262(x)
        Base.inferencebarrier(false) && error()
        return a
    catch ex
        GC.gc()
    finally
        @isdefined(a) && Base.donotdelete(a)
        GC.gc()
    end
end
@testset "#37262" begin
    str_opaque = "getelementptr inbounds i8, ptr %.roots.phic, i32 8\n  store volatile ptr null"
    llvmstr = get_llvm(f37262, (Bool,), false, false, false)
    @test contains(llvmstr, str_opaque)
    @test f37262(Base.inferencebarrier(true)) === nothing
end

# issue #37671
let d = Dict((:a,) => 1, (:a, :b) => 2)
    @test d[(:a,)] == 1
    @test d[(:a, :b)] == 2
end

# issue #37880
primitive type Has256Bits 256 end
let x = reinterpret(Has256Bits, [0xfcdac822cac89d82de4f9b3326da8294, 0x6ebac4d5982880ca703c57e37657f1ee])[]
    shifted = [0xeefcdac822cac89d82de4f9b3326da82, 0x006ebac4d5982880ca703c57e37657f1]
    f(x) = Base.lshr_int(x, 0x8)
    @test reinterpret(UInt128, [f(x)]) == shifted
    @test reinterpret(UInt128, [Base.lshr_int(x, 0x8)]) == shifted
    g(x) = Base.ashr_int(x, 0x8)
    @test reinterpret(UInt128, [g(x)]) == shifted
    @test reinterpret(UInt128, [Base.ashr_int(x, 0x8)]) == shifted
    lshifted = [0xdac822cac89d82de4f9b3326da829400, 0xbac4d5982880ca703c57e37657f1eefc]
    h(x) = Base.shl_int(x, 0x8)
    @test reinterpret(UInt128, [h(x)]) == lshifted
    @test reinterpret(UInt128, [Base.shl_int(x, 0x8)]) == lshifted
end

# issue #37872
let f(@nospecialize(x)) = x===Base.ImmutableDict(Int128=>:big)
    @test !f(Dict(Int=>Int))
end

# issue #37974
primitive type UInt24 24 end
let a = Core.Intrinsics.trunc_int(UInt24, 3),
    f(t) = t[2]
    @test f((a, true)) === true
    @test f((a, false)) === false
    @test sizeof(Tuple{UInt24,Bool}) == 8
    @test sizeof(UInt24) == 3
    @test sizeof(Union{UInt8,UInt24}) == 3
    @test sizeof(Base.RefValue{Union{UInt8,UInt24}}) == 8
end

# issue #39232
function f39232(a)
    z = Any[]
    for (i, ai) in enumerate(a)
        push!(z, ai)
    end
    return z
end
@test f39232((+, -)) == Any[+, -]

@testset "GC.@preserve" begin
    # main use case
    function f1(cond)
        val = [1]
        GC.@preserve val begin end
        return cond
    end
    @test occursin("llvm.julia.gc_preserve_begin", get_llvm(f1, Tuple{Bool}, true, false, false))

    # stack allocated objects (JuliaLang/julia#34241)
    function f3(cond)
        val = ([1],)
        GC.@preserve val begin end
        return cond
    end
    @test occursin("llvm.julia.gc_preserve_begin", get_llvm(f3, Tuple{Bool}, true, false, false))

    # PhiNode of unions of immutables (JuliaLang/julia#39501)
    function f2(cond)
        val = cond ? 1 : ""
        GC.@preserve val begin end
        return cond
    end
    @test occursin("llvm.julia.gc_preserve_begin", get_llvm(f2, Tuple{Bool}, true, false, false))
    # make sure the fix for the above doesn't regress #34241
    function f4(cond)
        val = cond ? ([1],) : ([1f0],)
        GC.@preserve val begin end
        return cond
    end
    @test occursin("llvm.julia.gc_preserve_begin", get_llvm(f4, Tuple{Bool}, true, false, false))
end

# issue #32843
function f32843(vals0, v)
    (length(vals0) > 1) && (vals = v[1])
    (length(vals0) == 1 && vals0[1]==1) && (vals = 1:2)
    vals
end
@test_throws UndefVarError f32843([6], Vector[[1]])

# issue #40855, struct constants with union fields
@enum T40855 X40855
struct A40855
    d::Union{Nothing, T40855}
    b::Union{Nothing, Int}
end
g() = string(A40855(X40855, 1))
let mod_prefix = (@__MODULE__) == Core.Main ? "" : "$(@__MODULE__)."
    @test g() == "$(mod_prefix)A40855($(mod_prefix)X40855, 1)"
end

# issue #40612
f40612(a, b) = a|b === a|b
g40612(a, b) = a[]|a[] === b[]|b[]
@test f40612(true, missing)
@test !g40612(Union{Bool,Missing}[missing], Union{Bool,Missing}[true])
@test !g40612(Union{Bool,Missing}[false], Union{Bool,Missing}[true])
@test g40612(Union{Bool,Missing}[missing], Union{Bool,Missing}[missing])
@test g40612(Union{Bool,Missing}[true], Union{Bool,Missing}[true])
@test g40612(Union{Bool,Missing}[false], Union{Bool,Missing}[false])

# issue #41438
struct A41438{T}
    x::Ptr{T}
end
struct B41438{T}
    x::T
end
f41438(y) = y[].x
@test A41438.body.layout != C_NULL
@test B41438.body.layout === C_NULL
@test f41438(Ref{A41438}(A41438(C_NULL))) === C_NULL
@test f41438(Ref{B41438}(B41438(C_NULL))) === C_NULL

const S41438 = Pair{Any, Ptr{T}} where T
g41438() = Array{S41438,1}(undef,1)[1].first
get_llvm(g41438, ()); # cause allocation of layout
@test S41438.body.layout != C_NULL
@test !Base.datatype_pointerfree(S41438.body)
@test S41438{Int}.layout != C_NULL
@test !Base.datatype_pointerfree(S41438{Int})


# issue #43303
struct A43303{T}
    x::Pair{Ptr{T},Ptr{T}}
end
@test A43303.body.layout != C_NULL
@test isbitstype(A43303{Int})
@test A43303.body.types[1].layout != C_NULL

# issue #41157
f41157(a, b) = a[1] = b[1]
@test_throws BoundsError f41157(Tuple{Int}[], (NTuple{N,Union{}} where N)[])

# issue #41096
struct Modulate41096{M<:Union{Function, Val{true}, Val{false}}, id}
    modulate::M
    Modulate41096(id::Symbol, modulate::Function) = new{typeof(modulate), id}(modulate)
    Modulate41096(id::Symbol, modulate::Bool=true) = new{Val{modulate}, id}(modulate|>Val)
end
@inline ismodulatable41096(modulate::Modulate41096) = ismodulatable41096(typeof(modulate))
@inline ismodulatable41096(::Type{<:Modulate41096{Val{B}}}) where B = B
@inline ismodulatable41096(::Type{<:Modulate41096{<:Function}}) = true

mutable struct Term41096{I, M<:Modulate41096}
    modulate::M
    Term41096{I}(modulate::Modulate41096) where I = new{I, typeof(modulate)}(modulate)
end
@inline ismodulatable41096(term::Term41096) = ismodulatable41096(typeof(term))
@inline ismodulatable41096(::Type{<:Term41096{I, M} where I}) where M = ismodulatable41096(M)

function newexpand41096(gen, name::Symbol)
    flag = ismodulatable41096(getfield(gen, name))
    if flag
        return true
    else
        return false
    end
end

t41096 = Term41096{:t}(Modulate41096(:t, false))
μ41096 = Term41096{:μ}(Modulate41096(:μ, false))
U41096 = Term41096{:U}(Modulate41096(:U, false))

@test !newexpand41096((t=t41096, μ=μ41096, U=U41096), :U)


# test that we can start julia with libjulia-codegen removed; PR #41936
mktempdir() do pfx
    cp(dirname(Sys.BINDIR), pfx; force=true)
    libpath = relpath(dirname(dlpath(libjulia_codegen_name())), dirname(Sys.BINDIR))
    libs_deleted = 0
    libfiles = filter(f -> startswith(f, "libjulia-codegen"), readdir(joinpath(pfx, libpath)))
    for f in libfiles
        rm(joinpath(pfx, libpath, f); force=true, recursive=true)
        libs_deleted += 1
    end
    @test libs_deleted > 0
    @test readchomp(`$pfx/bin/$(Base.julia_exename()) --startup-file=no -e 'print("no codegen!\n")'`) == "no codegen!"

    # PR #47343
    libs_emptied = 0
    for f in libfiles
        touch(joinpath(pfx, libpath, f))
        libs_emptied += 1
    end

    errfile = joinpath(pfx, "stderr.txt")
    @test libs_emptied > 0
    @test_throws ProcessFailedException run(pipeline(`$pfx/bin/$(Base.julia_exename()) -e 'print("This should fail!\n")'`; stderr=errfile))
    @test contains(readline(errfile), "ERROR: Unable to load dependent library")
end

# issue #42645
mutable struct A42645{T}
    x::Bool
    function A42645(a::Vector{T}) where T
        r = new{T}()
        r.x = false
        return r
    end
end
mutable struct B42645{T}
    y::A42645{T}
end
x42645 = 1
function f42645()
    res = B42645(A42645([x42645]))
    res.y = A42645([x42645])
    res.y.x = true
    res
end
@test ((f42645()::B42645).y::A42645{Int}).x

struct A44921{T}
    x::T
end
function f44921(a)
    if a === :x
        A44921(_f) # _f purposefully undefined
    elseif a === :p
        g44921(a)
    end
end
function g44921(a)
    if !@isdefined _f # just needs to be some non constprop-able condition
        A44921(())
    end
end
@test f44921(:p) isa A44921

# issue #43123
@noinline cmp43123(a::Some, b::Some) = something(a) === something(b)
@noinline cmp43123(a, b) = a[] === b[]
@test cmp43123(Some{Function}(+), Some{Union{typeof(+), typeof(-)}}(+))
@test !cmp43123(Some{Function}(+), Some{Union{typeof(+), typeof(-)}}(-))
@test cmp43123(Ref{Function}(+), Ref{Union{typeof(+), typeof(-)}}(+))
@test !cmp43123(Ref{Function}(+), Ref{Union{typeof(+), typeof(-)}}(-))
@test cmp43123(Function[+], Union{typeof(+), typeof(-)}[+])
@test !cmp43123(Function[+], Union{typeof(+), typeof(-)}[-])

# Test that donotdelete survives through to LLVM time
f_donotdelete_input(x) = Base.donotdelete(x+1)
f_donotdelete_const() = Base.donotdelete(1+1)
@test occursin("call void (...) @jl_f_donotdelete(i64", get_llvm(f_donotdelete_input, Tuple{Int64}, true, false, false))
@test occursin("call void (...) @jl_f_donotdelete()", get_llvm(f_donotdelete_const, Tuple{}, true, false, false))

# Test 45476 fixes
struct MaybeTuple45476
    val::Union{Nothing, Tuple{Float32}}
end

@test MaybeTuple45476((0,)).val[1] == 0f0

# Test int paths for getfield/isdefined
f_getfield_nospecialize(@nospecialize(x)) = getfield(x, 1)
f_isdefined_nospecialize(@nospecialize(x)) = isdefined(x, 1)

@test !occursin("jl_box_int", get_llvm(f_getfield_nospecialize, Tuple{Any}, true, false, false))
@test !occursin("jl_box_int", get_llvm(f_isdefined_nospecialize, Tuple{Any}, true, false, false))

# Test codegen for isa(::Any, Type)
f_isa_type(@nospecialize(x)) = isa(x, Type)
@test !occursin("jl_isa", get_llvm(f_isa_type, Tuple{Any}, true, false, false))

# Issue #47247
f47247(a::Ref{Int}, b::Nothing) = setfield!(a, :x, b)
@test_throws TypeError f47247(Ref(5), nothing)

f48085(@nospecialize x...) = length(x)
@test Compiler.get_compileable_sig(which(f48085, (Vararg{Any},)), Tuple{typeof(f48085), Vararg{Int}}, Core.svec()) === nothing
@test Compiler.get_compileable_sig(which(f48085, (Vararg{Any},)), Tuple{typeof(f48085), Int, Vararg{Int}}, Core.svec()) === Tuple{typeof(f48085), Any, Vararg{Any}}

# Make sure that the bounds check is elided in tuple iteration
@test !occursin("call void @", strip_debug_calls(get_llvm(iterate, Tuple{NTuple{4, Float64}, Int})))

# issue #34459
function f34459(args...)
    Base.pointerset(args[1], 1, 1, 1)
    return
end
@test !occursin("jl_f_tuple", get_llvm(f34459, Tuple{Ptr{Int}, Type{Int}}, true, false, false))

# issue #48394: incorrectly-inferred getproperty shouldn't introduce invalid cgval_t
#               when dealing with unions of ghost values
struct X48394
    x::Nothing
    y::Bool
end
struct Y48394
    x::Nothing
    z::Missing
end
function F48394(a, b, i)
    c = i ? a : b
    c.y
end
@test F48394(X48394(nothing,true), Y48394(nothing, missing), true)
@test occursin("llvm.trap", get_llvm(F48394, Tuple{X48394, Y48394, Bool}))

# issue 48917, hoisting load to above the parent
f48917(x, w) = (y = (a=1, b=x); z = (; a=(a=(1, w), b=(3, y))))
@test f48917(1,2) == (a = (a = (1, 2), b = (3, (a = 1, b = 1))),)

# https://github.com/JuliaLang/julia/issues/50317 getproperty allocation on struct with 1 field
struct Wrapper50317
    lock::ReentrantLock
end
const MONITOR50317 = Wrapper50317(ReentrantLock())
issue50317() = @noinline MONITOR50317.lock
issue50317()
let res = @timed issue50317()
    @test res.bytes == 0
    return res # must return otherwise the compiler may eliminate the result entirely
end
struct Wrapper50317_2
    lock::ReentrantLock
    fun::Vector{Int}
end
const MONITOR50317_2 = Wrapper50317_2(ReentrantLock(),[1])
issue50317_2() = @noinline MONITOR50317.lock
issue50317_2()
let res = @timed issue50317_2()
    @test res.bytes == 0
    return res
end
const a50317 = (b=3,)
let res = @timed a50317[:b]
    @test res.bytes == 0
    return res
end

# https://github.com/JuliaLang/julia/issues/50964
@noinline bar50964(x::Core.Const) = Base.inferencebarrier(1)
@noinline bar50964(x::DataType) = Base.inferencebarrier(2)
foo50964(x) = bar50964(Base.inferencebarrier(Core.Const(x)))
foo50964(1) # Shouldn't assert!

# https://github.com/JuliaLang/julia/issues/51233
obj51233 = (1,)
@test_throws FieldError obj51233.x

# Very specific test for multiversioning
if Sys.ARCH === :x86_64
    foo52079() = Core.Intrinsics.have_fma(Float64)
    if foo52079() == true
        let io = IOBuffer()
            code_native(io,Base.Math.exp_impl,(Float64,Float64,Val{:ℯ}), dump_module=false)
            str = String(take!(io))
            @test !occursin("fma_emulated", str)
            @test occursin("vfmadd", str)
        end
    end
end

#Check if we aren't emitting the store with the wrong TBAA metadata

foo54166(x,i,y) = x[i] = y
let io = IOBuffer()
    code_llvm(io,foo54166, (Vector{Union{Missing,Int}}, Int, Int), dump_module=true, raw=true)
    str = String(take!(io))
    @test !occursin("jtbaa_unionselbyte", str)
    @test occursin("jtbaa_arrayselbyte", str)
end

ex54166 = Union{Missing, Int64}[missing -2; missing -2];
dims54166 = (1,2)
@test (minimum(ex54166; dims=dims54166)[1] === missing)

bar54599() = Base.inferencebarrier(true) ? (Base.PkgId(Main),1) : nothing

function foo54599()
    pkginfo = @noinline bar54599()
    pkgid = pkginfo !== nothing ? pkginfo[1] : nothing
    @noinline println(devnull, pkgid)
    pkgid.uuid !== nothing ? pkgid.uuid : false
end

#this function used to crash allocopt due to a no predecessors bug
barnopreds() = Base.inferencebarrier(true) ? (Base.PkgId(Test),1) : nothing
function foonopreds()
    pkginfo = @noinline barnopreds()
    pkgid = pkginfo !== nothing ? pkginfo[1] : nothing
    pkgid.uuid !== nothing ? pkgid.uuid : false
end
@test foonopreds() !== nothing

# issue 55396
struct Incomplete55396
  x::Tuple{Int}
  y::Int
  @noinline Incomplete55396(x::Int) = new((x,))
end
let x = Incomplete55396(55396)
    @test x.x === (55396,)
end

# Core.getptls() special handling
@test !occursin("call ptr @jlplt", get_llvm(Core.getptls, Tuple{})) #It should lower to a direct load of the ptls and not a ccall

# issue 55208
@noinline function f55208(x, i)
    z = (i == 0 ? x[1] : x[i])
    return z isa Core.TypeofBottom
end
@test f55208((Union{}, 5, 6, 7), 0)

@noinline function g55208(x, i)
    z = (i == 0 ? x[1] : x[i])
    typeof(z)
end
@test g55208((Union{}, true, true), 0) === typeof(Union{})

@test string((Core.Union{}, true, true, true)) == "(Union{}, true, true, true)"

# Issue #55558
for (T, StructName) in ((Int128, :Issue55558), (UInt128, :UIssue55558))
    @eval begin
        struct $(StructName)
            a::$(T)
            b::Int64
            c::$(T)
        end
        local broken_i128 = Base.BinaryPlatforms.arch(Base.BinaryPlatforms.HostPlatform()) == "powerpc64le"
        @test fieldoffset($(StructName), 2) == 16
        @test fieldoffset($(StructName), 3) == 32 broken=broken_i128
        @test sizeof($(StructName)) == 48 broken=broken_i128
    end
end

# Issue #42326
primitive type PadAfter64_42326 448 end
mutable struct CheckPadAfter64_42326
    a::UInt64
    pad::PadAfter64_42326
    b::UInt64
end
@test fieldoffset(CheckPadAfter64_42326, 3) == 80
@test sizeof(CheckPadAfter64_42326) == 96

@noinline Base.@nospecializeinfer f55768(@nospecialize z::UnionAll) = z === Vector
@test f55768(Vector)
@test f55768(Vector{T} where T)
@test !f55768(Vector{S} where S)

# test that values get rooted correctly over throw
for a in ((@noinline Ref{Int}(2)),
          (@noinline Ref{Int}(3)),
          5,
          (@noinline Ref{Int}(4)),
          6)
    @test a[] != 0
    try
        b = (@noinline Ref{Int}(5),
             @noinline Ref{Int}(6),
             @noinline Ref{Int}(7),
             @noinline Ref{Int}(8),
             @noinline Ref{Int}(9),
             @noinline Ref{Int}(10),
             @noinline Ref{Int}(11))
        GC.gc(true)
        GC.@preserve b throw(a)
    catch ex
        @test ex === a
    end
end

# Make sure that code that has unbound sparams works
#https://github.com/JuliaLang/julia/issues/56739

@test_warn r"declares type variable T but does not use it" @eval f56739(a) where {T} = a

@test f56739(1) == 1
g56739(x) = @noinline f56739(x)
@test g56739(1) == 1

struct Vec56937 x::NTuple{8, VecElement{Int}} end

x56937 = Ref(Vec56937(ntuple(_->VecElement(1),8)))
@test x56937[].x[1] == VecElement{Int}(1) # shouldn't crash

# issue #56996
let
   ()->() # trigger various heuristics
   Base.Experimental.@force_compile
   default_rng_orig = [] # make a value in a Slot
   try
       # overwrite the gc-slots in the exception branch
       throw(ErrorException("This test is supposed to throw an error"))
   catch ex
       # destroy any values that aren't referenced
       GC.gc()
       # make sure that default_rng_orig value is still valid
       @noinline copy!([], default_rng_orig)
   end
   nothing
end

# Test that turning an implicit import into an explicit one doesn't pessimize codegen
module TurnedIntoExplicit
    using Test
    import ..get_llvm

    module ReExportBitCast
        export bitcast
        import Base: bitcast
    end
    using .ReExportBitCast

    f(x::UInt) = bitcast(Float64, x)

    @test !occursin("jl_apply_generic", get_llvm(f, Tuple{UInt}))

    import Base: bitcast

    @test !occursin("jl_apply_generic", get_llvm(f, Tuple{UInt}))
end

# Test codegen for `isdefinedglobal` of constant (#57872)
const x57872 = "Hello"
f57872() = (Core.isdefinedglobal(@__MODULE__, Base.compilerbarrier(:const, :x57872)), x57872) # Extra globalref here to force world age bounds
@test f57872() == (true, "Hello")

@noinline f_mutateany(@nospecialize x) = x[] = 1
g_mutateany() = (y = Ref(0); f_mutateany(y); y[])
@test g_mutateany() === 1

# 58470 tbaa for unionselbyte of heap allocated mutables
mutable struct Wrapper58470
    x::Union{Nothing,Int}
end

function findsomething58470(dict, inds)
    default = Wrapper58470(nothing)
    for i in inds
        x = get(dict, i, default).x
        if !isnothing(x)
            return x
        end
    end
    return nothing
end

let io = IOBuffer()
    code_llvm(io, findsomething58470, Tuple{Dict{Int64, Wrapper58470}, Vector{Int}}, dump_module=true, raw=true, optimize=false)
    str = String(take!(io))
    @test !occursin("jtbaa_unionselbyte", str)
end

let io = IOBuffer()
    code_llvm(io, (x, y) -> (@atomic x[1] = y; nothing), (AtomicMemory{Pair{Any,Any}}, Pair{Any,Any},), raw=true, optimize=false)
    str = String(take!(io))
    @test occursin("julia.write_barrier", str)
end

# Test phi node codegen for union types with inline roots
function union_phi_inline_roots(x::Bool)
    if x
        return ("Q8", 1)
    else
        return ("Q10", Ref(5))
    end
end
@test union_phi_inline_roots(true) === ("Q8", 1)

mutable struct AnyBoxEA val::Any end
function preserve_any_ea(x)
    b = AnyBoxEA(x)
    GC.@preserve b begin
        return b.val
    end
end
function loop_preserve_any_ea(n)
    s = "v"
    for _ in 1:n
        s = preserve_any_ea(s)::String
    end
    s
end
loop_preserve_any_ea(10)
@test (@allocated loop_preserve_any_ea(10)) == 0

# blackbox compiles to zero-cost inline asm, not a runtime call
@testset "blackbox codegen" begin
    # Scalar blackbox: should produce inline asm, no call
    blackbox_int(x::Int) = Base.blackbox(x)
    ir_int = get_llvm(blackbox_int, Tuple{Int})
    @test !occursin("call ", strip_debug_calls(ir_int)) || occursin("asm", ir_int)
    @test !occursin("jl_", ir_int)

    # Pointer/boxed blackbox: should produce julia.blackbox intrinsic (lowered to asm after GC)
    blackbox_str(x::String) = Base.blackbox(x)
    ir_str = get_llvm(blackbox_str, Tuple{String}, true, false, false)
    @test occursin("julia.blackbox", ir_str)

    # blackbox preserves value identity at runtime
    @test Base.blackbox(42) == 42
    @test Base.blackbox([1,2,3]) == [1,2,3]

    # blackbox does not allocate for isbits types
    @test (@allocated Base.blackbox(42)) == 0

    # No gc frame needed for scalar blackbox
    @test !occursin("%gcframe", get_llvm(blackbox_int, Tuple{Int}))

    # Struct blackbox: uses memory clobber for unboxed aggregates
    struct BlackboxTestStruct
        a::Float64
        b::Float64
    end
    blackbox_struct(x::BlackboxTestStruct) = Base.blackbox(x)
    @test blackbox_struct(BlackboxTestStruct(1.0, 2.0)) == BlackboxTestStruct(1.0, 2.0)
    ir_struct = get_llvm(blackbox_struct, Tuple{BlackboxTestStruct})
    @test occursin("~{memory}", ir_struct)
    @test !occursin("jl_", strip_debug_calls(ir_struct))

    # blackbox barriers Julia-level constprop: return type must be Int, not Const(42)
    @test Base.return_types() do; Base.blackbox(42); end |> only === Int
end

# sret parameters must have an alignment attribute (required by LLVM LangRef).
@testset "sret alignment attribute" begin
    struct SretAlignTest
        a::Float32
        b::Float32
        c::Float32
    end
    @noinline f_srettest(x::Float32) = SretAlignTest(x, x+1, x+2)
    ir = get_llvm(f_srettest, Tuple{Float32}, true, true, true)
    @test occursin(r"sret\([^)]+\) align \d+", ir)
end

# ipo_purity_bits are translated into LLVM call-site attributes
function _fib_cse_test(n::Int)
    n <= 1 && return n
    n == 2 && return 1
    return _fib_cse_test(n-1) + _fib_cse_test(n-2)
end
_bench_cse_test() = _fib_cse_test(40)
_fib_cse_test(5); _bench_cse_test()
@noinline _pure_effects_attrtest(x::Float64) = x * x + 1.0
_pure_effects_attrcaller(x::Float64) = _pure_effects_attrtest(x) + 1.0
_pure_effects_attrtest(1.0); _pure_effects_attrcaller(1.0)
@noinline _pure_effects_licmcallee(x::Float64) = x * x * x + 2.0 * x * x + x + 1.0
function _pure_effects_licmloop(A::Vector{Float64}, x::Float64)
    for i in eachindex(A)
        A[i] = _pure_effects_licmcallee(x)
    end
    return A
end
function _blackbox_licmloop(A::Vector{Float64}, x::Float64)
    for i in eachindex(A)
        A[i] = _pure_effects_licmcallee(Base.blackbox(x))
    end
    return A
end
function _blackbox_licmloop_simple(x::Float64)
    for i in 1:5
        y = Base.blackbox(x)
        z = _pure_effects_licmcallee(y)
        Base.donotdelete(z)
    end
    return nothing
end
_pure_effects_licmcallee(1.0); _pure_effects_licmloop(Float64[0.0], 1.0)
_blackbox_licmloop(Float64[0.0], 1.0)
_blackbox_licmloop_simple(1.0)
@testset "effects to LLVM attributes" begin
    # CSE: duplicate fib call eliminated by GVN using memory(argmem: read)
    ir_bench = get_llvm(_bench_cse_test, Tuple{}, true, false, true)
    @test count(r"call (swiftcc )?i\d+ @j__fib", ir_bench) == 3  # 4 calls reduced to 3

    # Attribute emission: ipo_purity_bits translated to LLVM attrs on pure @noinline calls
    ir_attrs = get_llvm(_pure_effects_attrcaller, Tuple{Float64}, true, true, false)
    @test occursin("nounwind", ir_attrs)
    @test occursin("willreturn", ir_attrs)
    @test occursin("memory(argmem: read)", ir_attrs)

    re_licmcall = r"call\b.*@j__pure_effects_licmcallee"
    re_raw_arg = r"@j__pure_effects_licmcallee\w*\(double %\"x::Float64\"\)"

    # LICM: loop-invariant pure call hoisted out of loop (takes raw argument)
    ir_licm = get_llvm(_pure_effects_licmloop, Tuple{Vector{Float64}, Float64}, true, false, true)
    @test count(re_licmcall, ir_licm) == 1
    @test occursin(re_raw_arg, ir_licm)

    # blackbox prevents LICM: the callee takes the asm barrier output instead
    # of the raw argument, proving it depends on the barrier.
    ir_bb = get_llvm(_blackbox_licmloop, Tuple{Vector{Float64}, Float64}, true, false, true)
    @test count(re_licmcall, ir_bb) == 1
    @test !occursin(re_raw_arg, ir_bb)

    # blackbox prevents LICM in a simple loop without memory writes
    ir_bb_simple = get_llvm(_blackbox_licmloop_simple, Tuple{Float64}, true, false, true)
    @test count(re_licmcall, ir_bb_simple) == 5  # unrolled, not hoisted
    @test !occursin(re_raw_arg, ir_bb_simple)
end
