Skip to content

Commit c5c0354

Browse files
authored
Merge pull request #185 from maleadt/tb/ptr_interop
Add Julia pointer intrinsics.
2 parents ef24b88 + 3202571 commit c5c0354

File tree

3 files changed

+235
-0
lines changed

3 files changed

+235
-0
lines changed

src/interop.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,8 @@ const jlctx = Ref{LLVM.Context}()
99
include("interop/base.jl")
1010
include("interop/asmcall.jl")
1111
include("interop/passes.jl")
12+
if VERSION >= v"1.5-"
13+
include("interop/pointer.jl")
14+
end
1215

1316
end

src/interop/pointer.jl

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# pointer intrinsics
2+
3+
# TODO: can we use constant propagation instead of passing the alignment as a Val?
4+
5+
using Core: LLVMPtr
6+
7+
function tbaa_make_child(name::String, constant::Bool=false; ctx::LLVM.Context=JuliaContext())
8+
tbaa_root = MDNode([MDString("custom_tbaa", ctx)], ctx)
9+
tbaa_struct_type =
10+
MDNode([MDString("custom_tbaa_$name", ctx),
11+
tbaa_root,
12+
LLVM.ConstantInt(0, ctx)], ctx)
13+
tbaa_access_tag =
14+
MDNode([tbaa_struct_type,
15+
tbaa_struct_type,
16+
LLVM.ConstantInt(0, ctx),
17+
LLVM.ConstantInt(constant ? 1 : 0, ctx)], ctx)
18+
19+
return tbaa_access_tag
20+
end
21+
22+
tbaa_addrspace(as) = tbaa_make_child("addrspace($(as))")
23+
24+
@generated function pointerref(ptr::LLVMPtr{T,A}, i::Int, ::Val{align}) where {T,A,align}
25+
sizeof(T) == 0 && return T.instance
26+
eltyp = convert(LLVMType, T)
27+
28+
T_int = convert(LLVMType, Int)
29+
T_ptr = convert(LLVMType, ptr)
30+
31+
T_typed_ptr = LLVM.PointerType(eltyp, A)
32+
33+
# create a function
34+
param_types = [T_ptr, T_int]
35+
llvm_f, _ = create_function(eltyp, param_types)
36+
37+
# generate IR
38+
Builder(JuliaContext()) do builder
39+
entry = BasicBlock(llvm_f, "entry", JuliaContext())
40+
position!(builder, entry)
41+
42+
typed_ptr = bitcast!(builder, parameters(llvm_f)[1], T_typed_ptr)
43+
typed_ptr = gep!(builder, typed_ptr, [parameters(llvm_f)[2]])
44+
ld = load!(builder, typed_ptr)
45+
46+
if A != 0
47+
metadata(ld)[LLVM.MD_tbaa] = tbaa_addrspace(A)
48+
end
49+
alignment!(ld, align)
50+
51+
ret!(builder, ld)
52+
end
53+
54+
call_function(llvm_f, T, Tuple{LLVMPtr{T,A}, Int}, :((ptr, Int(i-one(i)))))
55+
end
56+
57+
@generated function pointerset(ptr::LLVMPtr{T,A}, x::T, i::Int, ::Val{align}) where {T,A,align}
58+
sizeof(T) == 0 && return
59+
eltyp = convert(LLVMType, T)
60+
61+
T_int = convert(LLVMType, Int)
62+
T_ptr = convert(LLVMType, ptr)
63+
64+
T_typed_ptr = LLVM.PointerType(eltyp, A)
65+
66+
# create a function
67+
param_types = [T_ptr, eltyp, T_int]
68+
llvm_f, _ = create_function(LLVM.VoidType(JuliaContext()), param_types)
69+
70+
# generate IR
71+
Builder(JuliaContext()) do builder
72+
entry = BasicBlock(llvm_f, "entry", JuliaContext())
73+
position!(builder, entry)
74+
75+
typed_ptr = bitcast!(builder, parameters(llvm_f)[1], T_typed_ptr)
76+
typed_ptr = gep!(builder, typed_ptr, [parameters(llvm_f)[3]])
77+
val = parameters(llvm_f)[2]
78+
st = store!(builder, val, typed_ptr)
79+
80+
if A != 0
81+
metadata(st)[LLVM.MD_tbaa] = tbaa_addrspace(A)
82+
end
83+
alignment!(st, align)
84+
85+
ret!(builder)
86+
end
87+
88+
call_function(llvm_f, Cvoid, Tuple{LLVMPtr{T,A}, T, Int},
89+
:((ptr, convert(T,x), Int(i-one(i)))))
90+
end
91+
92+
Base.unsafe_load(ptr::Core.LLVMPtr, i::Integer=1, align::Val=Val(1)) =
93+
pointerref(ptr, Int(i), align)
94+
95+
Base.unsafe_store!(ptr::Core.LLVMPtr{T}, x, i::Integer=1, align::Val=Val(1)) where {T} =
96+
pointerset(ptr, convert(T, x), Int(i), align)
97+
98+
99+
# pointer operations
100+
101+
# NOTE: this is type-pirating; move functionality upstream
102+
103+
LLVMPtr{T,A}(x::Union{Int,UInt,Ptr}) where {T,A} = reinterpret(LLVMPtr{T,A}, x)
104+
LLVMPtr{T,A}() where {T,A} = LLVMPtr{T,A}(0)
105+
106+
# conversions from and to integers
107+
Base.UInt(x::LLVMPtr) = reinterpret(UInt, x)
108+
Base.Int(x::LLVMPtr) = reinterpret(Int, x)
109+
Base.convert(::Type{LLVMPtr{T,A}}, x::Union{Int,UInt}) where {T,A} =
110+
reinterpret(LLVMPtr{T,A}, x)
111+
112+
Base.isequal(x::LLVMPtr, y::LLVMPtr) = (x === y)
113+
Base.isless(x::LLVMPtr{T,A}, y::LLVMPtr{T,A}) where {T,A} = x < y
114+
115+
Base.:(==)(x::LLVMPtr{<:Any,A}, y::LLVMPtr{<:Any,A}) where {A} = UInt(x) == UInt(y)
116+
Base.:(<)(x::LLVMPtr{<:Any,A}, y::LLVMPtr{<:Any,A}) where {A} = UInt(x) < UInt(y)
117+
Base.:(==)(x::LLVMPtr, y::LLVMPtr) = false
118+
119+
Base.:(-)(x::LLVMPtr{<:Any,A}, y::LLVMPtr{<:Any,A}) where {A} = UInt(x) - UInt(y)
120+
121+
Base.:(+)(x::LLVMPtr, y::Integer) = oftype(x, Base.add_ptr(UInt(x), (y % UInt) % UInt))
122+
Base.:(-)(x::LLVMPtr, y::Integer) = oftype(x, Base.sub_ptr(UInt(x), (y % UInt) % UInt))
123+
Base.:(+)(x::Integer, y::LLVMPtr) = y + x
124+
125+
Base.unsigned(x::LLVMPtr) = UInt(x)
126+
Base.signed(x::LLVMPtr) = Int(x)

