From 2456fbddfb5d01597b4007a109997743f4395848 Mon Sep 17 00:00:00 2001 From: Tim Kaune <tim.kaune@gmx.de> Date: Wed, 14 May 2025 10:45:50 +0200 Subject: [PATCH] Move parsing of alias files to external dependency Accelerate LAPACK --- CMakeLists.txt | 88 +++-------------------------- README.md | 138 ++++++++++----------------------------------- src/CMakeLists.txt | 129 +++++------------------------------------- 3 files changed, 54 insertions(+), 301 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ab98ec8..74adf71 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,86 +41,16 @@ option(BUILD_INDEX64 "Link to ILP64 version of LAPACK?" OFF) message(STATUS "Trying to link to ILP64 interface of LAPACK: ${BUILD_NEW_LAPACK}") -if (CMAKE_HOST_SYSTEM_NAME MATCHES "Darwin") - # The available binary Accelerate version is determined by the MacOS system - message(STATUS "The Darwin kernel version is: ${CMAKE_HOST_SYSTEM_VERSION}") - - if (CMAKE_HOST_SYSTEM_VERSION VERSION_GREATER_EQUAL 24.0) - # Mac OS X 15.0 Sequoia or later - message(STATUS "The MacOS version is: >=15.0") - set(MINIMUM_MACOS_SDK_VERSION 15.0) - set(ACCELERATE_NEW_LAPACK_VERSION 3.11.0) - elseif (CMAKE_HOST_SYSTEM_VERSION VERSION_GREATER_EQUAL 22.4) - # Mac OS X 13.3 Ventura or later - message(STATUS "The MacOS version is: >=13.3,<15.0") - set(MINIMUM_MACOS_SDK_VERSION 13.3) - set(VALID_MACOS_SDK_VERSIONS 14.5 14.4 14.2 14.0 13.3) - set(ACCELERATE_NEW_LAPACK_VERSION 3.9.1) - else () - # Before Mac OS X 13.3 Ventura - message(FATAL_ERROR "You need at least MacOS 13.3 Ventura for Accelerate with BLAS/LAPACK v3.9.1!") - endif () - - message(STATUS "The BLAS/LAPACK version provided by the Accelerate framework should be: ${ACCELERATE_NEW_LAPACK_VERSION}") -else () - message(FATAL_ERROR "Accelerate is only available on MacOS!") -endif () - -if (NOT DEFINED CMAKE_OSX_SYSROOT OR CMAKE_OSX_SYSROOT STREQUAL "") - message(FATAL_ERROR "Please provide the CMAKE_OSX_SYSROOT location! See <https://cmake.org/cmake/help/latest/variable/CMAKE_OSX_SYSROOT.html>") -endif () - -string(REGEX MATCH [=[SDKs/MacOSX(.*)\.sdk$]=] _REGEX_DUMMY "${CMAKE_OSX_SYSROOT}") -set(MAIN_MACOS_SDK_VERSION "${CMAKE_MATCH_1}") - -# The text-based .dylib stubs describing the Accelerate framework are provided -# by the MacOS SDK, which has to match the MacOS system. Otherwise, there might -# be newer symbols in the text-based .dylib stubs that are not provided by the -# Accelerate binary, leading to linking errors. -if (NOT DEFINED VALID_MACOS_SDK_VERSIONS) - # MacOS system supports the latest iteration of the new Accelerate - # BLAS/LAPACK. Use the default SDK, if it's recent enough. - if (MAIN_MACOS_SDK_VERSION VERSION_LESS MINIMUM_MACOS_SDK_VERSION) - message(STATUS "The MacOS SDK is: ${CMAKE_OSX_SYSROOT}") - message(STATUS "The minimum compatible MacOS SDK version is: ${MINIMUM_MACOS_SDK_VERSION}") - message(FATAL_ERROR "Please install a more recent XCode for a compatible MacOS SDK.") - endif () -elseif (NOT MAIN_MACOS_SDK_VERSION IN_LIST VALID_MACOS_SDK_VERSIONS) - # MacOS system supports one of the past iterations of the new Accelerate - # BLAS/LAPACK. If the latest possible XCode is installed on this older - # system, there might be a mismatch between the SDK and the system binary. - # Find a matching SDK from the Command Line Tools instead (which must be - # manually installed). - foreach (_MACOS_SDK_VERSION IN LISTS VALID_MACOS_SDK_VERSIONS) - find_path(VALID_MACOS_SDK NAMES "MacOSX${_MACOS_SDK_VERSION}.sdk" HINTS "/Library/Developer/CommandLineTools/SDKs" NO_CACHE) - - if (VALID_MACOS_SDK) - set(VALID_MACOS_SDK "${VALID_MACOS_SDK}/MacOSX${_MACOS_SDK_VERSION}.sdk") - break () - endif () - endforeach () - - if (VALID_MACOS_SDK) - # Override CMAKE_OSX_SYSROOT with a local variable. - set(CMAKE_OSX_SYSROOT "${VALID_MACOS_SDK}") - else () - message(STATUS "The MacOS SDK is: ${CMAKE_OSX_SYSROOT}") - message(STATUS "The compatible MacOS SDK versions are: ${VALID_MACOS_SDK_VERSIONS}") - message(FATAL_ERROR "Couldn't find a compatible MacOS SDK. Please install compatible XCode Command Line Tools.") - endif () -endif () +FetchContent_Declare( + AccelerateLAPACK + GIT_REPOSITORY "https://github.com/lepus2589/accelerate-lapack.git" + GIT_TAG v1.4.0 + SYSTEM + FIND_PACKAGE_ARGS 1.4.0 CONFIG NAMES AccelerateLAPACK +) -message(STATUS "The MacOS SDK is: ${CMAKE_OSX_SYSROOT}") +set(AccelerateLAPACK_INCLUDE_PACKAGING FALSE) -set(ENV{CMAKE_FRAMEWORK_PATH} "${CMAKE_OSX_SYSROOT}/System/Library/Frameworks") - -set(BLA_VENDOR "Apple") -find_package(LAPACK MODULE) - -unset(ENV{CMAKE_FRAMEWORK_PATH}) - -if (NOT LAPACK_FOUND OR NOT LAPACK_Accelerate_LIBRARY) - message(FATAL_ERROR "Couldn't find Accelerate framework in MacOS SDK!") -endif () +FetchContent_MakeAvailable(AccelerateLAPACK) add_subdirectory(src) diff --git a/README.md b/README.md index 745dd0c..c0f207c 100644 --- a/README.md +++ b/README.md @@ -25,119 +25,24 @@ SOFTWARE. # Accelerate LAPACKE # -Since MacOS 13.3 Ventura, Apple's Accelerate framework comes with a new -[BLAS/LAPACK interface][accelerate-docs] compatible with [Reference LAPACK -v3.9.1][lapack-v3.9.1] in addition to the quite outdated [Reference LAPACK -v3.2.1][lapack-v3.2.1]. It also provides an ILP64 interface. On Apple Silicon -M-processors, it utilises the [proprietary AMX co-processor][apple-amx], which -makes it especially interesting. Unfortunately, it comes without the LAPACKE -C-interface library. - -**Update**: With the release of MacOS 15.0 Sequoia, Apple updated the Accelerate -framework to be compatible with [Reference LAPACK v3.11.0][lapack-v3.11.0]. -Unfortunately, there is no mention of it in the [MacOS 15.0 Sequoia Release -Notes][macos15-release-notes], but the note in the [Accelerate BLAS -docs][accelerate-docs] has been updated accordingly. - -These new interfaces are hidden behind the preprocessor defines -`ACCELERATE_NEW_LAPACK` and `ACCELERATE_LAPACK_ILP64` and they only work, if you -include the Accelerate C/C++ headers. +Unfortunately, Apple's Accelerate framework doesn't provide the LAPACKE +C-interface library. Since MacOS 13.3 Ventura, the [BLAS/LAPACK +interface][accelerate-docs] provided by Accelerate is recent enough to support +the LAPACKE library. + +This project uses [Accelerate LAPACK][accelerate-lapack] to build the LAPACKE +library against the Accelerate NEWLAPACK interface. More info on BLAS/LAPACK in +Accelerate can be found there, too. [accelerate-docs]: https://developer.apple.com/documentation/accelerate/blas-library -[apple-amx]: https://github.com/corsix/amx -[lapack-v3.2.1]: https://netlib.org/lapack/#_lapack_version_3_2_1 -[lapack-v3.9.1]: https://github.com/Reference-LAPACK/lapack/releases/tag/v3.9.1 -[lapack-v3.11.0]: https://github.com/Reference-LAPACK/lapack/releases/tag/v3.11.0 -[macos15-release-notes]: https://developer.apple.com/documentation/macos-release-notes/macos-15-release-notes - -- [The Problem](#the-problem) -- [The Solution](#the-solution) - - [The alias files (to use in other projects)](#the-alias-files-to-use-in-other-projects) +[accelerate-lapack]: https://github.com/lepus2589/accelerate-lapack + - [How to compile](#how-to-compile) - [Prerequisites](#prerequisites) - [Workflow with CMake](#workflow-with-cmake) - [CMake v4 compatibility](#cmake-v4-compatibility) - [Using LAPACKE in another project](#using-lapacke-in-another-project) -## The Problem ## - -But what if you have to or just want to link against the Accelerate framework -without including the C/C++ headers, e. g. when compiling Fortran code or a -third-party project, that uses the standard BLAS/LAPACK API? Well, you're out of -luck. The binary symbols for the new LAPACK version exported by the Accelerate -framework do not adhere to the BLAS/LAPACK API. Thus, they cannot be resolved by -the linker, when linking any program or library that uses the standard -BLAS/LAPACK API. - -Take, for example, the `dgeqrt` LAPACK routine, that is used by the [Reference -LAPACK CMake script][dgeqrt-ref] to determine, if the user provided LAPACK -version is recent enough. When the Fortran test executable is compiled, the -`gfortran` compiler creates a function call with the binary symbol `_dgeqrt_`, -which results in the following error when linking to Accelerate (`ld` is the -Apple system linker, here): - -```plaintext -ld: Undefined symbols: - _dgeqrt_, referenced from: - _MAIN__ in testFortranCompiler.f.o -``` - -The reason for this is, that the binary symbol provided by the Accelerate -framework is called `_dgeqrt$NEWLAPACK`, literally. This is a symbol, that no -Fortran compiler will probably ever emit voluntarily. So, what to do? - -[dgeqrt-ref]: https://github.com/Reference-LAPACK/lapack/blob/v3.11.0/CMakeLists.txt#L365-L366 - -## The Solution ## - -According to its `man` page, the Apple system linker `ld` provides the options -`-alias` and `-alias_list`, which let you create alias names for existing binary -symbols. Calling the linker with `-alias '_dgeqrt$NEWLAPACK' _dgeqrt_` makes the -linking of the above Fortran test executable finish successfully. - -Because BLAS and LAPACK contain quite a number of subroutines and functions, -this CMake scipt uses the `-alias_list` option, which loads a plaintext file -listing all the aliases. - -To generate the full alias list for the Accelerate NEWLAPACK interface, it -parses the symbols listed in the BLAS and LAPACK text-based `.dylib` stubs. For -every symbol that ends in `$NEWLAPACK` (or `$NEWLAPACK$ILP64` for the ILP64 -interface), an alias is added to the alias file. - -The additional linker options get attached to new interface library targets, -which are injected into the Reference LAPACK configure process using the -`CMAKE_REQUIRED_LIBRARIES` variable for the Fortran test compiles and the -`LINK_LIBRARIES` target property for the shared LAPACKE library. - -This enables the compilation of the LAPACKE C-interface library for the -Accelerate framework (e. g. to be used in the `Eigen3` library). Analyzing -the resulting `.dylib` with `otool`, you can see: - -```shell -$ otool -L ./build/32/_deps/reference-lapack-build/lib/liblapacke.dylib -./build/32/_deps/reference-lapack-build/lib/liblapacke.dylib: - @rpath/liblapacke.3.dylib (compatibility version 3.0.0, current version 3.11.0) - /System/Library/Frameworks/Accelerate.framework/Versions/A/Accelerate (compatibility version 1.0.0, current version 4.0.0) - /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1351.0.0) -``` - -Only the Accelerate framework and the System library are linked into the -`.dylib`. No `libgfortran` or other libraries are needed. - -### The alias files (to use in other projects) ### - -After the CMake configuration, the alias files can be found in `./build/<32|64>/src`: - -```plaintext -./build/<32|64>/src -├── new-blas-ilp64.alias -├── new-blas.alias -├── new-lapack-ilp64.alias -└── new-lapack.alias -``` - -These files can be used to link other projects against Accelerate, too, of course! - ## How to compile ## It is recommended to use the Apple System C Compiler `/usr/bin/cc`. You can also @@ -181,6 +86,19 @@ $ cmake --workflow --preset user-accelerate-lapacke32 I wouldn't recommend installing to `/usr/local` (used by Homebrew on Intel Macs) or `/opt/local` (used by MacPorts). +Analyzing the resulting `.dylib` with `otool`, you can see: + +```shell +$ otool -L ./build/32/_deps/reference-lapack-build/lib/liblapacke.dylib +./build/32/_deps/reference-lapack-build/lib/liblapacke.dylib: + @rpath/liblapacke.3.dylib (compatibility version 3.0.0, current version 3.11.0) + /System/Library/Frameworks/Accelerate.framework/Versions/A/Accelerate (compatibility version 1.0.0, current version 4.0.0) + /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1351.0.0) +``` + +Only the Accelerate framework and the System library are linked into the +`.dylib`. No `libgfortran` or other libraries are needed. + #### CMake v4 compatibility #### To build the project with CMake v4 or higher, you must explicitly provide the @@ -198,6 +116,12 @@ the CMake package in the other project's `CMakeLists.txt` file: find_package(LAPACKE CONFIG) ``` +or + +```cmake +find_package(LAPACKE64 CONFIG) +``` + and providing the above install location via the `CMAKE_PREFIX_PATH` variable from the command line: @@ -205,5 +129,5 @@ from the command line: $ cmake -S . -B ./build -D "CMAKE_PREFIX_PATH=~/.local" ``` -This makes the imported `lapacke` shared library target available in the other -project's `CMakeLists.txt`. +This makes the imported `lapacke`/`lapacke64` shared library target available in +the other project's `CMakeLists.txt`. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1b126d6..0791892 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -23,114 +23,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] -include("../cmake/ParseTBDMacros.cmake") - -# Paths to text-based .dylib stubs -set(BLAS_TBD_PATH "${LAPACK_Accelerate_LIBRARY}/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.tbd") -set(LAPACK_TBD_PATH "${LAPACK_Accelerate_LIBRARY}/Versions/A/Frameworks/vecLib.framework/Versions/A/libLAPACK.tbd") - -parse_tbd_symbols(BLAS "${BLAS_TBD_PATH}") -filter_new_lapack_symbols(BLAS) -filter_new_lapack_ilp64_symbols(BLAS) - -parse_tbd_symbols(LAPACK "${LAPACK_TBD_PATH}") -filter_new_lapack_symbols(LAPACK) -filter_new_lapack_ilp64_symbols(LAPACK) - -build_aliases(BLAS_NEWLAPACK_SYMBOLS) -build_aliases(BLAS_NEWLAPACK_ILP64_SYMBOLS) -build_aliases(LAPACK_NEWLAPACK_SYMBOLS) -build_aliases(LAPACK_NEWLAPACK_ILP64_SYMBOLS) - -configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/new-blas.alias.in" - "${CMAKE_CURRENT_BINARY_DIR}/new-blas.alias" - @ONLY -) - -configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/new-lapack.alias.in" - "${CMAKE_CURRENT_BINARY_DIR}/new-lapack.alias" - @ONLY -) - -configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/new-blas-ilp64.alias.in" - "${CMAKE_CURRENT_BINARY_DIR}/new-blas-ilp64.alias" - @ONLY -) - -configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/new-lapack-ilp64.alias.in" - "${CMAKE_CURRENT_BINARY_DIR}/new-lapack-ilp64.alias" - @ONLY -) - -add_library(NEW_BLAS INTERFACE IMPORTED GLOBAL) -target_link_libraries(NEW_BLAS INTERFACE ${BLAS_LIBRARIES}) - -add_library(NEW_LAPACK INTERFACE IMPORTED GLOBAL) -target_link_libraries(NEW_LAPACK INTERFACE ${LAPACK_LIBRARIES} NEW_BLAS) - -# Add the $NEWLAPACK symbols to the linker flags of the NEW_BLAS and NEW_LAPACK -# targets -target_link_options( - NEW_BLAS - INTERFACE - ${BLAS_LINKER_FLAGS} - "LINKER:-alias_list,${CMAKE_CURRENT_BINARY_DIR}/new-blas.alias" -) -target_link_options( - NEW_LAPACK - INTERFACE - ${LAPACK_LINKER_FLAGS} - "LINKER:-alias_list,${CMAKE_CURRENT_BINARY_DIR}/new-lapack.alias" -) - -add_library(NEW_BLAS64 INTERFACE IMPORTED GLOBAL) -target_link_libraries(NEW_BLAS64 INTERFACE ${BLAS_LIBRARIES}) - -add_library(NEW_LAPACK64 INTERFACE IMPORTED GLOBAL) -target_link_libraries(NEW_LAPACK64 INTERFACE ${LAPACK_LIBRARIES} NEW_BLAS64) - -# Add the $NEWLAPACK$ILP64 symbols to the linker flags of the NEW_BLAS64 and -# NEW_LAPACK64 targets -target_link_options( - NEW_BLAS64 - INTERFACE - ${BLAS_LINKER_FLAGS} - "LINKER:-alias_list,${CMAKE_CURRENT_BINARY_DIR}/new-blas-ilp64.alias" -) -target_link_options( - NEW_LAPACK64 - INTERFACE - ${LAPACK_LINKER_FLAGS} - "LINKER:-alias_list,${CMAKE_CURRENT_BINARY_DIR}/new-lapack-ilp64.alias" -) - -if (BUILD_INDEX64) - # Add the $NEWLAPACK$ILP64 symbols to the BLAS and LAPACK libraries. - set(BLAS_LIBRARIES "NEW_BLAS64") - set(LAPACK_LIBRARIES "NEW_LAPACK64") - set(CHECK_LAPACK_VERSION_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/lapack64_version.c") -else () - # Add the $NEWLAPACK symbols to the linker flags of the lapacke target - set(BLAS_LIBRARIES "NEW_BLAS") - set(LAPACK_LIBRARIES "NEW_LAPACK") - set(CHECK_LAPACK_VERSION_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/lapack_version.c") -endif () - -set_target_properties(BLAS::BLAS PROPERTIES INTERFACE_LINK_LIBRARIES "${BLAS_LIBRARIES}") -set_target_properties(LAPACK::LAPACK PROPERTIES INTERFACE_LINK_LIBRARIES "${LAPACK_LIBRARIES}") - -try_run( - HAS_RUN_ILAVER HAS_ILAVER - SOURCES ${CHECK_LAPACK_VERSION_SOURCE} - LINK_LIBRARIES ${LAPACK_LIBRARIES} - RUN_OUTPUT_VARIABLE ACCELERATE_LAPACK_ILAVER_VERSION -) - -message(STATUS "The BLAS/LAPACK version provided by the Accelerate framework is confirmed to be: ${ACCELERATE_LAPACK_ILAVER_VERSION}") +get_target_property(ACCELERATE_NEW_LAPACK_VERSION LAPACK::NEW_LAPACK VERSION) FetchContent_Declare( reference-lapack @@ -150,14 +43,14 @@ if (CMAKE_VERSION VERSION_GREATER_EQUAL 4) set(ENV{CMAKE_POLICY_VERSION_MINIMUM} 3.5) endif () -set(BLAS_LIBRARIES_BACKUP "${BLAS_LIBRARIES}") -set(LAPACK_LIBRARIES_BACKUP "${LAPACK_LIBRARIES}") +set(BLAS_LIBRARIES_BACKUP "BLAS::BLAS") +set(LAPACK_LIBRARIES_BACKUP "LAPACK::LAPACK") # Set the BLAS and LAPACK libraries to the new 32bit targets. This helps the # Reference LAPACK CMake script to pass the CheckFortranFunctionExists tests for # `dgemm` and `dgeqrt`. -set(BLAS_LIBRARIES "NEW_BLAS") -set(LAPACK_LIBRARIES "NEW_LAPACK") +set(BLAS_LIBRARIES "BLAS::NEW_BLAS_IMPORTED") +set(LAPACK_LIBRARIES "LAPACK::NEW_LAPACK_IMPORTED") FetchContent_MakeAvailable(reference-lapack) @@ -171,8 +64,14 @@ endif () if (BUILD_INDEX64) # Replace the LINK_LIBRARIES of the lapacke64 target with the ILP64 ones. - # The lapacke64 target was created linking to the NEW_LAPACK target, which - # we had to add to satisfy the Check... tests, that the Reference LAPACK - # build script does. + # The lapacke64 target was created linking to the NEW_LAPACK_IMPORTED + # target, which we had to add to satisfy the Check... tests, that the + # Reference LAPACK build script does. set_target_properties(lapacke64 PROPERTIES LINK_LIBRARIES "${LAPACK_LIBRARIES}") +else () + # Replace the LINK_LIBRARIES of the lapacke target with the NEWLAPACK ones. + # The lapacke target was created linking to the NEW_LAPACK_IMPORTED target, + # which we had to add to satisfy the Check... tests, that the Reference + # LAPACK build script does. + set_target_properties(lapacke PROPERTIES LINK_LIBRARIES "${LAPACK_LIBRARIES}") endif () -- GitLab