Skip to content
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

bpo-41100: ctypes fixes for arm64 Mac OS #21249

Conversation

lawrence-danna-apple
Copy link
Contributor

@lawrence-danna-apple lawrence-danna-apple commented Jul 1, 2020

Posted in response to this comment by Ronald #21242 (comment)

This PR would supersede these two, combining because the third commit depends on both

This has three commits:

  • ctypes: use the correct ABI for variadic functions
  • use system libffi for Mac OS 10.15 and up
  • setup.py: probe libffi for ffi_closure_alloc and ffi_prep_cif_var

The first two are from those previous PRs. The third makes setup.py probe for those functions by searching libffi's headers.

https://bugs.python.org/issue41100

This updates setup.py to default to the system libffi on Mac OS 10.15 and forward.   It also updates
detect_ctypes to prefer finding libffi in the Xcode SDK, rather than /usr/include
On arm64 the calling convention for variardic functions is different
than the convention for fixed-arg functions of the same arg types.

ctypes needs to use ffi_prep_cif_var to tell libffi which calling
convention to use.
SMillerDev pushed a commit to Homebrew/homebrew-core that referenced this pull request Jul 4, 2020
This set of patches includes the following upstream pull requests:

- python/cpython#21114
  "Support `arm64` in Mac/Tools/pythonw"

- python/cpython#21224
  "allow python to build for macosx-11.0-arm64"

- python/cpython#21249
  "ctypes fixes for arm64 Mac OS"

Adding the patches before upstream has released them is warranted here
because `python@3.8` is widely used as a dependency, and the patch is
needed to enable testing dependent formulae on arm64.

CC: Sean Molenaar <1484494+SMillerDev@users.noreply.github.com>
@maxbelanger
Copy link
Contributor

@lawrence-danna-apple We currently redistribute libffi along with Python to support Mac OS X 10.10 as our deployment target. Your change implies that there were changes made to libffi (and the copy that ships with macOS 11) for arm64 macOS. Are these upstreamed and available?

@lawrence-danna-apple
Copy link
Contributor Author

@lawrence-danna-apple We currently redistribute libffi along with Python to support Mac OS X 10.10 as our deployment target. Your change implies that there were changes made to libffi (and the copy that ships with macOS 11) for arm64 macOS. Are these upstreamed and available?

We also distribute libffi as part of Mac OS. Our changes for 10.15, which cover the necessary API changes are available here: https://opensource.apple.com/source/libffi/libffi-25/

I'm not sure what the status is as to upstreaming, but I'll do a review of it and see if anything necessary is still missing from upstream.

Noe that in the changes I'm proposing here, the Mac OS system libffi is only used if the deployment target is set to 10.15 or better.

@maxbelanger
Copy link
Contributor

@lawrence-danna-apple We currently redistribute libffi along with Python to support Mac OS X 10.10 as our deployment target. Your change implies that there were changes made to libffi (and the copy that ships with macOS 11) for arm64 macOS. Are these upstreamed and available?

We also distribute libffi as part of Mac OS. Our changes for 10.15, which cover the necessary API changes are available here: https://opensource.apple.com/source/libffi/libffi-25/

I'm not sure what the status is as to upstreaming, but I'll do a review of it and see if anything necessary is still missing from upstream.

Noe that in the changes I'm proposing here, the Mac OS system libffi is only used if the deployment target is set to 10.15 or better.

Got it, thanks for clarifying. It looks like there may be some issues in libffi w/ XC12 + arm64: libffi/libffi#571. Good to know that we could rely on libffi-25, though.

Co-authored-by: Arch Oversight <archoversight@gmail.com>
Copy link
Contributor

@ronaldoussoren ronaldoussoren left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lawrence-danna-apple We currently redistribute libffi along with Python to support Mac OS X 10.10 as our deployment target. Your change implies that there were changes made to libffi (and the copy that ships with macOS 11) for arm64 macOS. Are these upstreamed and available?

Isn't it possible/save to use the system libffi on 10.9 or later? The 10.9 SDK includes libffi, even if it uses an older API (just like libffi_osx in the CPython tree).

I'd prefer to drop the copy of libffi included with CPython and always use the system libffi, without including a copy of libffi in the installers for macOS.

@ronaldoussoren
Copy link
Contributor

I'm a bit conflicted about the "variadic" attribute for _FuncPtr. I agree that it is necessary, but this is an API change and as such is a lot harder to back port.

@lawrence-danna-apple
Copy link
Contributor Author

lawrence-danna-apple commented Jul 14, 2020

I'd prefer to drop the copy of libffi included with CPython and always use the system libffi

I'm not sure if this is possible. The libffi prior to 10.15 won't have the APIs we need to support arm64. However, we may be able to use ifdefs and availability to sort it out. I'll try.

Do you know why libffi was bundled into cpython in the first place? Do we have reason to believe the libffi on older releases of mac OS was broken in some way?

claui added a commit to Homebrew/formula-patches that referenced this pull request Jul 15, 2020
This set of patches includes the following upstream pull requests,
in this order:

