Skip to content

Commit 029bcc7

Browse files
committed
Start implementing a direct pocl backend
1 parent 8a87f77 commit 029bcc7

File tree

3 files changed

+310
-10
lines changed

3 files changed

+310
-10
lines changed

Project.toml

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,23 @@ EnzymeCore = "f151be2c-9106-41f4-ab19-57ee4f262869"
1010
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
1111
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1212
MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
13+
OpenCL_jll = "6cb37087-e8b6-5417-8430-1f242f1e46e4"
1314
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
1415
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
1516
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
1617
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
1718
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
19+
pocl_jll = "627d6b7a-bbe6-5189-83e7-98cc0a5aeadd"
20+
21+
[weakdeps]
22+
EnzymeCore = "f151be2c-9106-41f4-ab19-57ee4f262869"
23+
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
24+
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
25+
26+
[extensions]
27+
EnzymeExt = "EnzymeCore"
28+
LinearAlgebraExt = "LinearAlgebra"
29+
SparseArraysExt = "SparseArrays"
1830

1931
[compat]
2032
Adapt = "0.4, 1.0, 2.0, 3.0, 4"
@@ -30,17 +42,7 @@ StaticArrays = "0.12, 1.0"
3042
UUIDs = "<0.0.1, 1.6"
3143
julia = "1.6"
3244

33-
[extensions]
34-
EnzymeExt = "EnzymeCore"
35-
LinearAlgebraExt = "LinearAlgebra"
36-
SparseArraysExt = "SparseArrays"
37-
3845
[extras]
3946
EnzymeCore = "f151be2c-9106-41f4-ab19-57ee4f262869"
4047
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
4148
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
42-
43-
[weakdeps]
44-
EnzymeCore = "f151be2c-9106-41f4-ab19-57ee4f262869"
45-
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
46-
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

