Skip to content

Commit

Permalink
macOS/arm64 support, based on pythonGH-21249
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
ronaldoussoren committed Jul 20, 2020
1 parent deda5f0 commit ea3c200
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 71 deletions.
1 change: 1 addition & 0 deletions Lib/test/test_bytes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1035,6 +1035,7 @@ def test_from_format(self):
c_char_p)

PyBytes_FromFormat = pythonapi.PyBytes_FromFormat
PyBytes_FromFormat.argtypes = (c_char_p,)
PyBytes_FromFormat.restype = py_object

# basic tests
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_unicode.py
Original file line number Diff line number Diff line change
Expand Up @@ -2513,11 +2513,13 @@ class CAPITest(unittest.TestCase):
def test_from_format(self):
import_helper.import_module('ctypes')
from ctypes import (
c_char_p,
pythonapi, py_object, sizeof,
c_int, c_long, c_longlong, c_ssize_t,
c_uint, c_ulong, c_ulonglong, c_size_t, c_void_p)
name = "PyUnicode_FromFormat"
_PyUnicode_FromFormat = getattr(pythonapi, name)
_PyUnicode_FromFormat.argtypes = (c_char_p,)
_PyUnicode_FromFormat.restype = py_object

def PyUnicode_FromFormat(format, *args):
Expand Down
39 changes: 31 additions & 8 deletions Modules/_ctypes/callbacks.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include "Python.h"
#include "frameobject.h"

#include <stdbool.h>

#include <ffi.h>
#ifdef MS_WIN32
#include <windows.h>
Expand All @@ -18,7 +20,7 @@ CThunkObject_dealloc(PyObject *myself)
Py_XDECREF(self->callable);
Py_XDECREF(self->restype);
if (self->pcl_write)
ffi_closure_free(self->pcl_write);
Py_ffi_closure_free(self->pcl_write);
PyObject_GC_Del(self);
}

