Skip to content

Commit 1da287a

Browse files
authored
Add handling for variadic functions on arm64 (#50)
osx-arm64 handles variadic functions differently than x86. cffi doesn't appear to handle this, so a bit of a hack is used to trick cffi into providing varargs in the expected manner for arm64.
1 parent d911f20 commit 1da287a

File tree

7 files changed

+3544
-27
lines changed

7 files changed

+3544
-27
lines changed

suitesparse_graphblas/__init__.py

+34-4
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,40 @@
33
from . import utils
44
from ._graphblas import ffi, lib # noqa
55

6+
import struct
7+
import platform
8+
9+
_is_osx_arm64 = platform.machine() == "arm64"
10+
_c_float = ffi.typeof("float")
11+
_c_double = ffi.typeof("double")
12+
13+
14+
if _is_osx_arm64:
15+
16+
def vararg(val):
17+
# Interpret float as int32 and double as int64
18+
# https://devblogs.microsoft.com/oldnewthing/20220823-00/?p=107041
19+
tov = ffi.typeof(val)
20+
if tov == _c_float:
21+
val = struct.unpack("l", struct.pack("f", val))[0]
22+
val = ffi.cast("int64_t", val)
23+
elif tov == _c_double:
24+
val = struct.unpack("q", struct.pack("d", val))[0]
25+
val = ffi.cast("int64_t", val)
26+
# Cast variadic argument as char * to force it onto the stack where ARM64 expects it
27+
# https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms
28+
return ffi.cast("char *", val)
29+
30+
else:
31+
32+
def vararg(val):
33+
return val
34+
635

736
def is_initialized():
837
"""Is GraphBLAS initialized via GrB_init or GxB_init?"""
9-
return lib.GxB_Global_Option_get(lib.GxB_MODE, ffi.new("GrB_Mode*")) != lib.GrB_PANIC
38+
mode = ffi.new("GrB_Mode*")
39+
return lib.GxB_Global_Option_get(lib.GxB_MODE, vararg(mode)) != lib.GrB_PANIC
1040

1141

1242
def supports_complex():
@@ -214,7 +244,7 @@ def __init__(self):
214244
def is_enabled(self):
215245
"""Is burble enabled?"""
216246
val_ptr = ffi.new("bool*")
217-
info = lib.GxB_Global_Option_get(lib.GxB_BURBLE, val_ptr)
247+
info = lib.GxB_Global_Option_get(lib.GxB_BURBLE, vararg(val_ptr))
218248
if info != lib.GrB_SUCCESS:
219249
raise _error_code_lookup[info](
220250
"Failed to get burble status (has GraphBLAS been initialized?"
@@ -223,15 +253,15 @@ def is_enabled(self):
223253

224254
def enable(self):
225255
"""Enable diagnostic output"""
226-
info = lib.GxB_Global_Option_set(lib.GxB_BURBLE, ffi.cast("int", 1))
256+
info = lib.GxB_Global_Option_set(lib.GxB_BURBLE, vararg(ffi.cast("int", 1)))
227257
if info != lib.GrB_SUCCESS:
228258
raise _error_code_lookup[info](
229259
"Failed to enable burble (has GraphBLAS been initialized?"
230260
)
231261

232262
def disable(self):
233263
"""Disable diagnostic output"""
234-
info = lib.GxB_Global_Option_set(lib.GxB_BURBLE, ffi.cast("int", 0))
264+
info = lib.GxB_Global_Option_set(lib.GxB_BURBLE, vararg(ffi.cast("int", 0)))
235265
if info != lib.GrB_SUCCESS:
236266
raise _error_code_lookup[info](
237267
"Failed to disable burble (has GraphBLAS been initialized?"

suitesparse_graphblas/build.py

+4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import os
22
import sys
3+
import platform
34

45
from cffi import FFI
56

67
is_win = sys.platform.startswith("win")
8+
is_arm64 = platform.machine() == "arm64"
79
thisdir = os.path.dirname(__file__)
810

911
ffibuilder = FFI()
@@ -28,6 +30,8 @@
2830
header = "suitesparse_graphblas.h"
2931
if is_win:
3032
header = "suitesparse_graphblas_no_complex.h"
33+
if is_arm64:
34+
header = "suitesparse_graphblas_arm64.h"
3135
gb_cdef = open(os.path.join(thisdir, header))
3236

3337
ffibuilder.cdef(gb_cdef.read())

suitesparse_graphblas/create_headers.py

+13-3
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,7 @@ def main():
796796

797797
# final files used by cffi (with and without complex numbers)
798798
final_h = os.path.join(thisdir, "suitesparse_graphblas.h")
799+
final_arm64_h = os.path.join(thisdir, "suitesparse_graphblas_arm64.h")
799800
final_no_complex_h = os.path.join(thisdir, "suitesparse_graphblas_no_complex.h")
800801
source_c = os.path.join(thisdir, "source.c")
801802

@@ -823,21 +824,30 @@ def main():
823824
with open(final_h, "w") as f:
824825
f.write("\n".join(text))
825826

827+
# Create final header file (arm64)
828+
# Replace all variadic arguments (...) with "char *"
829+
print(f"Step 4: parse header file to create {final_arm64_h}")
830+
orig_text = text
831+
patt = re.compile(r"^(extern GrB_Info .*\(.*)(\.\.\.)(\);)$")
832+
text = [patt.sub(r"\1char *\3", line) for line in orig_text]
833+
with open(final_arm64_h, "w") as f:
834+
f.write("\n".join(text))
835+
826836
# Create final header file (no complex)
827-
print(f"Step 4: parse header file to create {final_no_complex_h}")
837+
print(f"Step 5: parse header file to create {final_no_complex_h}")
828838
groups_no_complex = parse_header(processed_h, skip_complex=True)
829839
text = create_header_text(groups_no_complex)
830840
with open(final_no_complex_h, "w") as f:
831841
f.write("\n".join(text))
832842

833843
# Create source
834-
print(f"Step 5: create {source_c}")
844+
print(f"Step 6: create {source_c}")
835845
text = create_source_text(groups)
836846
with open(source_c, "w") as f:
837847
f.write("\n".join(text))
838848

839849
# Check defines
840-
print("Step 6: check #define definitions")
850+
print("Step 7: check #define definitions")
841851
with open(graphblas_h) as f:
842852
text = f.read()
843853
define_lines = re.compile(r".*?#define\s+\w+\s+")

suitesparse_graphblas/io/binary.py

-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ def binwrite(A, filename, comments=None, opener=Path.open):
124124

125125
typecode = ffinew("int32_t*")
126126
matrix_type = ffi.new("GrB_Type*")
127-
sparsity_status = ffinew("int32_t*")
128127

129128
nrows[0] = matrix.nrows(A)
130129
ncols[0] = matrix.ncols(A)

suitesparse_graphblas/io/serialize.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import numpy as np
22

3-
from suitesparse_graphblas import check_status, ffi, lib
3+
from suitesparse_graphblas import check_status, ffi, lib, vararg
44
from suitesparse_graphblas.utils import claim_buffer
55

66

@@ -20,15 +20,19 @@ def get_serialize_desc(compression=lib.GxB_COMPRESSION_DEFAULT, level=None, nthr
2020
check_status(desc, lib.GrB_Descriptor_new(desc))
2121
desc = ffi.gc(desc, free_desc)
2222
if nthreads is not None:
23-
check_status(desc, lib.GxB_Desc_set(desc[0], lib.GxB_NTHREADS, ffi.cast("int", nthreads)))
23+
check_status(
24+
desc,
25+
lib.GxB_Desc_set(desc[0], lib.GxB_NTHREADS, vararg(ffi.cast("int", nthreads))),
26+
)
2427
if compression is not None:
2528
if level is not None and compression in {
2629
lib.GxB_COMPRESSION_LZ4HC,
2730
lib.GxB_COMPRESSION_ZSTD,
2831
}:
2932
compression += level
3033
check_status(
31-
desc, lib.GxB_Desc_set(desc[0], lib.GxB_COMPRESSION, ffi.cast("int", compression))
34+
desc,
35+
lib.GxB_Desc_set(desc[0], lib.GxB_COMPRESSION, vararg(ffi.cast("int", compression))),
3236
)
3337
return desc
3438

suitesparse_graphblas/matrix.py

+18-16
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from suitesparse_graphblas import check_status, ffi, lib
1+
from suitesparse_graphblas import check_status, ffi, lib, vararg
22

33
from .io.serialize import deserialize_matrix as deserialize # noqa
44
from .io.serialize import serialize_matrix as serialize # noqa
@@ -109,7 +109,7 @@ def format(A):
109109
110110
"""
111111
format = ffi.new("GxB_Format_Value*")
112-
check_status(A, lib.GxB_Matrix_Option_get(A[0], lib.GxB_FORMAT, format))
112+
check_status(A, lib.GxB_Matrix_Option_get(A[0], lib.GxB_FORMAT, vararg(format)))
113113
return format[0]
114114

115115

@@ -122,58 +122,60 @@ def set_format(A, format):
122122
True
123123
124124
"""
125-
check_status(
126-
A, lib.GxB_Matrix_Option_set(A[0], lib.GxB_FORMAT, ffi.cast("GxB_Format_Value", format))
127-
)
125+
format_val = ffi.cast("GxB_Format_Value", format)
126+
check_status(A, lib.GxB_Matrix_Option_set(A[0], lib.GxB_FORMAT, vararg(format_val)))
128127

129128

130129
def sparsity_status(A):
131130
"""Get the sparsity status of the matrix."""
132131
sparsity_status = ffi.new("int32_t*")
133-
check_status(A, lib.GxB_Matrix_Option_get(A[0], lib.GxB_SPARSITY_STATUS, sparsity_status))
132+
check_status(
133+
A, lib.GxB_Matrix_Option_get(A[0], lib.GxB_SPARSITY_STATUS, vararg(sparsity_status))
134+
)
134135
return sparsity_status[0]
135136

136137

137138
def sparsity_control(A):
138139
"""Get the sparsity control of the matrix."""
139140
sparsity_control = ffi.new("int32_t*")
140-
check_status(A, lib.GxB_Matrix_Option_get(A[0], lib.GxB_SPARSITY_CONTROL, sparsity_control))
141+
check_status(
142+
A, lib.GxB_Matrix_Option_get(A[0], lib.GxB_SPARSITY_CONTROL, vararg(sparsity_control))
143+
)
141144
return sparsity_control[0]
142145

143146

144147
def set_sparsity_control(A, sparsity):
145148
"""Set the sparsity control of the matrix."""
149+
sparsity_control = ffi.cast("int32_t", sparsity)
146150
check_status(
147-
A, lib.GxB_Matrix_Option_set(A[0], lib.GxB_SPARSITY_CONTROL, ffi.cast("int", sparsity))
151+
A, lib.GxB_Matrix_Option_set(A[0], lib.GxB_SPARSITY_CONTROL, vararg(sparsity_control))
148152
)
149153

150154

151155
def hyper_switch(A):
152156
"""Get the hyper switch of the matrix."""
153157
hyper_switch = ffi.new("double*")
154-
check_status(A, lib.GxB_Matrix_Option_get(A[0], lib.GxB_HYPER_SWITCH, hyper_switch))
158+
check_status(A, lib.GxB_Matrix_Option_get(A[0], lib.GxB_HYPER_SWITCH, vararg(hyper_switch)))
155159
return hyper_switch[0]
156160

157161

158162
def set_hyper_switch(A, hyper_switch):
159163
"""Set the hyper switch of the matrix."""
160-
check_status(
161-
A, lib.GxB_Matrix_Option_set(A[0], lib.GxB_HYPER_SWITCH, ffi.cast("double", hyper_switch))
162-
)
164+
hyper_switch = ffi.cast("double", hyper_switch)
165+
check_status(A, lib.GxB_Matrix_Option_set(A[0], lib.GxB_HYPER_SWITCH, vararg(hyper_switch)))
163166

164167

165168
def bitmap_switch(A):
166169
"""Get the bitmap switch of the matrix."""
167170
bitmap_switch = ffi.new("double*")
168-
check_status(A, lib.GxB_Matrix_Option_get(A[0], lib.GxB_BITMAP_SWITCH, bitmap_switch))
171+
check_status(A, lib.GxB_Matrix_Option_get(A[0], lib.GxB_BITMAP_SWITCH, vararg(bitmap_switch)))
169172
return bitmap_switch[0]
170173

171174

172175
def set_bitmap_switch(A, bitmap_switch):
173176
"""Set the bitmap switch of the matrix."""
174-
check_status(
175-
A, lib.GxB_Matrix_Option_set(A[0], lib.GxB_BITMAP_SWITCH, ffi.cast("double", bitmap_switch))
176-
)
177+
bitmap_switch = ffi.cast("double", bitmap_switch)
178+
check_status(A, lib.GxB_Matrix_Option_set(A[0], lib.GxB_BITMAP_SWITCH, vararg(bitmap_switch)))
177179

178180

179181
def set_bool(A, value, i, j):

0 commit comments

Comments
 (0)