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

using Base: llvmcall
using InteractiveUtils: code_llvm

#function add1234(x::Tuple{Int32,Int32,Int32,Int32})
#    llvmcall("""%3 = add <4 x i32> %1, %0
#                ret <4 x i32> %3""",
#        Tuple{Int32,Int32,Int32,Int32},
#        Tuple{Tuple{Int32,Int32,Int32,Int32},
#        Tuple{Int32,Int32,Int32,Int32}},
#        (Int32(1),Int32(2),Int32(3),Int32(4)),
#        x)
#end
#
#function add1234(x::NTuple{4,Int64})
#    llvmcall("""%3 = add <4 x i64> %1, %0
#      ret <4 x i64> %3""",NTuple{4,Int64},
#      Tuple{NTuple{4,Int64},NTuple{4,Int64}},
#        (Int64(1),Int64(2),Int64(3),Int64(4)),
#      x)
#end
#
function add1234(x::Tuple{Int32,Int32,Int32,Int32})
    llvmcall("""%3 = extractvalue [4 x i32] %0, 0
      %4 = extractvalue [4 x i32] %0, 1
      %5 = extractvalue [4 x i32] %0, 2
      %6 = extractvalue [4 x i32] %0, 3
      %7 = extractvalue [4 x i32] %1, 0
      %8 = extractvalue [4 x i32] %1, 1
      %9 = extractvalue [4 x i32] %1, 2
      %10 = extractvalue [4 x i32] %1, 3
      %11 = add i32 %3, %7
      %12 = add i32 %4, %8
      %13 = add i32 %5, %9
      %14 = add i32 %6, %10
      %15 = insertvalue [4 x i32] undef, i32 %11, 0
      %16 = insertvalue [4 x i32] %15, i32 %12, 1
      %17 = insertvalue [4 x i32] %16, i32 %13, 2
      %18 = insertvalue [4 x i32] %17, i32 %14, 3
      ret [4 x i32] %18""",Tuple{Int32,Int32,Int32,Int32},
      Tuple{Tuple{Int32,Int32,Int32,Int32},Tuple{Int32,Int32,Int32,Int32}},
        (Int32(1),Int32(2),Int32(3),Int32(4)),
        x)
end

@test add1234(map(Int32,(2,3,4,5))) === map(Int32,(3,5,7,9))
#@test add1234(map(Int64,(2,3,4,5))) === map(Int64,(3,5,7,9))

# Test whether llvmcall escapes the function name correctly
baremodule PlusTest
    using Base: llvmcall
    using Test
    using Base

    function +(x::Int32, y::Int32)
        llvmcall("""%3 = add i32 %1, %0
                    ret i32 %3""",
            Int32,
            Tuple{Int32, Int32},
            x,
            y)
    end
    @test Int32(1) + Int32(2) == Int32(3)
end

# issue #11800
@test_throws ErrorException eval(Expr(:call,Core.Intrinsics.llvmcall,
    """%3 = add i32 %1, %0
       ret i32 %3""", Int32, Tuple{Int32, Int32},
        Int32(1), Int32(2))) # llvmcall must be compiled to be called

#Since LLVM 18, LLVM does a best effort to automatically include the intrinsics
function undeclared_ceil(x::Float64)
    llvmcall("""%2 = call double @llvm.ceil.f64(double %0)
        ret double %2""", Float64, Tuple{Float64}, x)
end
@test undeclared_ceil(4.2) == 5.0
@test undeclared_ceil(4.2) == 5.0

function declared_floor(x::Float64)
    llvmcall(
        ("""declare double @llvm.floor.f64(double)
            define double @entry(double) #0 {
            1:
                %2 = call double @llvm.floor.f64(double %0)
                ret double %2
            }
            attributes #0 = { alwaysinline }
         """, "entry"), Float64, Tuple{Float64}, x)
end
@test declared_floor(4.2) ≈ 4.
ir = sprint(code_llvm, declared_floor, Tuple{Float64})
@test occursin("call double @llvm.floor.f64", ir) # should be inlined

function doubly_declared_floor(x::Float64)
    llvmcall(
        ("""declare double @llvm.floor.f64(double)
            define double @entry(double) #0 {
            1:
                %2 = call double @llvm.floor.f64(double %0)
                ret double %2
            }
            attributes #0 = { alwaysinline }
         """, "entry"), Float64, Tuple{Float64}, x+1)-1
end
@test doubly_declared_floor(4.2) ≈ 4.

function doubly_declared2_trunc(x::Float64)
    a = llvmcall(
        ("""declare double @llvm.trunc.f64(double)
            define double @entry(double) #0 {
            1:
                %2 = call double @llvm.trunc.f64(double %0)
                ret double %2
            }
            attributes #0 = { alwaysinline }
         """, "entry"), Float64, Tuple{Float64}, x)
    b = llvmcall(
        ("""declare double @llvm.trunc.f64(double)
            define double @entry(double) #0 {
            1:
                %2 = call double @llvm.trunc.f64(double %0)
                ret double %2
            }
            attributes #0 = { alwaysinline }
         """, "entry"), Float64, Tuple{Float64}, x+1)-1
    a + b
