Skip to content

Add handling for variadic functions on arm64 #50

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions suitesparse_graphblas/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,40 @@
from . import utils
from ._graphblas import ffi, lib # noqa

import struct
import platform

_is_osx_arm64 = platform.machine() == "arm64"
_c_float = ffi.typeof("float")
_c_double = ffi.typeof("double")


if _is_osx_arm64:

def vararg(val):
# Interpret float as int32 and double as int64
# https://devblogs.microsoft.com/oldnewthing/20220823-00/?p=107041
tov = ffi.typeof(val)
if tov == _c_float:
val = struct.unpack("l", struct.pack("f", val))[0]
val = ffi.cast("int64_t", val)
elif tov == _c_double:
val = struct.unpack("q", struct.pack("d", val))[0]
val = ffi.cast("int64_t", val)
# Cast variadic argument as char * to force it onto the stack where ARM64 expects it
# https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms
return ffi.cast("char *", val)

else:

def vararg(val):
return val


def is_initialized():
"""Is GraphBLAS initialized via GrB_init or GxB_init?"""
return lib.GxB_Global_Option_get(lib.GxB_MODE, ffi.new("GrB_Mode*")) != lib.GrB_PANIC
mode = ffi.new("GrB_Mode*")
return lib.GxB_Global_Option_get(lib.GxB_MODE, vararg(mode)) != lib.GrB_PANIC