- PR 20171, "Fix _tkinter use"
  python/cpython#20171
  (prerequisite for patch #21249 to apply)

- PR 21114, "Support `arm64` in Mac/Tools/pythonw"
  python/cpython#21114

- PR 21224, "allow python to build for macosx-11.0-arm64"
  python/cpython#21224

- PR 21249, "ctypes fixes for arm64 Mac OS"
  python/cpython#21249

The patches for 20171 and 21249 have been minimally modified in order
to backport them to 3.8.3.

Note that these have been successfully tested for `python@3.8`
but not for `python@3.7`.

The patch directive should be surrounded by an `if Hardware::CPU.arm?`
block.
claui pushed a commit to xvilo/homebrew-core that referenced this pull request Jul 15, 2020
This replaces the three unmerged PR patches with a hosted
formula patch.

This includes the following upstream pull requests:

- python/cpython#20171, "Fix _tkinter use" (prerequisite for 21249)
- python/cpython#21114, "Support arm64 in Mac/Tools/pythonw"
- python/cpython#21224, "allow python to build for macosx-11.0-arm64"
- python/cpython#21249, "ctypes fixes for arm64 Mac OS"

See also:

- Homebrew/formula-patches#292
@lawrence-danna-apple
Copy link
Contributor Author

@ronaldoussoren

I'm a bit conflicted about the "variadic" attribute for _FuncPtr. I agree that it is necessary

Well, it looks like I misread the documentation. It turns out that in the case where a variadic function is called with zero variadic arguments, the two calling conventions coincide. So the flag is not necessary after all.

@lawrence-danna-apple
Copy link
Contributor Author

lawrence-danna-apple commented Jul 15, 2020

@ronaldoussoren

I found it is possible to use the system libffi even if the deployment target is set to before 10.15. I've added runtime checks for ffi_prep_cif_var, ffi_prep_closure_loc and ffi_closure_alloc. If they are present, it will use them. If not, it will fall back on the old way of doing things.

I've also enabled the system ffi if either the deployment target is 10.15 OR the arm64 is in the target arches. I'm not sure if Mac OS on PPC is still supported by python, but these changes shouldn't break it if it is.

The only thing that will not work is to build a super-fat binary that works on ppc, x86 and arm64 at the same time.

Should I add a commit deleting libffi_osx altogether, or leave it in for ppc support?

BrewTestBot pushed a commit to Homebrew/homebrew-core that referenced this pull request Jul 16, 2020
This replaces the three unmerged PR patches with a hosted
formula patch.

This includes the following upstream pull requests:

- python/cpython#20171, "Fix _tkinter use" (prerequisite for 21249)
- python/cpython#21114, "Support arm64 in Mac/Tools/pythonw"
- python/cpython#21224, "allow python to build for macosx-11.0-arm64"
- python/cpython#21249, "ctypes fixes for arm64 Mac OS"

See also:

- Homebrew/formula-patches#292

Closes #57997.

Signed-off-by: Claudia Pellegrino <1239874+claui@users.noreply.github.com>
Copy link
Member

@ned-deily ned-deily left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a smoke test, I tried building with --with-system-ffi on macOS 10.9 and ran into the issue that __builtin_available is apparently not available until Xcode 9.0 on macOS 10.12 and it is a requirement that we can continue to be built on older systems with their supported Xcode versions. I found a workaround from the curl project that might be usable here if we need to support using the system libffi which would be desirable.

Also we should avoid the potential pragma warnings when building on other platforms.

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 11, ios 13, watchos 6, tvos 13, *)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__builtin_available is itself not available with older versions of macOS and Xcode, in particular with macOS 10.9 and Xcode 6, which we currently need to use to build our most common installer variants. And there are other projects like MacPorts that builds Python for older macOS systems. The general review comment has a link for a possible workaround from the curl project.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This use of __builtin_available should be safe, it is within a guard of HAVE_FFI_PREP_CLOSURE_LOC and USING_APPLE_OS_LIBFFI: The system version of libffi does not have ffi_prep_closure_loc in the 10.9 SDK (it is introduced in 10.15).

return -1;

# if USING_APPLE_OS_LIBFFI
# define HAVE_FFI_PREP_CIF_VAR_RUNTIME __builtin_available(macos 11, ios 13, watchos 6, tvos 13, *)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__builtin_available again