end
@test doubly_declared2_trunc(4.2) ≈ 8.

# Test for proper mangling of external (C) functions
function call_jl_errno()
    llvmcall(
        ("""declare i32 @jl_errno()
            define i32 @entry() #0 {
            0:
                %r = call i32 @jl_errno()
                ret i32 %r
            }
            attributes #0 = { alwaysinline }
         """, "entry"),Int32,Tuple{})
end
call_jl_errno()

module ObjLoadTest
    using Base: llvmcall, @ccallable
    using Test
    didcall = false
    """    jl_the_callback()

    Sets the global didcall when it did the call
    """
    @ccallable Cvoid function jl_the_callback()
        global didcall
        didcall = true
        nothing
    end
    @test_throws(ErrorException("@ccallable was already defined for this method name"),
                 @eval @ccallable Cvoid jl_the_callback(not_the_method::Int) = "other")
    # Make sure everything up until here gets compiled
    @test jl_the_callback() === nothing
    @test jl_the_callback(1) == "other"
    didcall = false
    function do_the_call()
        llvmcall(
            ("""declare void @jl_the_callback()
                define void @entry() #0 {
                0:
                    call void @jl_the_callback()
                    ret void
                }
                attributes #0 = { alwaysinline }
            """, "entry"),Cvoid,Tuple{})
    end
    do_the_call()
    @test didcall
end

# Test for proper parenting
local foo
function foo()
    # this IR snippet triggers an optimization relying
    # on the llvmcall function having a parent module
    Base.llvmcall(
     """%1 = getelementptr i64, i64* null, i64 1
        ret void""",
    Cvoid, Tuple{})
end
code_llvm(devnull, foo, ())

module CcallableRetTypeTest
    using Base: llvmcall, @ccallable
    using Test
    @ccallable function jl_test_returns_float()::Float64
        return 42
    end
    function do_the_call()
        llvmcall(
            ("""declare double @jl_test_returns_float()
                define double @entry() #0 {
                0:
                    %1 = call double @jl_test_returns_float()
                    ret double %1
                }
                attributes #0 = { alwaysinline }
            """, "entry"),Float64,Tuple{})
    end
    @test do_the_call() === 42.0
end

# Issue #48093 - test that non-external globals are not deduplicated
function kernel()
    Base.llvmcall(("""
        @shmem = internal global i8 0, align 8
        define void @entry() {
            store i8 1, i8* @shmem
            ret void
        }""", "entry"), Cvoid, Tuple{})
    Base.llvmcall(("""
        @shmem = internal global i8 0, align 8
        define i8 @entry() {
            %1 = load i8, i8* @shmem
            ret i8 %1
        }""", "entry"), UInt8, Tuple{})
end
@test kernel() == 0x00

# If this test breaks, you've probably broken Cxx.jl - please check
module LLVMCallFunctionTest
    using Base: llvmcall
    using Test

    const libllvmcalltest = "libllvmcalltest"
    const the_ir = unsafe_string(ccall((:MakeIdentityFunction, libllvmcalltest), Cstring, (Any,), Any))

    @eval really_complicated_identity(x) = llvmcall(($(the_ir), "identity"), Any, Tuple{Any}, x)

    mutable struct boxed_struct
    end
    let x = boxed_struct()
        @test really_complicated_identity(x) === x
    end

    # Define two functions that each compute the address of a dedicated internal global variable.
    # The names of these globals are the same, so if their linkages are overwritten, then the
    # linker will merge the globals. Consequently, we can test that linkage is preserved by testing
    # that the addresses of the globals differ. The next few lines of code do just that.
    const the_other_ir1 = unsafe_string(ccall((:MakeLoadGlobalFunction, libllvmcalltest), Cstring, ()))
    const the_other_ir2 = unsafe_string(ccall((:MakeLoadGlobalFunction, libllvmcalltest), Cstring, ()))

    @eval global_value_address1() = llvmcall(($(the_other_ir1), "load_global_var"), Int64, Tuple{})
    @eval global_value_address2() = llvmcall(($(the_other_ir2), "load_global_var"), Int64, Tuple{})

    @test global_value_address1() != global_value_address2()
end

# issue 34166
f34166(x) = Base.llvmcall("ret i$(Sys.WORD_SIZE) %0", Int, (Int,), x)
@test_throws ErrorException f34166(1)

# Test that codegen can construct constant LLVMPtr #38864
struct MyStruct
    kern::UInt64
    ptr::Core.LLVMPtr{UInt8,1}
end
MyStruct(kern) = MyStruct(kern, reinterpret(Core.LLVMPtr{UInt8,1}, 0))
MyStruct() = MyStruct(0)
s = MyStruct()

# ensure LLVMPtr properly subtypes
@test eltype(supertype(Core.LLVMPtr{UInt8,1})) <: UInt8
@test s.kern == 0
@test reinterpret(Int, s.ptr) == 0