def supports_complex():
Expand Down Expand Up @@ -214,7 +244,7 @@ def __init__(self):
def is_enabled(self):
"""Is burble enabled?"""
val_ptr = ffi.new("bool*")
info = lib.GxB_Global_Option_get(lib.GxB_BURBLE, val_ptr)
info = lib.GxB_Global_Option_get(lib.GxB_BURBLE, vararg(val_ptr))
if info != lib.GrB_SUCCESS:
raise _error_code_lookup[info](
"Failed to get burble status (has GraphBLAS been initialized?"
Expand All @@ -223,15 +253,15 @@ def is_enabled(self):

def enable(self):
"""Enable diagnostic output"""
info = lib.GxB_Global_Option_set(lib.GxB_BURBLE, ffi.cast("int", 1))
info = lib.GxB_Global_Option_set(lib.GxB_BURBLE, vararg(ffi.cast("int", 1)))
if info != lib.GrB_SUCCESS:
raise _error_code_lookup[info](
"Failed to enable burble (has GraphBLAS been initialized?"
)

def disable(self):
"""Disable diagnostic output"""
info = lib.GxB_Global_Option_set(lib.GxB_BURBLE, ffi.cast("int", 0))
info = lib.GxB_Global_Option_set(lib.GxB_BURBLE, vararg(ffi.cast("int", 0)))
if info != lib.GrB_SUCCESS:
raise _error_code_lookup[info](
"Failed to disable burble (has GraphBLAS been initialized?"
Expand Down
4 changes: 4 additions & 0 deletions suitesparse_graphblas/build.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import os
import sys
import platform

from cffi import FFI

is_win = sys.platform.startswith("win")
is_arm64 = platform.machine() == "arm64"
thisdir = os.path.dirname(__file__)

ffibuilder = FFI()
Expand All @@ -28,6 +30,8 @@
header = "suitesparse_graphblas.h"
if is_win:
header = "suitesparse_graphblas_no_complex.h"
if is_arm64:
header = "suitesparse_graphblas_arm64.h"
gb_cdef = open(os.path.join(thisdir, header))

ffibuilder.cdef(gb_cdef.read())
Expand Down
16 changes: 13 additions & 3 deletions suitesparse_graphblas/create_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,7 @@ def main():

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

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

# Create final header file (arm64)
# Replace all variadic arguments (...) with "char *"
print(f"Step 4: parse header file to create {final_arm64_h}")
orig_text = text
patt = re.compile(r"^(extern GrB_Info .*\(.*)(\.\.\.)(\);)$")
text = [patt.sub(r"\1char *\3", line) for line in orig_text]
with open(final_arm64_h, "w") as f:
f.write("\n".join(text))

# Create final header file (no complex)
print(f"Step 4: parse header file to create {final_no_complex_h}")
print(f"Step 5: parse header file to create {final_no_complex_h}")
groups_no_complex = parse_header(processed_h, skip_complex=True)
text = create_header_text(groups_no_complex)
with open(final_no_complex_h, "w") as f:
f.write("\n".join(text))

# Create source
print(f"Step 5: create {source_c}")
print(f"Step 6: create {source_c}")
text = create_source_text(groups)
with open(source_c, "w") as f:
f.write("\n".join(text))

# Check defines
print("Step 6: check #define definitions")
print("Step 7: check #define definitions")
with open(graphblas_h) as f:
text = f.read()
define_lines = re.compile(r".*?#define\s+\w+\s+")
Expand Down
1 change: 0 additions & 1 deletion suitesparse_graphblas/io/binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ def binwrite(A, filename, comments=None, opener=Path.open):

typecode = ffinew("int32_t*")
matrix_type = ffi.new("GrB_Type*")
sparsity_status = ffinew("int32_t*")

nrows[0] = matrix.nrows(A)
ncols[0] = matrix.ncols(A)
Expand Down
10 changes: 7 additions & 3 deletions suitesparse_graphblas/io/serialize.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import numpy as np

from suitesparse_graphblas import check_status, ffi, lib
from suitesparse_graphblas import check_status, ffi, lib, vararg
from suitesparse_graphblas.utils import claim_buffer


Expand All @@ -20,15 +20,19 @@ def get_serialize_desc(compression=lib.GxB_COMPRESSION_DEFAULT, level=None, nthr
check_status(desc, lib.GrB_Descriptor_new(desc))
desc = ffi.gc(desc, free_desc)
if nthreads is not None:
check_status(desc, lib.GxB_Desc_set(desc[0], lib.GxB_NTHREADS, ffi.cast("int", nthreads)))
check_status(
desc,
lib.GxB_Desc_set(desc[0], lib.GxB_NTHREADS, vararg(ffi.cast("int", nthreads))),
)
if compression is not None:
if level is not None and compression in {
lib.GxB_COMPRESSION_LZ4HC,
lib.GxB_COMPRESSION_ZSTD,
}:
compression += level
check_status(
desc, lib.GxB_Desc_set(desc[0], lib.GxB_COMPRESSION, ffi.cast("int", compression))
desc,
lib.GxB_Desc_set(desc[0], lib.GxB_COMPRESSION, vararg(ffi.cast("int", compression))),
)
return desc

Expand Down
34 changes: 18 additions & 16 deletions suitesparse_graphblas/matrix.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from suitesparse_graphblas import check_status, ffi, lib
from suitesparse_graphblas import check_status, ffi, lib, vararg

from .io.serialize import deserialize_matrix as deserialize # noqa
from .io.serialize import serialize_matrix as serialize # noqa
Expand Down Expand Up @@ -109,7 +109,7 @@ def format(A):

"""
format = ffi.new("GxB_Format_Value*")
check_status(A, lib.GxB_Matrix_Option_get(A[0], lib.GxB_FORMAT, format))
check_status(A, lib.GxB_Matrix_Option_get(A[0], lib.GxB_FORMAT, vararg(format)))
return format[0]


Expand All @@ -122,58 +122,60 @@ def set_format(A, format):
True

"""
check_status(
A, lib.GxB_Matrix_Option_set(A[0], lib.GxB_FORMAT, ffi.cast("GxB_Format_Value", format))
)
format_val = ffi.cast("GxB_Format_Value", format)
check_status(A, lib.GxB_Matrix_Option_set(A[0], lib.GxB_FORMAT, vararg(format_val)))


def sparsity_status(A):
"""Get the sparsity status of the matrix."""
sparsity_status = ffi.new("int32_t*")
check_status(A, lib.GxB_Matrix_Option_get(A[0], lib.GxB_SPARSITY_STATUS, sparsity_status))
check_status(
A, lib.GxB_Matrix_Option_get(A[0], lib.GxB_SPARSITY_STATUS, vararg(sparsity_status))
)
return sparsity_status[0]


def sparsity_control(A):
"""Get the sparsity control of the matrix."""
sparsity_control = ffi.new("int32_t*")
check_status(A, lib.GxB_Matrix_Option_get(A[0], lib.GxB_SPARSITY_CONTROL, sparsity_control))
check_status(
A, lib.GxB_Matrix_Option_get(A[0], lib.GxB_SPARSITY_CONTROL, vararg(sparsity_control))
)
return sparsity_control[0]


def set_sparsity_control(A, sparsity):
"""Set the sparsity control of the matrix."""
sparsity_control = ffi.cast("int32_t", sparsity)
check_status(
A, lib.GxB_Matrix_Option_set(A[0], lib.GxB_SPARSITY_CONTROL, ffi.cast("int", sparsity))
A, lib.GxB_Matrix_Option_set(A[0], lib.GxB_SPARSITY_CONTROL, vararg(sparsity_control))
)


def hyper_switch(A):
"""Get the hyper switch of the matrix."""
hyper_switch = ffi.new("double*")
check_status(A, lib.GxB_Matrix_Option_get(A[0], lib.GxB_HYPER_SWITCH, hyper_switch))
check_status(A, lib.GxB_Matrix_Option_get(A[0], lib.GxB_HYPER_SWITCH, vararg(hyper_switch)))
return hyper_switch[0]


def set_hyper_switch(A, hyper_switch):
"""Set the hyper switch of the matrix."""
check_status(
A, lib.GxB_Matrix_Option_set(A[0], lib.GxB_HYPER_SWITCH, ffi.cast("double", hyper_switch))
)
hyper_switch = ffi.cast("double", hyper_switch)
check_status(A, lib.GxB_Matrix_Option_set(A[0], lib.GxB_HYPER_SWITCH, vararg(hyper_switch)))


def bitmap_switch(A):
"""Get the bitmap switch of the matrix."""
bitmap_switch = ffi.new("double*")
check_status(A, lib.GxB_Matrix_Option_get(A[0], lib.GxB_BITMAP_SWITCH, bitmap_switch))
check_status(A, lib.GxB_Matrix_Option_get(A[0], lib.GxB_BITMAP_SWITCH, vararg(bitmap_switch)))
return bitmap_switch[0]


def set_bitmap_switch(A, bitmap_switch):
"""Set the bitmap switch of the matrix."""
check_status(
A, lib.GxB_Matrix_Option_set(A[0], lib.GxB_BITMAP_SWITCH, ffi.cast("double", bitmap_switch))
)
bitmap_switch = ffi.cast("double", bitmap_switch)
check_status(A, lib.GxB_Matrix_Option_set(A[0], lib.GxB_BITMAP_SWITCH, vararg(bitmap_switch)))


def set_bool(A, value, i, j):
Expand Down
Loading