{
#if USING_APPLE_OS_LIBFFI
if (__builtin_available(macos 11, ios 13, watchos 6, tvos 13, *)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__builtin_available again

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid this needs a configure test, ffi_closure_free is not available at all in older SDKs.

{
#if USING_APPLE_OS_LIBFFI
if (__builtin_available(macos 11, ios 13, watchos 6, tvos 13, *)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__builtin_available again

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

p,
p->pcl_exec);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
result = ffi_prep_closure(p->pcl_write, &p->cif, closure_fcn, p);
#pragma clang diagnostic pop
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

result = ffi_prep_closure_loc(p->pcl_write, &p->cif, closure_fcn,
p,
p->pcl_exec);
#pragma clang diagnostic push
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid -Wunknown-pragmas warnings when building on other platforms.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@bedevere-bot
Copy link

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

Copy link
Contributor

@ronaldoussoren ronaldoussoren left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added some comments, and am finally working on this PR on the DTK.

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 11, ios 13, watchos 6, tvos 13, *)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This use of __builtin_available should be safe, it is within a guard of HAVE_FFI_PREP_CLOSURE_LOC and USING_APPLE_OS_LIBFFI: The system version of libffi does not have ffi_prep_closure_loc in the 10.9 SDK (it is introduced in 10.15).

@@ -2545,4 +2545,3 @@ Arrays and pointers

Returns the object to which to pointer points. Assigning to this
attribute changes the pointer to point to the assigned object.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary removal of a blank line (not important)

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 11, ios 13, watchos 6, tvos 13, *)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why test for macOS 11 and not 10.15? According to the header files this API is introduced in 10.15.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right it should be 10.15

return -1;

# if USING_APPLE_OS_LIBFFI
# define HAVE_FFI_PREP_CIF_VAR_RUNTIME __builtin_available(macos 11, ios 13, watchos 6, tvos 13, *)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise: the headers claim this _var variant is present in macOS 10.15.

"ffi_prep_cif failed");
return -1;

# if USING_APPLE_OS_LIBFFI
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proposed change:

#if USING_APPLE_OS_LIBFFI && HAVE_FFI_PREP_CIF_VAR

This way the __builtin_available is not used when building on older macOS versions.

{
#if USING_APPLE_OS_LIBFFI
if (__builtin_available(macos 11, ios 13, watchos 6, tvos 13, *)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid this needs a configure test, ffi_closure_free is not available at all in older SDKs.

{
#if USING_APPLE_OS_LIBFFI
if (__builtin_available(macos 11, ios 13, watchos 6, tvos 13, *)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

self.use_system_libffi = True
else:
self.use_system_libffi = '--with-system-ffi' in sysconfig.get_config_var("CONFIG_ARGS")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to just drop "use_system_libffi" and unconditionally use the system headers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

ronaldoussoren added a commit to ronaldoussoren/cpython that referenced this pull request Jul 20, 2020
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)
@lawrence-danna-apple
Copy link
Contributor Author

I have made the requested changes; please review again

@bedevere-bot
Copy link

Thanks for making the requested changes!

@ned-deily: please review the changes made to this pull request.

@lawrence-danna-apple
Copy link
Contributor Author

@ned-deily

Your comments have alerted me to a new problem. If I understand you correctly, the way you're doing the official builds for python is to build on an old version of Mac OS, with an old version of Xcode, in order to assure compatibility with a broad range of Mac OS versions.

This will work for x86_64, but if you want a universal build that works on both 10.10 and 11.0-arm64, it will not work, becaue the old Xcode doesn't support arm64 for Mac OS

On the other hand, if you build with the new Xcode using MACOSX_DEPLOYMENT_TARGET=10.10, that also does not work on 10.10, because autoconf will discover several functions such as getentropy which are available in the SDK, but are not available at runtime on 10.10. This will cause -Wunguarded-availability warnings in the build and an abort at runtime if the symbol is used.

@maxbelanger
Copy link
Contributor

@lawrence-danna-apple quite right. This has been an issue for us at Dropbox in recent years. Python's official approach for backwards compatibility is not based on setting the deployment target, which is problematic for use in client-side software. We've fixed this internally by patching pyconfig.h, but that's not a great fix (ideally we could do something like weak-link them).

See https://bugs.python.org/issue31359.

@lawrence-danna-apple
Copy link
Contributor Author

lawrence-danna-apple commented Jul 21, 2020

@maxbelanger @ned-deily

I opened up a new PR to deal with the deployment target issue.

#21576
#21577

@ned-deily
Copy link
Member

Thanks for the PR. We are aware of the issues with supporting multiple levels and architectures Being able to weaklink from a build on a newer version to an older version has been desirable but it hasn't been a high priority issue. Actively supporting multiple architectures again (something we haven't had to do since the retirement of PPC :) makes this a higher priority issue and @ronaldoussoren is now working on it. I've added him to the reviewers of the new PR (#21577).

Comment on lines +2153 to +2154
ext.extra_compile_args.append("-DUSING_APPLE_OS_LIBFFI=1")
ffi_in_sdk = os.path.join(macosx_sdk_root(), "usr/include/ffi")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please bear in mind that Python may be built against an external libffi on macOS that is not the OS-provided version. In MacPorts we build against our port of libffi 3.3 on all OS versions.

Copy link
Contributor Author

@lawrence-danna-apple lawrence-danna-apple Sep 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See previous comment by @ronaldoussoren who prefers to unconditionally use the system's libffi. I updated this PR accordingly. Is there any reason not to use the system libffi anymore? I tested going back to 10.10 i think for the other PR adding availability checks. What's the need to support alternative libffi these days?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same reasons as any other library: the OS often ships an older version which naturally lacks the features and bug fixes of later versions.

@ned-deily
Copy link
Member

Thanks for the PR. It has been included in and superseded by GH-22855.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants