Skip to content

Commit ea3c200

Browse files
macOS/arm64 support, based on pythonGH-21249
This is support for ctypes on macOS/arm64 based on PR 21249 by Lawrence D'Anna (Apple). Changes: - changed __builtin_available tests from 11.0 to 10.15 - added test to setup.py for ffi_closure_alloc and use that in malloc_closure.c - Minor change in the code path for ffi_prep_closure_var (coding style change)
1 parent deda5f0 commit ea3c200

7 files changed

Lines changed: 168 additions & 71 deletions

File tree

Lib/test/test_bytes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,7 @@ def test_from_format(self):
10351035
c_char_p)
10361036

10371037
PyBytes_FromFormat = pythonapi.PyBytes_FromFormat
1038+
PyBytes_FromFormat.argtypes = (c_char_p,)
10381039
PyBytes_FromFormat.restype = py_object
10391040

10401041
# basic tests

Lib/test/test_unicode.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2513,11 +2513,13 @@ class CAPITest(unittest.TestCase):
25132513
def test_from_format(self):
25142514
import_helper.import_module('ctypes')
25152515
from ctypes import (
2516+
c_char_p,
25162517
pythonapi, py_object, sizeof,
25172518
c_int, c_long, c_longlong, c_ssize_t,
25182519
c_uint, c_ulong, c_ulonglong, c_size_t, c_void_p)
25192520
name = "PyUnicode_FromFormat"
25202521
_PyUnicode_FromFormat = getattr(pythonapi, name)
2522+
_PyUnicode_FromFormat.argtypes = (c_char_p,)
25212523
_PyUnicode_FromFormat.restype = py_object
25222524

25232525
def PyUnicode_FromFormat(format, *args):

Modules/_ctypes/callbacks.c

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#include "Python.h"
22
#include "frameobject.h"
33

4+
#include <stdbool.h>
5+
46
#include <ffi.h>
57
#ifdef MS_WIN32
68
#include <windows.h>
@@ -18,7 +20,7 @@ CThunkObject_dealloc(PyObject *myself)
1820
Py_XDECREF(self->callable);
1921
Py_XDECREF(self->restype);
2022
if (self->pcl_write)
21-
ffi_closure_free(self->pcl_write);
23+
Py_ffi_closure_free(self->pcl_write);
2224
PyObject_GC_Del(self);
2325
}
2426

