Skip to content

Commit 5830d56

Browse files
authored
Improved Apple Silicon support (variadic functions) + add tests on apple-silicon-m1 (self hosted runner) (#85)
* Add tests on apple-silicon-m1 (Self hosted runner) * Remove (unnecessary?) dylib and fix Makefile to build them as universal2 * Fixes variadic function call on ARM64 * IMP*(void*, SEL, ...) is failing on Apple Silicon but is not our fault * ffi_prep_cif_var should be guarded via __builtin_available(macOS 10.15, *) * On iOS we're using a non-apple version of ffi that should support ffi_prep_cif_var
1 parent 586991a commit 5830d56

File tree

9 files changed

+93
-15
lines changed

9 files changed

+93
-15
lines changed

.ci/osx_ci.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/bash
2+
set -e -x
3+
4+
arm64_set_path_and_python_version(){
5+
python_version="$1"
6+
if [[ $(/usr/bin/arch) = arm64 ]]; then
7+
export PATH=/opt/homebrew/bin:$PATH
8+
eval "$(pyenv init --path)"
9+
pyenv install $python_version -s
10+
pyenv global $python_version
11+
export PATH=$(pyenv prefix)/bin:$PATH
12+
fi
13+
}

.github/workflows/python-package.yml

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,48 @@ on: [push, pull_request]
55
jobs:
66
build:
77

8-
runs-on: macOs-latest
8+
name: "build (${{ matrix.runs_on }}, ${{ matrix.python }})"
9+
defaults:
10+
run:
11+
shell: ${{ matrix.run_wrapper || 'bash --noprofile --norc -eo pipefail {0}' }}
12+
runs-on: ${{ matrix.runs_on || 'macos-latest' }}
913
strategy:
1014
matrix:
11-
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]
12-
15+
include:
16+
- runs_on: macos-latest
17+
python: "3.7"
18+
- runs_on: macos-latest
19+
python: "3.8"
20+
- runs_on: macos-latest
21+
python: "3.9"
22+
- runs_on: macos-latest
23+
python: "3.10"
24+
- runs_on: apple-silicon-m1
25+
run_wrapper: arch -arm64 bash --noprofile --norc -eo pipefail {0}
26+
python: "3.9.11"
27+
- runs_on: apple-silicon-m1
28+
run_wrapper: arch -arm64 bash --noprofile --norc -eo pipefail {0}
29+
python: "3.10.3"
1330
steps:
1431
- uses: actions/checkout@v2
15-
- name: Set up Python ${{ matrix.python-version }}
32+
- name: Set up Python ${{ matrix.python }}
33+
# Needs to be skipped on our self-hosted runners tagged as 'apple-silicon-m1'
34+
if: ${{ matrix.runs_on != 'apple-silicon-m1' }}
1635
uses: actions/setup-python@v2
1736
with:
18-
python-version: ${{ matrix.python-version }}
37+
python-version: ${{ matrix.python }}
1938

2039
- name: Install project
2140
run: |
41+
source .ci/osx_ci.sh
42+
arm64_set_path_and_python_version ${{ matrix.python }}
2243
pip install cython pytest
2344
pip install .
2445
2546
- name: Test with pytest
2647
run: |
48+
source .ci/osx_ci.sh
49+
arm64_set_path_and_python_version ${{ matrix.python }}
2750
make test_lib
2851
make
2952
make tests

Makefile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ build_ext:
44
env CFLAGS="-O0" python setup.py build_ext --inplace -g
55

66
test_lib:
7-
rm -rf objc_classes/test/usrlib.dylib
8-
clang objc_classes/test/testlib.m -o objc_classes/test/testlib.dylib -dynamiclib -framework Foundation
7+
rm -rf objc_classes/test/testlib.dylib objc_classes/test/CArrayTestlib.dylib
8+
clang objc_classes/test/testlib.m -o objc_classes/test/testlib.dylib -dynamiclib -framework Foundation -arch arm64 -arch x86_64
9+
clang objc_classes/test/CArrayTestlib.m -o objc_classes/test/CArrayTestlib.dylib -dynamiclib -framework Foundation -arch arm64 -arch x86_64
910

1011
tests: build_ext
1112
cd tests && env PYTHONPATH=..:$(PYTHONPATH) python -m pytest -v

objc_classes/test/CArrayTestlib.dylib

-41.4 KB
Binary file not shown.

objc_classes/test/testlib.dylib

-30.3 KB
Binary file not shown.

objc_classes/test/testlib.m

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,22 @@ - (IMP) getImp {
146146
return [self methodForSelector:@selector(getSumOf:and:)];
147147
}
148148

