diff --git a/lib/intrinsics/Project.toml b/lib/intrinsics/Project.toml index 4046a0d1..7ea186b2 100644 --- a/lib/intrinsics/Project.toml +++ b/lib/intrinsics/Project.toml @@ -1,7 +1,7 @@ name = "SPIRVIntrinsics" uuid = "71d1d633-e7e8-4a92-83a1-de8814b09ba8" authors = ["Tim Besard "] -version = "0.5.9" +version = "0.5.10" [deps] ExprTools = "e2ba6199-217a-4e67-a87a-7c52f15ade04" @@ -18,7 +18,7 @@ SPIRVIntrinsicsSIMDExt = "SIMD" [compat] ExprTools = "0.1" GPUToolbox = "0.2, 0.3, 1" -LLVM = "9.1" +LLVM = "10" SIMD = "3.6" SpecialFunctions = "1.3, 2" julia = "1.10" diff --git a/lib/intrinsics/src/SPIRVIntrinsics.jl b/lib/intrinsics/src/SPIRVIntrinsics.jl index b2bca59d..f6c7d6c8 100644 --- a/lib/intrinsics/src/SPIRVIntrinsics.jl +++ b/lib/intrinsics/src/SPIRVIntrinsics.jl @@ -1,6 +1,6 @@ module SPIRVIntrinsics -using LLVM, LLVM.Interop +using LLVM, LLVM.IR, LLVM.Build, LLVM.Interop using Core: LLVMPtr import ExprTools diff --git a/lib/intrinsics/src/memory.jl b/lib/intrinsics/src/memory.jl index da5c5b2b..7259b403 100644 --- a/lib/intrinsics/src/memory.jl +++ b/lib/intrinsics/src/memory.jl @@ -1,38 +1,22 @@ # local memory # get a pointer to local memory, with known (static) or zero length (dynamic) -@generated function emit_localmemory(::Type{T}, ::Val{len}=Val(0)) where {T,len} - Context() do ctx - # XXX: as long as LLVMPtr is emitted as i8*, it doesn't make sense to type the GV - eltyp = convert(LLVMType, LLVM.Int8Type()) - T_ptr = convert(LLVMType, LLVMPtr{T,AS.Workgroup}) - - # create a function - llvm_f, _ = create_function(T_ptr) - - # create the global variable - mod = LLVM.parent(llvm_f) - gv_typ = LLVM.ArrayType(eltyp, len * sizeof(T)) - gv = GlobalVariable(mod, gv_typ, "local_memory", AS.Workgroup) - if len > 0 - linkage!(gv, LLVM.API.LLVMInternalLinkage) - initializer!(gv, null(gv_typ)) - end - # TODO: Make the alignment configurable - alignment!(gv, Base.datatype_alignment(T)) - - # generate IR - IRBuilder() do builder - entry = BasicBlock(llvm_f, "entry") - position!(builder, entry) - - ptr = gep!(builder, gv_typ, gv, [ConstantInt(0), ConstantInt(0)]) - - untyped_ptr = bitcast!(builder, ptr, T_ptr) - - ret!(builder, untyped_ptr) - end - - call_function(llvm_f, LLVMPtr{T,AS.Workgroup}) +@llvmgenerated builder function emit_localmemory(::Type{T}, + ::Val{len}=Val(0))::LLVMPtr{T,AS.Workgroup} where {T,len} + # XXX: as long as LLVMPtr is emitted as i8*, it doesn't make sense to type the GV + eltyp = LLVM.Int8Type() + T_ptr = convert(LLVMType, LLVMPtr{T,AS.Workgroup}) + + # create the global variable + gv_typ = LLVM.ArrayType(eltyp, len * sizeof(T)) + gv = GlobalVariable(current_module(builder), gv_typ, "local_memory", AS.Workgroup) + if len > 0 + gv.linkage = LLVM.Linkage.Internal + gv.initializer = null(gv_typ) end + # TODO: Make the alignment configurable + gv.alignment = Base.datatype_alignment(T) + + ptr = gep!(builder, gv_typ, gv, [ConstantInt(0), ConstantInt(0)]) + bitcast!(builder, ptr, T_ptr) end diff --git a/lib/intrinsics/src/printf.jl b/lib/intrinsics/src/printf.jl index 203a966d..28aa8066 100644 --- a/lib/intrinsics/src/printf.jl +++ b/lib/intrinsics/src/printf.jl @@ -26,73 +26,61 @@ end @generated function emit_printf(::Val{fmt}, argspec...) where {fmt} arg_exprs = [:( argspec[$i] ) for i in 1:length(argspec)] - arg_types = [argspec...] - - Context() do ctx - T_void = LLVM.VoidType() - T_int32 = LLVM.Int32Type() - T_pint8 = LLVM.PointerType(LLVM.Int8Type(), AS.UniformConstant) - - # create functions - param_types = LLVMType[convert(LLVMType, typ) for typ in arg_types] - llvm_f, _ = create_function(T_int32, param_types) - mod = LLVM.parent(llvm_f) - - IRBuilder() do builder - entry = BasicBlock(llvm_f, "entry") - position!(builder, entry) - - # `printf` needs to be invoked very specifically, e.g., the format string needs - # to be a pointer to a string, and arguments need to match exactly what is - # expected by the format string, so we cannot rely on how the arguments to this - # function have been passed in (by `llvmcall`). - T_actual_args = LLVMType[] - actual_args = LLVM.Value[] - for (_, (arg, argtyp)) in enumerate(zip(parameters(llvm_f), arg_types)) - if argtyp <: LLVMPtr - # passed as i8* - T,AS = argtyp.parameters - actual_typ = LLVM.PointerType(convert(LLVMType, T), AS) - actual_arg = bitcast!(builder, arg, actual_typ) - elseif argtyp <: Ptr - T = eltype(argtyp) - if T === Nothing - T = Int8 - end - actual_typ = LLVM.PointerType(convert(LLVMType, T)) - actual_arg = if value_type(arg) isa LLVM.PointerType - # passed as i8* or ptr - bitcast!(builder, arg, actual_typ) - else - # passed as i64 - inttoptr!(builder, arg, actual_typ) - end - elseif argtyp <: Bool - # passed as i8 - T = eltype(argtyp) - actual_typ = LLVM.Int1Type() - actual_arg = trunc!(builder, arg, actual_typ) - else - actual_typ = convert(LLVMType, argtyp) - actual_arg = arg - end - push!(T_actual_args, actual_typ) - push!(actual_args, actual_arg) - end - - str = globalstring_ptr!(builder, String(fmt); addrspace=AS.UniformConstant) + arg_types = Tuple{argspec...} - # invoke printf and return - printf_typ = LLVM.FunctionType(T_int32, [T_pint8]; vararg=true) - printf = LLVM.Function(mod, "printf", printf_typ) - push!(function_attributes(printf), EnumAttribute("nobuiltin")) - chars = call!(builder, printf_typ, printf, [str, actual_args...]) + # pass the format string and the argument types as statically-known arguments, so that + # the IR generator is compiled once instead of for every format string + generate_llvmcall(printf_ir, Int32, Tuple{Val{fmt}, Type{arg_types}, argspec...}, + Val(fmt), arg_types, arg_exprs...) +end - ret!(builder, chars) +function printf_ir(builder, fmt::Val, arg_types::Type{<:Tuple}, args...) + @nospecialize + T_int32 = LLVM.Int32Type() + T_pint8 = LLVM.PointerType(LLVM.Int8Type(), AS.UniformConstant) + + # `printf` needs to be invoked very specifically, e.g., the format string needs + # to be a pointer to a string, and arguments need to match exactly what is + # expected by the format string, so we cannot rely on how the arguments to this + # function have been passed in (by `llvmcall`). + actual_args = LLVM.Value[] + for (arg, argtyp) in zip(args, fieldtypes(arg_types)) + if argtyp <: LLVMPtr + # passed as i8* + T,AS = argtyp.parameters + actual_typ = LLVM.PointerType(convert(LLVMType, T), AS) + actual_arg = bitcast!(builder, arg, actual_typ) + elseif argtyp <: Ptr + T = eltype(argtyp) + if T === Nothing + T = Int8 + end + actual_typ = LLVM.PointerType(convert(LLVMType, T)) + actual_arg = if arg.value_type isa LLVM.PointerType + # passed as i8* or ptr + bitcast!(builder, arg, actual_typ) + else + # passed as i64 + inttoptr!(builder, arg, actual_typ) + end + elseif argtyp <: Bool + # passed as i8 + actual_typ = LLVM.Int1Type() + actual_arg = trunc!(builder, arg, actual_typ) + else + actual_arg = arg end - - call_function(llvm_f, Int32, Tuple{arg_types...}, arg_exprs...) + push!(actual_args, actual_arg) end + + fmt_str = String(typeof(fmt).parameters[1]::Symbol) + str = globalstring_ptr!(builder, fmt_str; addrspace=AS.UniformConstant) + + # invoke printf and return + printf_typ = LLVM.FunctionType(T_int32, [T_pint8]; vararg=true) + printf = LLVM.Function(current_module(builder), "printf", printf_typ) + push!(printf.function_attributes, EnumAttribute(:nobuiltin)) + call!(builder, printf_typ, printf, [str, actual_args...]) end