@@ -361,8 +363,7 @@ CThunkObject *_ctypes_alloc_callback(PyObject *callable,
361363

362364
assert(CThunk_CheckExact((PyObject *)p));
363365

364-
p->pcl_write = ffi_closure_alloc(sizeof(ffi_closure),
365-
&p->pcl_exec);
366+
p->pcl_write = Py_ffi_closure_alloc(sizeof(ffi_closure), &p->pcl_exec);
366367
if (p->pcl_write == NULL) {
367368
PyErr_NoMemory();
368369
goto error;
@@ -408,13 +409,35 @@ CThunkObject *_ctypes_alloc_callback(PyObject *callable,
408409
"ffi_prep_cif failed with %d", result);
409410
goto error;
410411
}
411-
#if defined(X86_DARWIN) || defined(POWERPC_DARWIN)
412-
result = ffi_prep_closure(p->pcl_write, &p->cif, closure_fcn, p);
412+
#if HAVE_FFI_PREP_CLOSURE_LOC
413+
# if USING_APPLE_OS_LIBFFI
414+
# define HAVE_FFI_PREP_CLOSURE_LOC_RUNTIME __builtin_available(macos 10.15, ios 13, watchos 6, tvos 13, *)
415+
# else
416+
# define HAVE_FFI_PREP_CLOSURE_LOC_RUNTIME true
417+
# endif
418+
if (HAVE_FFI_PREP_CLOSURE_LOC_RUNTIME) {
419+
result = ffi_prep_closure_loc(p->pcl_write, &p->cif, closure_fcn,
420+
p,
421+
p->pcl_exec);
422+
} else
423+
#endif
424+
{
425+
#if USING_APPLE_OS_LIBFFI && defined(__arm64__)
426+
PyErr_Format(PyExc_NotImplementedError, "ffi_prep_closure_loc() is missing");
427+
goto error;
413428
#else
414-
result = ffi_prep_closure_loc(p->pcl_write, &p->cif, closure_fcn,
415-
p,
416-
p->pcl_exec);
429+
#ifdef MACOSX
430+
#pragma clang diagnostic push
431+
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
417432
#endif
433+
result = ffi_prep_closure(p->pcl_write, &p->cif, closure_fcn, p);
434+
435+
#ifdef MACOSX
436+
#pragma clang diagnostic pop
437+
#endif
438+
439+
#endif
440+
}
418441
if (result != FFI_OK) {
419442
PyErr_Format(PyExc_RuntimeError,
420443
"ffi_prep_closure failed with %d", result);

Modules/_ctypes/callproc.c

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
#include "Python.h"
5858
#include "structmember.h" // PyMemberDef
5959

60+
#include <stdbool.h>
61+
6062
#ifdef MS_WIN32
6163
#include <windows.h>
6264
#include <tchar.h>
@@ -812,7 +814,8 @@ static int _call_function_pointer(int flags,
812814
ffi_type **atypes,
813815
ffi_type *restype,
814816
void *resmem,
815-
int argcount)
817+
int argcount,
818+
int argtypecount)
816819
{
817820
PyThreadState *_save = NULL; /* For Py_BLOCK_THREADS and Py_UNBLOCK_THREADS */
818821
PyObject *error_object = NULL;
@@ -835,14 +838,60 @@ static int _call_function_pointer(int flags,
835838
if ((flags & FUNCFLAG_CDECL) == 0)
836839
cc = FFI_STDCALL;
837840
#endif
838-
if (FFI_OK != ffi_prep_cif(&cif,
839-
cc,
840-
argcount,
841-
restype,
842-
atypes)) {
843-
PyErr_SetString(PyExc_RuntimeError,
844-
"ffi_prep_cif failed");
845-
return -1;
841+
842+
# if USING_APPLE_OS_LIBFFI
843+
# define HAVE_FFI_PREP_CIF_VAR_RUNTIME __builtin_available(macos 10.15, ios 13, watchos 6, tvos 13, *)
844+
# elif HAVE_FFI_PREP_CIF_VAR
845+
# define HAVE_FFI_PREP_CIF_VAR_RUNTIME true
846+
# else
847+
# define HAVE_FFI_PREP_CIF_VAR_RUNTIME false
848+
# endif
849+
850+
/* Even on Apple-arm64 the calling convention for variadic functions conincides
851+
* with the standard calling convention in the case that the function called
852+
* only with its fixed arguments. Thus, we do not need a special flag to be
853+
* set on variadic functions. We treat a function as variadic if it is called
854+
* with a nonzero number of variadic arguments */
855+
bool is_variadic = (argtypecount != 0 && argcount > argtypecount);
856+
(void) is_variadic;
857+
858+
#if defined(__APPLE__) && defined(__arm64__)
859+
if (is_variadic) {
860+
if (HAVE_FFI_PREP_CIF_VAR_RUNTIME) {
861+
} else {
862+
PyErr_SetString(PyExc_NotImplementedError, "ffi_prep_cif_var() is missing");
863+
return -1;
864+
}
865+
}
866+
#endif
867+
868+
#if HAVE_FFI_PREP_CIF_VAR
869+
if (is_variadic) {
870+
if (HAVE_FFI_PREP_CIF_VAR_RUNTIME) {
871+
if (FFI_OK != ffi_prep_cif_var(&cif,
872+
cc,
873+
argtypecount,
874+
argcount,
875+
restype,
876+
atypes)) {
877+
PyErr_SetString(PyExc_RuntimeError,
878+
"ffi_prep_cif_var failed");
879+
return -1;
880+
}
881+
}
882+
} else
883+
#endif
884+
885+
{
886+
if (FFI_OK != ffi_prep_cif(&cif,
887+
cc,
888+
argcount,
889+
restype,
890+
atypes)) {
891+
PyErr_SetString(PyExc_RuntimeError,
892+
"ffi_prep_cif failed");
893+
return -1;
894+
}
846895
}
847896

848897
if (flags & (FUNCFLAG_USE_ERRNO | FUNCFLAG_USE_LASTERROR)) {
@@ -1212,9 +1261,8 @@ PyObject *_ctypes_callproc(PPROC pProc,
12121261

12131262
if (-1 == _call_function_pointer(flags, pProc, avalues, atypes,
12141263
rtype, resbuf,
1215-
Py_SAFE_DOWNCAST(argcount,
1216-
Py_ssize_t,
1217-
int)))
1264+
Py_SAFE_DOWNCAST(argcount, Py_ssize_t, int),
1265+
Py_SAFE_DOWNCAST(argtype_count, Py_ssize_t, int)))
12181266
goto cleanup;
12191267

12201268
#ifdef WORDS_BIGENDIAN

Modules/_ctypes/ctypes.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,14 @@ PyObject *_ctypes_get_errobj(int **pspace);
366366
extern PyObject *ComError;
367367
#endif
368368

369+
#if USING_MALLOC_CLOSURE_DOT_C
370+
void Py_ffi_closure_free(void *p);
371+
void *Py_ffi_closure_alloc(size_t size, void** codeloc);
372+
#else
373+
#define Py_ffi_closure_free ffi_closure_free
374+
#define Py_ffi_closure_alloc ffi_closure_alloc
375+
#endif
376+
369377
/*
370378
Local Variables:
371379
compile-command: "python setup.py -q build install --home ~"

Modules/_ctypes/malloc_closure.c

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,16 +89,27 @@ static void more_core(void)
8989
/******************************************************************/
9090

9191
/* put the item back into the free list */
92-
void ffi_closure_free(void *p)
92+
void Py_ffi_closure_free(void *p)
9393
{
94+
#if USING_APPLE_OS_LIBFFI && HAVE_FFI_CLOSURE_ALLOC
95+
if (__builtin_available(macos 10.15, ios 13, watchos 6, tvos 13, *)) {
96+
ffi_closure_free(p);
97+
return;
98+
}
99+
#endif
94100
ITEM *item = (ITEM *)p;
95101
item->next = free_list;
96102
free_list = item;
97103
}
98104

99105
/* return one item from the free list, allocating more if needed */
100-
void *ffi_closure_alloc(size_t ignored, void** codeloc)
106+
void *Py_ffi_closure_alloc(size_t size, void** codeloc)
101107
{
108+
#if USING_APPLE_OS_LIBFFI && HAVE_FFI_CLOSURE_ALLOC
109+
if (__builtin_available(macos 10.15, ios 13, watchos 6, tvos 13, *)) {
110+
return ffi_closure_alloc(size, codeloc);
111+
}
112+
#endif
102113
ITEM *item;
103114
if (!free_list)
104115
more_core();

setup.py

Lines changed: 53 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,13 @@ def macosx_sdk_specified():
229229
macosx_sdk_root()
230230
return MACOS_SDK_SPECIFIED
231231

232+
def is_macosx_at_least(vers):
233+
if MACOS:
234+
dep_target = sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET')
235+
if dep_target:
236+
return tuple(map(int, dep_target.split('.'))) >= vers
237+
return False
238+
232239

233240
def is_macosx_sdk_path(path):
234241
"""
@@ -239,6 +246,13 @@ def is_macosx_sdk_path(path):
239246
or path.startswith('/Library/') )
240247

241248

249+
def grep_headers_for(function, headers):
250+
for header in headers:
251+
with open(header, 'r') as f:
252+
if function in f.read():
253+
return True
254+
return False
255+
242256
def find_file(filename, std_dirs, paths):
243257
"""Searches for the directory where a given file is located,
244258
and returns a possibly-empty list of additional directories, or None
@@ -2100,43 +2114,18 @@ def detect_tkinter(self):
21002114
library_dirs=added_lib_dirs))
21012115
return True
21022116

2103-
def configure_ctypes_darwin(self, ext):
2104-
# Darwin (OS X) uses preconfigured files, in
2105-
# the Modules/_ctypes/libffi_osx directory.
2106-
ffi_srcdir = os.path.abspath(os.path.join(self.srcdir, 'Modules',
2107-
'_ctypes', 'libffi_osx'))
2108-
sources = [os.path.join(ffi_srcdir, p)
2109-
for p in ['ffi.c',
2110-
'x86/darwin64.S',
2111-
'x86/x86-darwin.S',
2112-
'x86/x86-ffi_darwin.c',
2113-
'x86/x86-ffi64.c',
2114-
'powerpc/ppc-darwin.S',
2115-
'powerpc/ppc-darwin_closure.S',
2116-
'powerpc/ppc-ffi_darwin.c',
2117-
'powerpc/ppc64-darwin_closure.S',
2118-
]]
2119-
2120-
# Add .S (preprocessed assembly) to C compiler source extensions.
2121-
self.compiler.src_extensions.append('.S')
2122-
2123-
include_dirs = [os.path.join(ffi_srcdir, 'include'),
2124-
os.path.join(ffi_srcdir, 'powerpc')]
2125-
ext.include_dirs.extend(include_dirs)
2126-
ext.sources.extend(sources)
2127-
return True
2128-
21292117
def configure_ctypes(self, ext):
2130-
if not self.use_system_libffi:
2131-
if MACOS:
2132-
return self.configure_ctypes_darwin(ext)
2133-
print('INFO: Could not locate ffi libs and/or headers')
2134-
return False
21352118
return True
21362119

21372120
def detect_ctypes(self):
21382121
# Thomas Heller's _ctypes module
2139-
self.use_system_libffi = False
2122+
2123+
if (not sysconfig.get_config_var("LIBFFI_INCLUDEDIR") and MACOS and
2124+
(is_macosx_at_least((10,15)) or '-arch arm64' in sysconfig.get_config_var("CFLAGS"))):
2125+
self.use_system_libffi = True
2126+
else:
2127+
self.use_system_libffi = '--with-system-ffi' in sysconfig.get_config_var("CONFIG_ARGS")
2128+
21402129
include_dirs = []
21412130
extra_compile_args = ['-DPy_BUILD_CORE_MODULE']
21422131
extra_link_args = []
@@ -2149,11 +2138,10 @@ def detect_ctypes(self):
21492138

21502139
if MACOS:
21512140
sources.append('_ctypes/malloc_closure.c')
2152-
sources.append('_ctypes/darwin/dlfcn_simple.c')
2141+
extra_compile_args.append('-DUSING_MALLOC_CLOSURE_DOT_C=1')
2142+
#sources.append('_ctypes/darwin/dlfcn_simple.c')
21532143
extra_compile_args.append('-DMACOSX')
21542144
include_dirs.append('_ctypes/darwin')
2155-
# XXX Is this still needed?
2156-
# extra_link_args.extend(['-read_only_relocs', 'warning'])
21572145

21582146
elif HOST_PLATFORM == 'sunos5':
21592147
# XXX This shouldn't be necessary; it appears that some
@@ -2183,31 +2171,47 @@ def detect_ctypes(self):
21832171
sources=['_ctypes/_ctypes_test.c'],
21842172
libraries=['m']))
21852173

2174+
ffi_inc = sysconfig.get_config_var("LIBFFI_INCLUDEDIR")
2175+
ffi_lib = None
2176+
21862177
ffi_inc_dirs = self.inc_dirs.copy()
21872178
if MACOS:
2188-
if '--with-system-ffi' not in sysconfig.get_config_var("CONFIG_ARGS"):
2189-
return
2190-
# OS X 10.5 comes with libffi.dylib; the include files are
2191-
# in /usr/include/ffi
2192-
ffi_inc_dirs.append('/usr/include/ffi')
2193-
2194-
ffi_inc = [sysconfig.get_config_var("LIBFFI_INCLUDEDIR")]
2195-
if not ffi_inc or ffi_inc[0] == '':
2196-
ffi_inc = find_file('ffi.h', [], ffi_inc_dirs)
2197-
if ffi_inc is not None:
2198-
ffi_h = ffi_inc[0] + '/ffi.h'
2179+
# XXX: The define should only be added when actually using the system
2180+
# version (and not a locally compiled one)
2181+
ext.extra_compile_args.append("-DUSING_APPLE_OS_LIBFFI=1")
2182+
ffi_in_sdk = os.path.join(macosx_sdk_root(), "usr/include/ffi")
2183+
if os.path.exists(ffi_in_sdk):
2184+
ffi_inc = ffi_in_sdk
2185+
ffi_lib = 'ffi'
2186+
else:
2187+
# OS X 10.5 comes with libffi.dylib; the include files are
2188+
# in /usr/include/ffi
2189+
ffi_inc_dirs.append('/usr/include/ffi')
2190+
2191+
if not ffi_inc:
2192+
found = find_file('ffi.h', [], ffi_inc_dirs)
2193+
if found:
2194+
ffi_inc = found[0]
2195+
if ffi_inc:
2196+
ffi_h = ffi_inc + '/ffi.h'
21992197
if not os.path.exists(ffi_h):
22002198
ffi_inc = None
22012199
print('Header file {} does not exist'.format(ffi_h))
2202-
ffi_lib = None
2203-
if ffi_inc is not None:
2200+
if ffi_lib is None and ffi_inc:
22042201
for lib_name in ('ffi', 'ffi_pic'):
22052202
if (self.compiler.find_library_file(self.lib_dirs, lib_name)):
22062203
ffi_lib = lib_name
22072204
break
22082205

22092206
if ffi_inc and ffi_lib:
2210-
ext.include_dirs.extend(ffi_inc)
2207+
ffi_headers = glob(os.path.join(ffi_inc, '*.h'))
2208+
if grep_headers_for('ffi_prep_cif_var', ffi_headers):
2209+
ext.extra_compile_args.append("-DHAVE_FFI_PREP_CIF_VAR=1")
2210+
if grep_headers_for('ffi_prep_closure_loc', ffi_headers):
2211+
ext.extra_compile_args.append("-DHAVE_FFI_PREP_CLOSURE_LOC=1")
2212+
if grep_headers_for('ffi_closure_alloc', ffi_headers):
2213+
ext.extra_compile_args.append("-DHAVE_FFI_CLOSURE_ALLOC=1")
2214+
ext.include_dirs.append(ffi_inc)
22112215
ext.libraries.append(ffi_lib)
22122216
self.use_system_libffi = True
22132217

0 commit comments

Comments
 (0)