test/interop.jl

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using LLVM.Interop
2+
using InteractiveUtils
23

34
@testset "interop" begin
45

@@ -126,4 +127,109 @@ end
126127
end
127128
end
128129

130+
VERSION >= v"1.5-" && @testset "pointer" begin
131+
132+
using Core: LLVMPtr
133+
134+
@testset "pointer operations" begin
135+
a = LLVMPtr{Int,0}(0)
136+
b = LLVMPtr{Int,0}(1)
137+
c = LLVMPtr{UInt,0}(0)
138+
d = LLVMPtr{UInt,0}(1)
139+
e = LLVMPtr{Int,1}(0)
140+
f = LLVMPtr{Int,1}(1)
141+
142+
@test UInt(a) == 0
143+
@test UInt(f) == 1
144+
145+
@test isequal(a,a)
146+
@test !isequal(a,b)
147+
@test !isequal(a,c)
148+
@test !isequal(a,d)
149+
@test !isequal(a,e)
150+
@test !isequal(a,f)
151+
152+
@test a == a
153+
@test a != b
154+
@test a == c
155+
@test a != d
156+
@test a != e
157+
@test a != f
158+
159+
@test a < b
160+
@test a < d
161+
@test_throws MethodError a < f
162+
163+
@test b - a == 1
164+
@test d - a == 1
165+
@test_throws MethodError f - a
166+
167+
@test a + 1 == b
168+
@test c + 1 == d
169+
@test e + 1 == f
170+
171+
@test b - 1 == a
172+
@test d - 1 == c
173+
@test f - 1 == e
174+
end
175+
176+
@testset "unsafe_load" begin
177+
a = Int64[1]
178+
ptr = reinterpret(Core.LLVMPtr{Int64,0}, pointer(a))
179+
@test unsafe_load(ptr) == 1
180+
unsafe_store!(ptr, 2)
181+
@test unsafe_load(ptr) == 2
182+
183+
ir = sprint(io->code_llvm(io, unsafe_load, Tuple{typeof(ptr)}))
184+
if VERSION >= v"1.6-" && Sys.iswindows() && Sys.WORD_SIZE == 32
185+
# FIXME: Win32 nightly emits a i64*, even though bitstype_to_llvm uses T_int8
186+
@test_broken contains(ir, r"@julia_unsafe_load_\d+\(i8\*\)")
187+
else
188+
@test contains(ir, r"@julia_unsafe_load_\d+\(i8\*\)")
189+
end
190+
@test contains(ir, r"load i64, i64\* %\d+, align 1")
191+
192+
ir = sprint(io->code_llvm(io, unsafe_load, Tuple{typeof(ptr), Int, Val{4}}))
193+
@test contains(ir, r"load i64, i64\* %\d+, align 4")
194+
end
195+
196+
@testset "reinterpret(Nothing, nothing)" begin
197+
ptr = reinterpret(Core.LLVMPtr{Nothing,0}, C_NULL)
198+
@test unsafe_load(ptr) === nothing
199+
end
200+
201+
@testset "TBAA" begin
202+
load(ptr) = unsafe_load(ptr)
203+
store(ptr) = unsafe_store!(ptr, 0)
204+
205+
for f in (load, store)
206+
ir = sprint(io->code_llvm(io, f,
207+
Tuple{Core.LLVMPtr{Float32,1}};
208+
dump_module=true, raw=true))
209+
@test occursin("custom_tbaa_addrspace(1)", ir)
210+
211+
# no TBAA on generic pointers
212+
ir = sprint(io->code_llvm(io, f,
213+
Tuple{Core.LLVMPtr{Float32,0}};
214+
dump_module=true, raw=true))
215+
@test !occursin("custom_tbaa", ir)
216+
end
217+
end
218+
219+
@testset "ghost values" begin
220+
@eval struct Singleton end
221+
222+
ir = sprint(io->code_llvm(io, unsafe_load,
223+
Tuple{Core.LLVMPtr{Singleton,0}}))
224+
@test occursin("ret void", ir)
225+
@test unsafe_load(reinterpret(Core.LLVMPtr{Singleton,0}, C_NULL)) === Singleton()
226+
227+
ir = sprint(io->code_llvm(io, unsafe_store!,
228+
Tuple{Core.LLVMPtr{Singleton,0},
229+
Singleton}))
230+
@test !occursin("\bstore\b", ir)
231+
end
232+
233+
end
234+
129235
end

0 commit comments

Comments
 (0)