src/nanoOpenCL.jl

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
module nanoOpenCL
2+
3+
import OpenCL_jll
4+
import pocl_jll
5+
6+
const libopencl = OpenCL_jll.libopencl # TODO directly use POCL
7+
8+
"""
9+
@checked function foo(...)
10+
rv = ...
11+
return rv
12+
end
13+
14+
Macro for wrapping a function definition returning a status code. Two versions of the
15+
function will be generated: `foo`, with the function body wrapped by an invocation of the
16+
`check` function (to be implemented by the caller of this macro), and `unchecked_foo` where no
17+
such invocation is present and the status code is returned to the caller.
18+
"""
19+
macro checked(ex)
20+
# parse the function definition
21+
@assert Meta.isexpr(ex, :function)
22+
sig = ex.args[1]
23+
@assert Meta.isexpr(sig, :call)
24+
body = ex.args[2]
25+
@assert Meta.isexpr(body, :block)
26+
27+
# we need to detect the first API call, so add an initialization check
28+
body = quote
29+
if !initialized[]
30+
initialize()
31+
end
32+
$body
33+
end
34+
35+
# generate a "safe" version that performs a check
36+
safe_body = quote
37+
check() do
38+
$body
39+
end
40+
end
41+
safe_sig = Expr(:call, sig.args[1], sig.args[2:end]...)
42+
safe_def = Expr(:function, safe_sig, safe_body)
43+
44+
# generate a "unchecked" version that returns the error code instead
45+
unchecked_sig = Expr(:call, Symbol("unchecked_", sig.args[1]), sig.args[2:end]...)
46+
unchecked_def = Expr(:function, unchecked_sig, body)
47+
48+
return esc(:($safe_def, $unchecked_def))
49+
end
50+
51+
const CL_SUCCESS = 0
52+
53+
const CL_DEVICE_NOT_FOUND = -1
54+
55+
const CL_DEVICE_NOT_AVAILABLE = -2
56+
57+
const CL_PLATFORM_NOT_FOUND_KHR = -1001
58+
59+
const CL_PLATFORM_PROFILE = 0x0900
60+
61+
const CL_PLATFORM_VERSION = 0x0901
62+
63+
const CL_PLATFORM_NAME = 0x0902
64+
65+
const CL_PLATFORM_VENDOR = 0x0903
66+
67+
const CL_PLATFORM_EXTENSIONS = 0x0904
68+
69+
const CL_PLATFORM_HOST_TIMER_RESOLUTION = 0x0905
70+
71+
const CL_PLATFORM_NUMERIC_VERSION = 0x0906
72+
73+
const CL_PLATFORM_EXTENSIONS_WITH_VERSION = 0x0907
74+
75+
const CL_DEVICE_TYPE_DEFAULT = 1 << 0
76+
77+
const CL_DEVICE_TYPE_CPU = 1 << 1
78+
79+
const CL_DEVICE_TYPE_GPU = 1 << 2
80+
81+
const CL_DEVICE_TYPE_ACCELERATOR = 1 << 3
82+
83+
const CL_DEVICE_TYPE_CUSTOM = 1 << 4
84+
85+
const CL_DEVICE_TYPE_ALL = 0xffffffff
86+
87+
const CL_DEVICE_TYPE = 0x1000
88+
89+
90+
function check(f)
91+
res = f()
92+
93+
if res != CL_SUCCESS
94+
throw_api_error(res)
95+
end
96+
97+
return
98+
end
99+
100+
const cl_int = Int32
101+
102+
const cl_uint = UInt32
103+
104+
const cl_ulong = UInt64
105+
106+
mutable struct _cl_platform_id end
107+
108+
mutable struct _cl_device_id end
109+
110+
const cl_platform_id = Ptr{_cl_platform_id}
111+
112+
const cl_device_id = Ptr{_cl_device_id}
113+
114+
const cl_bitfield = cl_ulong
115+
116+
const cl_device_type = cl_bitfield
117+
118+
const cl_platform_info = cl_uint
119+
120+
const cl_device_info = cl_uint
121+
122+
123+
124+
@checked function clGetPlatformIDs(num_entries, platforms, num_platforms)
125+
@ccall libopencl.clGetPlatformIDs(num_entries::cl_uint, platforms::Ptr{cl_platform_id},
126+
num_platforms::Ptr{cl_uint})::cl_int
127+
end
128+
129+
@checked function clGetPlatformInfo(platform, param_name, param_value_size, param_value,
130+
param_value_size_ret)
131+
@ccall libopencl.clGetPlatformInfo(platform::cl_platform_id,
132+
param_name::cl_platform_info,
133+
param_value_size::Csize_t, param_value::Ptr{Cvoid},
134+
param_value_size_ret::Ptr{Csize_t})::cl_int
135+
end
136+
137+
@checked function clGetDeviceIDs(platform, device_type, num_entries, devices, num_devices)
138+
@ccall libopencl.clGetDeviceIDs(platform::cl_platform_id, device_type::cl_device_type,
139+
num_entries::cl_uint, devices::Ptr{cl_device_id},
140+
num_devices::Ptr{cl_uint})::cl_int
141+
end
142+
143+
@checked function clGetDeviceInfo(device, param_name, param_value_size, param_value,
144+
param_value_size_ret)
145+
@ccall libopencl.clGetDeviceInfo(device::cl_device_id, param_name::cl_device_info,
146+
param_value_size::Csize_t, param_value::Ptr{Cvoid},
147+
param_value_size_ret::Ptr{Csize_t})::cl_int
148+
end
149+
150+
# Init
151+
152+
# lazy initialization
153+
const initialized = Ref{Bool}(false)
154+
@noinline function initialize()
155+
initialized[] = true
156+
157+
@static if Sys.iswindows()
158+
if is_high_integrity_level()
159+
@warn """Running at high integrity level, preventing OpenCL.jl from loading drivers from JLLs.
160+
161+
Only system drivers will be available. To enable JLL drivers, do not run Julia as an administrator."""
162+
end
163+
end
164+
165+
ocd_filenames = join(OpenCL_jll.drivers, ':')
166+
if haskey(ENV, "OCL_ICD_FILENAMES")
167+
ocd_filenames *= ":" * ENV["OCL_ICD_FILENAMES"]
168+
end
169+
170+
withenv("OCL_ICD_FILENAMES"=>ocd_filenames) do
171+
num_platforms = Ref{Cuint}()
172+
@ccall libopencl.clGetPlatformIDs(
173+
0::cl_uint, C_NULL::Ptr{cl_platform_id},
174+
num_platforms::Ptr{cl_uint})::cl_int
175+
176+
if num_platforms[] == 0 && isempty(OpenCL_jll.drivers)
177+
@error """No OpenCL drivers available, either system-wide or provided by a JLL.
178+
179+
Please install a system-wide OpenCL driver, or load one together with OpenCL.jl,
180+
e.g., by doing `using OpenCL, pocl_jll`."""
181+
end
182+
end
183+
end
184+
185+
# Julia API
186+
187+
struct Platform
188+
id::cl_platform_id
189+
end
190+
191+
Base.unsafe_convert(::Type{cl_platform_id}, p::Platform) = p.id
192+
193+
function platforms()
194+
nplatforms = Ref{Cuint}()
195+
res = unchecked_clGetPlatformIDs(0, C_NULL, nplatforms)
196+
if res == CL_PLATFORM_NOT_FOUND_KHR || nplatforms[] == 0
197+
return Platform[]
198+
elseif res != CL_SUCCESS
199+
throw(CLError(res))
200+
end
201+
cl_platform_ids = Vector{cl_platform_id}(undef, nplatforms[])
202+
clGetPlatformIDs(nplatforms[], cl_platform_ids, C_NULL)
203+
return [Platform(id) for id in cl_platform_ids]
204+
end
205+
206+
207+
function Base.getproperty(p::Platform, s::Symbol)
208+
# simple string properties
209+
version_re = r"OpenCL (?<major>\d+)\.(?<minor>\d+)(?<vendor>.+)"
210+
@inline function get_string(prop)
211+
sz = Ref{Csize_t}()
212+
clGetPlatformInfo(p, prop, 0, C_NULL, sz)
213+
chars = Vector{Cchar}(undef, sz[])
214+
clGetPlatformInfo(p, prop, sz[], chars, C_NULL)
215+
return GC.@preserve chars unsafe_string(pointer(chars))
216+
end
217+
if s === :profile
218+
return get_string(CL_PLATFORM_PROFILE)
219+
elseif s === :version
220+
str = get_string(CL_PLATFORM_VERSION)
221+
m = match(version_re, str)
222+
if m === nothing
223+
error("Could not parse OpenCL version string: $str")
224+
end
225+
return strip(m["vendor"])
226+
elseif s === :opencl_version
227+
str = get_string(CL_PLATFORM_VERSION)
228+
m = match(version_re, str)
229+
if m === nothing
230+
error("Could not parse OpenCL version string: $str")
231+
end
232+
return VersionNumber(parse(Int, m["major"]), parse(Int, m["minor"]))
233+
elseif s === :name
234+
return get_string(CL_PLATFORM_NAME)
235+
elseif s === :vendor
236+
return get_string(CL_PLATFORM_VENDOR)
237+
end
238+
239+
if s == :extensions
240+
size = Ref{Csize_t}()
241+
clGetPlatformInfo(p, CL_PLATFORM_EXTENSIONS, 0, C_NULL, size)
242+
result = Vector{Cchar}(undef, size[])
243+
clGetPlatformInfo(p, CL_PLATFORM_EXTENSIONS, size[], result, C_NULL)
244+
return GC.@preserve result split(unsafe_string(pointer(result)))
245+
end
246+
return getfield(p, s)
247+
end
248+
249+
struct Device
250+
id::cl_device_id
251+
end
252+
253+
Base.unsafe_convert(::Type{cl_device_id}, d::Device) = d.id
254+
255+
function devices(p::Platform, dtype)
256+
ndevices = Ref{Cuint}()
257+
ret = unchecked_clGetDeviceIDs(p, dtype, 0, C_NULL, ndevices)
258+
if ret == CL_DEVICE_NOT_FOUND || ndevices[] == 0
259+
return Device[]
260+
elseif ret != CL_SUCCESS
261+
throw(CLError(ret))
262+
end
263+
result = Vector{cl_device_id}(undef, ndevices[])
264+
clGetDeviceIDs(p, dtype, ndevices[], result, C_NULL)
265+
return Device[Device(id) for id in result]
266+
end
267+
268+
function default_device(p::Platform)
269+
devs = devices(p, CL_DEVICE_TYPE_DEFAULT)
270+
isempty(devs) && return nothing
271+
# XXX: clGetDeviceIDs documents CL_DEVICE_TYPE_DEFAULT should only return one device,
272+
# but it's been observed to return multiple devices on some platforms...
273+
return first(devs)
274+
end
275+
276+
devices(p::Platform) = devices(p, CL_DEVICE_TYPE_ALL)
277+
278+
end

src/pocl.jl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module POCL
2+
include("nanoOpenCL.jl")
3+
4+
import .nanoOpenCL: nanoOpenCL
5+
6+
function platform()
7+
for p in nanoOpenCL.platforms()
8+
if p.vendor == "The pocl project"
9+
return p
10+
end
11+
end
12+
return nothing
13+
end
14+
15+
function device()
16+
p = platform()
17+
return nanoOpenCL.default_device(p)
18+
end
19+
20+
end

0 commit comments

Comments
 (0)