149-
- (int) useImp:(IMP*(void*, SEL, ...))imp withA:(int)a andB:(int)b {
149+
/*
150+
Variadic Functions are managed differently on ARM64!
151+
152+
IMP*(void*, SEL, ...) is failing on Apple Silicon, but not due to pyobjus.
153+
- getandUseImpWithDefaultValues is here to demonstrate is not pyobjus fault,
154+
in fact, directly calling getandUseImpWithDefaultValues with `IMP*(void*, SEL, ...)`
155+
instead of `IMP*(void*, SEL, int, int)` will lead to unexpected results.
156+
*/
157+
- (int) useImp:(IMP*(void*, SEL, int, int))imp withA:(int)a andB:(int)b {
150158
return (int)imp(self, @selector(getSumOf:and:), a, b);
151159
}
152160

161+
- (int) getandUseImpWithDefaultValues {
162+
return (int)[self useImp: [self getImp] withA: 7 andB: 5];
163+
}
164+
153165
/******************** </UNKNOWN TYPE TESTS> ***********************/
154166

155167
/******************** <IVARS TESTS> ***********************/

pyobjus/_runtime.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include <objc/runtime.h>
22
#include <objc/message.h>
3+
#include <ffi/ffi.h>
34
#include <stdio.h>
45
#include <dlfcn.h>
56
#include <string.h>
@@ -52,4 +53,21 @@ id objc_msgSend_custom(id obj, SEL sel){
5253
}
5354

5455
bool MACOS_HAVE_OBJMSGSEND_STRET = false;
55-
#endif
56+
#endif
57+
58+
ffi_status guarded_ffi_prep_cif_var(ffi_cif *_Nonnull cif, ffi_abi abi, unsigned int nfixedargs,
59+
unsigned int ntotalargs, ffi_type *_Nonnull rtype, ffi_type *_Nonnull *_Nonnull atypes)
60+
{
61+
if (ntotalargs > nfixedargs)
62+
{
63+
#if TARGET_OS_OSX
64+
if (__builtin_available(macOS 10.15, *))
65+
{
66+
return ffi_prep_cif_var(cif, abi, nfixedargs, ntotalargs, rtype, atypes);
67+
}
68+
#elif TARGET_OS_IOS
69+
return ffi_prep_cif_var(cif, abi, nfixedargs, ntotalargs, rtype, atypes);
70+
#endif
71+
}
72+
return ffi_prep_cif(cif, abi, ntotalargs, rtype, atypes);
73+
}

pyobjus/common.pxi

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,6 @@ cdef extern from "ffi/ffi.h":
148148
cdef ffi_type ffi_type_longdouble
149149
cdef ffi_type ffi_type_pointer
150150

151-
cdef ffi_status ffi_prep_cif(ffi_cif *cif, ffi_abi abi,
152-
unsigned int nargs,ffi_type *rtype, ffi_type **atypes)
153-
154151
cdef void ffi_call(ffi_cif *cif, void (*fn)(), void *rvalue,
155152
void **avalue)
156153

@@ -161,3 +158,6 @@ cdef extern from "_runtime.h":
161158
id objc_msgSend_custom(id obj, SEL sel)
162159
void objc_msgSend_stret__safe(id self, SEL selector, ...)
163160
bool MACOS_HAVE_OBJMSGSEND_STRET
161+
ffi_status guarded_ffi_prep_cif_var(ffi_cif *cif, ffi_abi abi,
162+
unsigned int nfixedargs, unsigned int ntotalargs,
163+
ffi_type *rtype, ffi_type **atypes)

pyobjus/pyobjus.pyx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ cdef class ObjcMethod(object):
294294

295295
# casting is needed here because otherwise we will get warning at compile
296296
cdef unsigned int num_args = <unsigned int>len(signature_args)
297+
cdef unsigned int num_fixed_args = <unsigned int>len(self.signature_args)
297298
cdef unsigned int size = sizeof(ffi_type) * num_args
298299

299300
# allocate memory to hold ffi_type* of arguments
@@ -317,8 +318,17 @@ cdef class ObjcMethod(object):
317318

318319
# FFI PREP
319320
cdef ffi_status f_status
320-
f_status = ffi_prep_cif(&self.f_cif, FFI_DEFAULT_ABI,
321-
num_args, self.f_result_type, self.f_arg_types)
321+
322+
f_status = guarded_ffi_prep_cif_var(
323+
&self.f_cif,
324+
FFI_DEFAULT_ABI,
325+
num_fixed_args,
326+
num_args,
327+
self.f_result_type,
328+
self.f_arg_types,
329+
)
330+
331+
322332
if f_status != FFI_OK:
323333
raise ObjcException(
324334
'Unable to prepare the method {0!r}'.format(self.name))
@@ -348,7 +358,8 @@ cdef class ObjcMethod(object):
348358
cdef size_t result_size = <size_t>int(self.signature_return[1])
349359

350360
# check that we have at least the same number of arguments as the
351-
# signature want.
361+
# signature want (having more than expected could signify that the called
362+
# function is variadic).
352363
if len(args) < len(self.signature_args) - 2:
353364
raise ObjcException('Not enough parameters for {}'.format(
354365
self.name))

0 commit comments

Comments
 (0)