Expand Down Expand Up @@ -361,8 +363,7 @@ CThunkObject *_ctypes_alloc_callback(PyObject *callable,

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

p->pcl_write = ffi_closure_alloc(sizeof(ffi_closure),
&p->pcl_exec);
p->pcl_write = Py_ffi_closure_alloc(sizeof(ffi_closure), &p->pcl_exec);
if (p->pcl_write == NULL) {
PyErr_NoMemory();
goto error;
Expand Down Expand Up @@ -408,13 +409,35 @@ CThunkObject *_ctypes_alloc_callback(PyObject *callable,
"ffi_prep_cif failed with %d", result);
goto error;
}
#if defined(X86_DARWIN) || defined(POWERPC_DARWIN)
result = ffi_prep_closure(p->pcl_write, &p->cif, closure_fcn, p);
#if HAVE_FFI_PREP_CLOSURE_LOC
# if USING_APPLE_OS_LIBFFI
# define HAVE_FFI_PREP_CLOSURE_LOC_RUNTIME __builtin_available(macos 10.15, ios 13, watchos 6, tvos 13, *)
# else
# define HAVE_FFI_PREP_CLOSURE_LOC_RUNTIME true
# endif
if (HAVE_FFI_PREP_CLOSURE_LOC_RUNTIME) {
result = ffi_prep_closure_loc(p->pcl_write, &p->cif, closure_fcn,
p,
p->pcl_exec);
} else
#endif
{
#if USING_APPLE_OS_LIBFFI && defined(__arm64__)
PyErr_Format(PyExc_NotImplementedError, "ffi_prep_closure_loc() is missing");
goto error;
#else
result = ffi_prep_closure_loc(p->pcl_write, &p->cif, closure_fcn,
p,
p->pcl_exec);
#ifdef MACOSX
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif
result = ffi_prep_closure(p->pcl_write, &p->cif, closure_fcn, p);

#ifdef MACOSX
#pragma clang diagnostic pop
#endif

#endif
}
if (result != FFI_OK) {
PyErr_Format(PyExc_RuntimeError,
"ffi_prep_closure failed with %d", result);
Expand Down
72 changes: 60 additions & 12 deletions Modules/_ctypes/callproc.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
#include "Python.h"
#include "structmember.h" // PyMemberDef

#include <stdbool.h>

#ifdef MS_WIN32
#include <windows.h>
#include <tchar.h>
Expand Down Expand Up @@ -812,7 +814,8 @@ static int _call_function_pointer(int flags,
ffi_type **atypes,
ffi_type *restype,
void *resmem,
int argcount)
int argcount,
int argtypecount)
{
PyThreadState *_save = NULL; /* For Py_BLOCK_THREADS and Py_UNBLOCK_THREADS */
PyObject *error_object = NULL;
Expand All @@ -835,14 +838,60 @@ static int _call_function_pointer(int flags,
if ((flags & FUNCFLAG_CDECL) == 0)
cc = FFI_STDCALL;
#endif
if (FFI_OK != ffi_prep_cif(&cif,
cc,
argcount,
restype,
atypes)) {
PyErr_SetString(PyExc_RuntimeError,
"ffi_prep_cif failed");
return -1;

# if USING_APPLE_OS_LIBFFI
# define HAVE_FFI_PREP_CIF_VAR_RUNTIME __builtin_available(macos 10.15, ios 13, watchos 6, tvos 13, *)
# elif HAVE_FFI_PREP_CIF_VAR
# define HAVE_FFI_PREP_CIF_VAR_RUNTIME true
# else
# define HAVE_FFI_PREP_CIF_VAR_RUNTIME false
# endif

/* Even on Apple-arm64 the calling convention for variadic functions conincides
* with the standard calling convention in the case that the function called
* only with its fixed arguments. Thus, we do not need a special flag to be
* set on variadic functions. We treat a function as variadic if it is called
* with a nonzero number of variadic arguments */
bool is_variadic = (argtypecount != 0 && argcount > argtypecount);
(void) is_variadic;

#if defined(__APPLE__) && defined(__arm64__)
if (is_variadic) {
if (HAVE_FFI_PREP_CIF_VAR_RUNTIME) {
} else {
PyErr_SetString(PyExc_NotImplementedError, "ffi_prep_cif_var() is missing");
return -1;
}
}
#endif

#if HAVE_FFI_PREP_CIF_VAR
if (is_variadic) {
if (HAVE_FFI_PREP_CIF_VAR_RUNTIME) {
if (FFI_OK != ffi_prep_cif_var(&cif,
cc,
argtypecount,
argcount,
restype,
atypes)) {
PyErr_SetString(PyExc_RuntimeError,
"ffi_prep_cif_var failed");
return -1;
}
}
} else
#endif

{
if (FFI_OK != ffi_prep_cif(&cif,
cc,
argcount,
restype,
atypes)) {
PyErr_SetString(PyExc_RuntimeError,
"ffi_prep_cif failed");
return -1;
}
}

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

if (-1 == _call_function_pointer(flags, pProc, avalues, atypes,
rtype, resbuf,
Py_SAFE_DOWNCAST(argcount,
Py_ssize_t,
int)))
Py_SAFE_DOWNCAST(argcount, Py_ssize_t, int),
Py_SAFE_DOWNCAST(argtype_count, Py_ssize_t, int)))
goto cleanup;

#ifdef WORDS_BIGENDIAN
Expand Down
8 changes: 8 additions & 0 deletions Modules/_ctypes/ctypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,14 @@ PyObject *_ctypes_get_errobj(int **pspace);
extern PyObject *ComError;
#endif

#if USING_MALLOC_CLOSURE_DOT_C
void Py_ffi_closure_free(void *p);
void *Py_ffi_closure_alloc(size_t size, void** codeloc);
#else
#define Py_ffi_closure_free ffi_closure_free
#define Py_ffi_closure_alloc ffi_closure_alloc
#endif

/*
Local Variables:
compile-command: "python setup.py -q build install --home ~"
Expand Down
15 changes: 13 additions & 2 deletions Modules/_ctypes/malloc_closure.c
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,27 @@ static void more_core(void)
/******************************************************************/

/* put the item back into the free list */
void ffi_closure_free(void *p)
void Py_ffi_closure_free(void *p)
{
#if USING_APPLE_OS_LIBFFI && HAVE_FFI_CLOSURE_ALLOC
if (__builtin_available(macos 10.15, ios 13, watchos 6, tvos 13, *)) {
ffi_closure_free(p);
return;
}
#endif
ITEM *item = (ITEM *)p;
item->next = free_list;
free_list = item;
}

/* return one item from the free list, allocating more if needed */
void *ffi_closure_alloc(size_t ignored, void** codeloc)
void *Py_ffi_closure_alloc(size_t size, void** codeloc)
{
#if USING_APPLE_OS_LIBFFI && HAVE_FFI_CLOSURE_ALLOC
if (__builtin_available(macos 10.15, ios 13, watchos 6, tvos 13, *)) {
return ffi_closure_alloc(size, codeloc);
}
#endif
ITEM *item;
if (!free_list)
more_core();
Expand Down
102 changes: 53 additions & 49 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,13 @@ def macosx_sdk_specified():
macosx_sdk_root()
return MACOS_SDK_SPECIFIED

def is_macosx_at_least(vers):
if MACOS:
dep_target = sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET')
if dep_target:
return tuple(map(int, dep_target.split('.'))) >= vers
return False


def is_macosx_sdk_path(path):
"""
Expand All @@ -239,6 +246,13 @@ def is_macosx_sdk_path(path):
or path.startswith('/Library/') )


def grep_headers_for(function, headers):
for header in headers:
with open(header, 'r') as f:
if function in f.read():
return True
return False

def find_file(filename, std_dirs, paths):
"""Searches for the directory where a given file is located,
and returns a possibly-empty list of additional directories, or None
Expand Down Expand Up @@ -2100,43 +2114,18 @@ def detect_tkinter(self):
library_dirs=added_lib_dirs))
return True

def configure_ctypes_darwin(self, ext):
# Darwin (OS X) uses preconfigured files, in
# the Modules/_ctypes/libffi_osx directory.
ffi_srcdir = os.path.abspath(os.path.join(self.srcdir, 'Modules',
'_ctypes', 'libffi_osx'))
sources = [os.path.join(ffi_srcdir, p)
for p in ['ffi.c',
'x86/darwin64.S',
'x86/x86-darwin.S',
'x86/x86-ffi_darwin.c',
'x86/x86-ffi64.c',
'powerpc/ppc-darwin.S',
'powerpc/ppc-darwin_closure.S',
'powerpc/ppc-ffi_darwin.c',
'powerpc/ppc64-darwin_closure.S',
]]

# Add .S (preprocessed assembly) to C compiler source extensions.
self.compiler.src_extensions.append('.S')

include_dirs = [os.path.join(ffi_srcdir, 'include'),
os.path.join(ffi_srcdir, 'powerpc')]
ext.include_dirs.extend(include_dirs)
ext.sources.extend(sources)
return True

def configure_ctypes(self, ext):
if not self.use_system_libffi:
if MACOS:
return self.configure_ctypes_darwin(ext)
print('INFO: Could not locate ffi libs and/or headers')
return False
return True

def detect_ctypes(self):
# Thomas Heller's _ctypes module
self.use_system_libffi = False

if (not sysconfig.get_config_var("LIBFFI_INCLUDEDIR") and MACOS and
(is_macosx_at_least((10,15)) or '-arch arm64' in sysconfig.get_config_var("CFLAGS"))):
self.use_system_libffi = True
else:
self.use_system_libffi = '--with-system-ffi' in sysconfig.get_config_var("CONFIG_ARGS")

include_dirs = []
extra_compile_args = ['-DPy_BUILD_CORE_MODULE']
extra_link_args = []
Expand All @@ -2149,11 +2138,10 @@ def detect_ctypes(self):

if MACOS:
sources.append('_ctypes/malloc_closure.c')
sources.append('_ctypes/darwin/dlfcn_simple.c')
extra_compile_args.append('-DUSING_MALLOC_CLOSURE_DOT_C=1')
#sources.append('_ctypes/darwin/dlfcn_simple.c')
extra_compile_args.append('-DMACOSX')
include_dirs.append('_ctypes/darwin')
# XXX Is this still needed?
# extra_link_args.extend(['-read_only_relocs', 'warning'])

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

ffi_inc = sysconfig.get_config_var("LIBFFI_INCLUDEDIR")
ffi_lib = None

ffi_inc_dirs = self.inc_dirs.copy()
if MACOS:
if '--with-system-ffi' not in sysconfig.get_config_var("CONFIG_ARGS"):
return
# OS X 10.5 comes with libffi.dylib; the include files are
# in /usr/include/ffi
ffi_inc_dirs.append('/usr/include/ffi')

ffi_inc = [sysconfig.get_config_var("LIBFFI_INCLUDEDIR")]
if not ffi_inc or ffi_inc[0] == '':
ffi_inc = find_file('ffi.h', [], ffi_inc_dirs)
if ffi_inc is not None:
ffi_h = ffi_inc[0] + '/ffi.h'
# XXX: The define should only be added when actually using the system
# version (and not a locally compiled one)
ext.extra_compile_args.append("-DUSING_APPLE_OS_LIBFFI=1")
ffi_in_sdk = os.path.join(macosx_sdk_root(), "usr/include/ffi")
if os.path.exists(ffi_in_sdk):
ffi_inc = ffi_in_sdk
ffi_lib = 'ffi'
else:
# OS X 10.5 comes with libffi.dylib; the include files are
# in /usr/include/ffi
ffi_inc_dirs.append('/usr/include/ffi')

if not ffi_inc:
found = find_file('ffi.h', [], ffi_inc_dirs)
if found:
ffi_inc = found[0]
if ffi_inc:
ffi_h = ffi_inc + '/ffi.h'
if not os.path.exists(ffi_h):
ffi_inc = None
print('Header file {} does not exist'.format(ffi_h))
ffi_lib = None
if ffi_inc is not None:
if ffi_lib is None and ffi_inc:
for lib_name in ('ffi', 'ffi_pic'):
if (self.compiler.find_library_file(self.lib_dirs, lib_name)):
ffi_lib = lib_name
break

if ffi_inc and ffi_lib:
ext.include_dirs.extend(ffi_inc)
ffi_headers = glob(os.path.join(ffi_inc, '*.h'))
if grep_headers_for('ffi_prep_cif_var', ffi_headers):
ext.extra_compile_args.append("-DHAVE_FFI_PREP_CIF_VAR=1")
if grep_headers_for('ffi_prep_closure_loc', ffi_headers):
ext.extra_compile_args.append("-DHAVE_FFI_PREP_CLOSURE_LOC=1")
if grep_headers_for('ffi_closure_alloc', ffi_headers):
ext.extra_compile_args.append("-DHAVE_FFI_CLOSURE_ALLOC=1")
ext.include_dirs.append(ffi_inc)
ext.libraries.append(ffi_lib)
self.use_system_libffi = True

Expand Down

0 comments on commit ea3c200

Please sign in to comment.