From 8de42eaf0fc5140932563950bbe9ba4abacf4912 Mon Sep 17 00:00:00 2001 From: Maria Kotsifakou Date: Mon, 9 Dec 2024 11:04:47 -0600 Subject: [PATCH 1/3] New GC strategy. (#1175) This PR adds a new GCStrategy, that treats any pointer with address space other than 0 as a pointer to managed memory. We extent class GCStrategy in llvm/IR/GCStrategy.h, and register the new GCStrategy. --- bin/llvm-kompile | 6 ++++ bin/llvm-kompile-clang | 8 +++++ include/kllvm/codegen/GCStrategy.h | 37 ++++++++++++++++++++ include/kllvm/codegen/Options.h | 1 + lib/codegen/CreateTerm.cpp | 4 +++ lib/codegen/Options.cpp | 4 +++ lib/passes/CMakeLists.txt | 2 ++ lib/passes/GCStrategy.cpp | 52 +++++++++++++++++++++++++++++ test/defn/imp.kore | 2 ++ test/lit.cfg.py | 7 ++++ tools/llvm-kompile-codegen/main.cpp | 3 ++ 11 files changed, 126 insertions(+) create mode 100644 include/kllvm/codegen/GCStrategy.h create mode 100644 lib/passes/GCStrategy.cpp diff --git a/bin/llvm-kompile b/bin/llvm-kompile index 77ba160b5..ffe57b58f 100755 --- a/bin/llvm-kompile +++ b/bin/llvm-kompile @@ -42,6 +42,7 @@ Options: (immutable) that are enabled by default. --hidden-visibility Set the visibility of all global symbols in generated code to "hidden" + --use-gcstrategy Use GC strategy defined for the LLVM backend. --profile-matching Instrument interpeter to emit a profile of time spent in top-level rule matching on stderr. --verify-ir Verify result of IR generation. @@ -197,6 +198,11 @@ while [[ $# -gt 0 ]]; do kompile_clang_flags+=("--hidden-visibility") shift ;; + --use-gcstrategy) + codegen_flags+=("--use-gcstrategy") + kompile_clang_flags+=("--use-gcstrategy") + shift + ;; --profile-matching) codegen_flags+=("--profile-matching") codegen_verify_flags+=("--profile-matching") diff --git a/bin/llvm-kompile-clang b/bin/llvm-kompile-clang index f35e6da9f..681d4378e 100644 --- a/bin/llvm-kompile-clang +++ b/bin/llvm-kompile-clang @@ -15,6 +15,7 @@ flags=() llc_flags=() llc_opt_flags="-O0" visibility_hidden=false +use_gcstrategy=false link=true export verbose=false export profile=false @@ -101,6 +102,10 @@ while [[ $# -gt 0 ]]; do visibility_hidden=true shift ;; + --use-gcstrategy) + use_gcstrategy=true + shift + ;; *) ;; esac @@ -188,6 +193,9 @@ if [ "$main" != "python_ast" ]; then run @OPT@ "$modopt" -load-pass-plugin "$passes" -set-visibility-hidden -o "$modhidden" modopt="$modhidden" fi + if $use_gcstrategy; then + llc_flags+=("-load="$passes"") + fi run @LLC@ \ "$modopt" -mtriple=@BACKEND_TARGET_TRIPLE@ \ -filetype=obj "$llc_opt_flags" "${llc_flags[@]}" -o "$modasm" diff --git a/include/kllvm/codegen/GCStrategy.h b/include/kllvm/codegen/GCStrategy.h new file mode 100644 index 000000000..aa52872f5 --- /dev/null +++ b/include/kllvm/codegen/GCStrategy.h @@ -0,0 +1,37 @@ +//===- Extend GCStrategy of llvm/CodeGen/GCStrategy.h ---------------------===// +// +// We extend the base GCStrategy as follows: +// - use gc.safepoints instead of (default) gc.roots. +// - specify that the RewriteStatepointsForGC pass should rewrite the calls of +// this function. +// - pointers with address space != 0 are pointing to GC-managed memory. +//===----------------------------------------------------------------------===// + +// NOLINTBEGIN + +#ifndef LLVM_BACKEND_GC_STRATEGY_H +#define LLVM_BACKEND_GC_STRATEGY_H + +#include "llvm/IR/GCStrategy.h" +#include "llvm/IR/Type.h" + +namespace kllvm { + +/// The GCStrategy for the LLVM Backend +class LLVMBackendGCStrategy : public llvm::GCStrategy { +public: + LLVMBackendGCStrategy(); + + // Override +#if LLVM_VERSION_MAJOR == 15 + llvm::Optional isGCManagedPointer(llvm::Type const *Ty) const override; +#else + std::optional isGCManagedPointer(llvm::Type const *Ty) const override; +#endif +}; + +} // namespace kllvm + +#endif // LLVM_BACKEND_GC_STRATEGY_H + +// NOLINTEND diff --git a/include/kllvm/codegen/Options.h b/include/kllvm/codegen/Options.h index fda9f8763..a156500a5 100644 --- a/include/kllvm/codegen/Options.h +++ b/include/kllvm/codegen/Options.h @@ -10,6 +10,7 @@ extern llvm::cl::opt no_optimize; extern llvm::cl::opt emit_object; extern llvm::cl::opt binary_ir; extern llvm::cl::opt force_binary; +extern llvm::cl::opt use_gcstrategy; extern llvm::cl::opt proof_hint_instrumentation; extern llvm::cl::opt proof_hint_instrumentation_slow; extern llvm::cl::opt keep_frame_pointer; diff --git a/lib/codegen/CreateTerm.cpp b/lib/codegen/CreateTerm.cpp index 3bb71c900..ac1119c9c 100644 --- a/lib/codegen/CreateTerm.cpp +++ b/lib/codegen/CreateTerm.cpp @@ -1,6 +1,7 @@ #include "kllvm/codegen/CreateTerm.h" #include "kllvm/codegen/CreateStaticTerm.h" #include "kllvm/codegen/Debug.h" +#include "kllvm/codegen/Options.h" #include "kllvm/codegen/ProofEvent.h" #include "kllvm/codegen/Util.h" @@ -1224,6 +1225,9 @@ bool make_function( = llvm::FunctionType::get(return_type, param_types, false); llvm::Function *apply_rule = get_or_insert_function(module, name, func_type); apply_rule->setLinkage(llvm::GlobalValue::InternalLinkage); + if (use_gcstrategy) { + apply_rule->setGC("gcs-llvm-backend"); + } init_debug_axiom(axiom->attributes()); std::string debug_name = name; if (axiom->attributes().contains(attribute_set::key::Label)) { diff --git a/lib/codegen/Options.cpp b/lib/codegen/Options.cpp index b345f879d..106c7fca1 100644 --- a/lib/codegen/Options.cpp +++ b/lib/codegen/Options.cpp @@ -48,6 +48,10 @@ cl::opt force_binary( "f", cl::desc("Force binary bitcode output to stdout"), cl::Hidden, cl::cat(codegen_lib_cat)); +cl::opt use_gcstrategy( + "use-gcstrategy", cl::desc("Use GC strategy defined for the LLVM backend."), + cl::Hidden, cl::init(false), cl::cat(codegen_lib_cat)); + namespace kllvm { void validate_codegen_args(bool is_tty) { diff --git a/lib/passes/CMakeLists.txt b/lib/passes/CMakeLists.txt index be5bc572e..e6e0b68ca 100644 --- a/lib/passes/CMakeLists.txt +++ b/lib/passes/CMakeLists.txt @@ -2,6 +2,7 @@ add_library(KLLVMPassInternal SetVisibilityHidden.cpp RemoveDeadKFunctions.cpp MustTailDeadArgElimination.cpp + GCStrategy.cpp PluginInfo.cpp ) @@ -9,6 +10,7 @@ add_library(KLLVMPass MODULE SetVisibilityHidden.cpp RemoveDeadKFunctions.cpp MustTailDeadArgElimination.cpp + GCStrategy.cpp PluginInfo.cpp ) diff --git a/lib/passes/GCStrategy.cpp b/lib/passes/GCStrategy.cpp new file mode 100644 index 000000000..32609d8dc --- /dev/null +++ b/lib/passes/GCStrategy.cpp @@ -0,0 +1,52 @@ +//===- Extend GCStrategy of llvm/CodeGen/GCStrategy.h ---------------------===// +// +// We extend the base GCStrategy as follows: +// - use gc.safepoints instead of (default) gc.roots. +// - specify that the RewriteStatepointsForGC pass should rewrite the calls of +// this function. +// - pointers with address space != 0 are pointing to GC-managed memory. +//===----------------------------------------------------------------------===// + +// NOLINTBEGIN + +#include "kllvm/codegen/GCStrategy.h" + +#include "llvm/CodeGen/GCMetadata.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/Support/Compiler.h" + +using namespace llvm; +using namespace kllvm; + +LLVMBackendGCStrategy::LLVMBackendGCStrategy() { + UseStatepoints = true; // Use gc.statepoints +#if LLVM_VERSION_MAJOR != 15 + UseRS4GC = true; // Rewrite the calls of a function that has this GCStrategy +#endif +} + +// Override +#if LLVM_VERSION_MAJOR == 15 +llvm::Optional +LLVMBackendGCStrategy::isGCManagedPointer(Type const *Ty) const { +#else +std::optional +LLVMBackendGCStrategy::isGCManagedPointer(Type const *Ty) const { +#endif + // Return false for any non-pointer type + if (!Ty->isPointerTy()) { + return false; + } + // Any pointer with address space != 0 is to managed memory. + PointerType const *PTy = dyn_cast(Ty); + if (PTy->getAddressSpace()) { + return true; + } + return false; +} + +// Add LLVMBackendGCStrategy to the global GCRegistry +static GCRegistry::Add + X("gcs-llvm-backend", "GC Strategy for the LLVM Backend"); + +// NOLINTEND diff --git a/test/defn/imp.kore b/test/defn/imp.kore index 4ac67067f..bccd8575e 100644 --- a/test/defn/imp.kore +++ b/test/defn/imp.kore @@ -1,6 +1,8 @@ // RUN: %interpreter // RUN: %check-grep // RUN: %check-statistics +// RUN: %gcs-interpreter +// RUN: %check-grep // RUN: %proof-interpreter // RUN: %check-proof-out [topCellInitializer{}(LblinitGeneratedTopCell{}()), org'Stop'kframework'Stop'attributes'Stop'Source{}("Source(/home/robertorosmaninho/rv/k/llvm-backend/src/main/native/llvm-backend/test/defn/k-files/imp.md)")] diff --git a/test/lit.cfg.py b/test/lit.cfg.py index 28f223eeb..cca48be9c 100644 --- a/test/lit.cfg.py +++ b/test/lit.cfg.py @@ -98,6 +98,13 @@ def exclude_x86_and_llvm18(s): exit 1 fi ''')), + ('%gcs-interpreter', one_line(''' + output=$(%kompile %s main --use-gcstrategy -o %t.interpreter 2>&1) + if [[ -n "$output" ]]; then + echo "llvm-kompile error or warning: $output" + exit 1 + fi + ''')), ('%proof-interpreter', one_line(''' output=$(%kompile %s main --proof-hint-instrumentation -o %t.interpreter 2>&1) if [[ -n "$output" ]]; then diff --git a/tools/llvm-kompile-codegen/main.cpp b/tools/llvm-kompile-codegen/main.cpp index 9cc5c5b0c..f98f7a9c9 100644 --- a/tools/llvm-kompile-codegen/main.cpp +++ b/tools/llvm-kompile-codegen/main.cpp @@ -1,3 +1,4 @@ +#include "kllvm/codegen/GCStrategy.h" #include #include #include @@ -147,6 +148,8 @@ void emit_metadata(llvm::Module &mod) { // NOLINTNEXTLINE(*-cognitive-complexity) int main(int argc, char **argv) { + // NOLINTNEXTLINE(*-identifier-naming) + LLVMBackendGCStrategy _gcs; // Unused. This is needed to ensure linking. initialize_llvm(); cl::HideUnrelatedOptions({&codegen_tool_cat, &codegen_lib_cat}); From eb79b84cb922c6e64e843bc51781eebe52b7b173 Mon Sep 17 00:00:00 2001 From: Steven Eker Date: Mon, 9 Dec 2024 12:10:57 -0800 Subject: [PATCH 2/3] Further allocator refactoring (#1173) Dead code eliminated: youngspace_size() arena_size() union memory_block_header and associated functionality replaced. The semispace id is now stored in the last byte of the hyperblock which simplifies address computations. --- include/runtime/alloc.h | 1 - include/runtime/arena.h | 71 +++++++++++++++++------------------------ runtime/alloc/arena.cpp | 17 ++++------ runtime/lto/alloc.cpp | 4 --- 4 files changed, 35 insertions(+), 58 deletions(-) diff --git a/include/runtime/alloc.h b/include/runtime/alloc.h index d94ed0c5b..9208e5ee8 100644 --- a/include/runtime/alloc.h +++ b/include/runtime/alloc.h @@ -15,7 +15,6 @@ extern "C" { char youngspace_collection_id(void); char oldspace_collection_id(void); -size_t youngspace_size(void); // allocates exactly requested bytes into the young generation void *kore_alloc(size_t requested); diff --git a/include/runtime/arena.h b/include/runtime/arena.h index 3f71445e4..bac3968a3 100644 --- a/include/runtime/arena.h +++ b/include/runtime/arena.h @@ -27,32 +27,21 @@ class arena { void *kore_arena_alloc(size_t requested); // Returns the address of the first byte that belongs in the given arena. - // Returns 0 if nothing has been allocated ever in that arena. - char *arena_start_ptr() const { - return current_addr_ptr ? current_addr_ptr + sizeof(memory_block_header) - : nullptr; - } + // Returns nullptr if nothing has been allocated ever in that arena. + char *arena_start_ptr() const { return current_addr_ptr; } // Returns a pointer to a location holding the address of last allocated // byte in the given arena plus 1. - // This address is 0 if nothing has been allocated ever in that arena. + // This address is nullptr if nothing has been allocated ever in that arena. char **arena_end_ptr() { return &allocation_ptr; } - // return the total number of allocatable bytes currently in the arena in its - // active semispace. - size_t arena_size() const { - update_num_blocks(); - return BLOCK_SIZE * std::max(num_blocks, num_collection_blocks); - } - // Clears the current allocation space by setting its start back to its first // block. It is used during garbage collection to effectively collect all of the - // arena. + // arena. Resets the tripwire. void arena_clear(); - // Resizes the last allocation as long as the resize does not require a new - // block allocation. - // Returns the address of the byte following the last newlly allocated byte. + // Resizes the last allocation. + // Returns the address of the byte following the last newly allocated byte. void *arena_resize_last_alloc(ssize_t increase) { return (allocation_ptr += increase); } @@ -71,10 +60,8 @@ class arena { void arena_swap_and_clear(); // Given two pointers to objects allocated in the same arena, return the number - // of bytes they are separated by within the virtual block of memory represented - // by the blocks of that arena. This difference will include blocks containing - // sentinel bytes. Undefined behavior will result if the pointers belong to - // different arenas. + // of bytes they are apart. Undefined behavior will result if the pointers + // don't belong to the same arena static ssize_t ptr_diff(char *ptr1, char *ptr2) { return ptr1 - ptr2; } // Given a starting pointer to an address allocated in an arena and a size in @@ -84,11 +71,11 @@ class arena { // 1st argument: the starting pointer // 2nd argument: the size in bytes to add to the starting pointer // 3rd argument: the address of last allocated byte in the arena plus 1 - // Return value: the address allocated in the arena after size bytes from the - // starting pointer, or 0 if this is equal to the 3rd argument. + // Return value: starting pointer + size unless this points to unallocated space + // in which case nullptr is returned static char *move_ptr(char *ptr, size_t size, char const *arena_end_ptr) { char *next_ptr = ptr + size; - return (next_ptr == arena_end_ptr) ? 0 : next_ptr; + return (next_ptr == arena_end_ptr) ? nullptr : next_ptr; } // Returns the ID of the semispace where the given address was allocated. @@ -97,15 +84,6 @@ class arena { static char get_arena_semispace_id_of_object(void *ptr); private: - union memory_block_header { - // - // Currently the header just holds the semispace id. But we need it to be a - // multiple of sizeof(char*) for alignment purposes so we add a dummy char*. - // - char semispace; - char *alignment_dummy; - }; - // // We update the number of 1MB blocks actually written to, only when we need this value, // or before a garbage collection rather than trying to determine when we write to a fresh block. @@ -121,13 +99,6 @@ class arena { } void initialize_semispace(); - - static memory_block_header *mem_block_header(void *ptr) { - uintptr_t address = reinterpret_cast(ptr); - return reinterpret_cast( - (address - 1) & ~(HYPERBLOCK_SIZE - 1)); - } - // // Current semispace where allocations are being made. // @@ -146,6 +117,19 @@ class arena { = 0; // notional number of BLOCK_SIZE blocks in collection semispace }; +inline char arena::get_arena_semispace_id_of_object(void *ptr) { + // + // We don't have to deal with the "1 past the end of block" case because + // a valid pointer will always point into our hyperblock - we will never return + // an allocation anywhere near the end of our hyperblock. + // + // Set the low bits to 1 to get the address of the last byte in the hyperblock. + // + uintptr_t end_address + = reinterpret_cast(ptr) | (HYPERBLOCK_SIZE - 1); + return *reinterpret_cast(end_address); +} + // Macro to define a new arena with the given ID. Supports IDs ranging from 0 to // 127. #define REGISTER_ARENA(name, id) static thread_local arena name(id) @@ -169,8 +153,11 @@ inline void *arena::kore_arena_alloc(size_t requested) { // collect when allowed. // time_for_collection = true; - tripwire = current_addr_ptr - + HYPERBLOCK_SIZE; // won't trigger again until arena swap + // + // We move the tripwire to 1 past the end of our hyperblock so that we have + // a well defined comparison that will always be false until the next arena swap. + // + tripwire = current_addr_ptr + HYPERBLOCK_SIZE; } void *result = allocation_ptr; allocation_ptr += requested; diff --git a/runtime/alloc/arena.cpp b/runtime/alloc/arena.cpp index 9fbde8bf9..c657dada4 100644 --- a/runtime/alloc/arena.cpp +++ b/runtime/alloc/arena.cpp @@ -11,11 +11,6 @@ extern size_t const VAR_BLOCK_SIZE = BLOCK_SIZE; -__attribute__((always_inline)) char -arena::get_arena_semispace_id_of_object(void *ptr) { - return mem_block_header(ptr)->semispace; -} - #ifdef __MACH__ // // thread_local disabled for Apple @@ -46,19 +41,19 @@ void arena::initialize_semispace() { } // // We allocated 2 * HYPERBLOCK_SIZE worth of address space but we're only going to use 1, aligned on a - // HYPERBLOCK_SIZE boundry. This is so we can get the start of the hyperblock by masking any address within it. + // HYPERBLOCK_SIZE boundry. This is so we can get end of the hyperblock by setting the low bits of any + // address within the space to 1. // We don't worry about unused address space either side of our aligned address space because there will be no // memory mapped to it. // current_addr_ptr = reinterpret_cast( std::align(HYPERBLOCK_SIZE, HYPERBLOCK_SIZE, addr, request)); // - // We put a memory_block_header at the beginning so we can identify the semispace a pointer belongs to - // id by masking off the low bits to access this memory_block_header. + // We put a semispace id in the last byte of the hyperblock so we can identify which semispace an address + // belongs to by setting the low bits to 1 to access this id. // - auto *header = reinterpret_cast(current_addr_ptr); - header->semispace = allocation_semispace_id; - allocation_ptr = current_addr_ptr + sizeof(arena::memory_block_header); + current_addr_ptr[HYPERBLOCK_SIZE - 1] = allocation_semispace_id; + allocation_ptr = current_addr_ptr; // // We set the tripwire for this space so we get trigger a garbage collection when we pass BLOCK_SIZE of memory // allocated from this space. diff --git a/runtime/lto/alloc.cpp b/runtime/lto/alloc.cpp index 0cd79a3f8..2928ad62f 100644 --- a/runtime/lto/alloc.cpp +++ b/runtime/lto/alloc.cpp @@ -39,10 +39,6 @@ char oldspace_collection_id() { return oldspace.get_arena_collection_semispace_id(); } -size_t youngspace_size(void) { - return youngspace.arena_size(); -} - void kore_alloc_swap(bool swap_old) { youngspace.arena_swap_and_clear(); if (swap_old) { From c362cf06d3b1b5d7c029c8d32ba33b796bbd5931 Mon Sep 17 00:00:00 2001 From: Roberto Rosmaninho Date: Mon, 9 Dec 2024 17:44:03 -0300 Subject: [PATCH 3/3] Add relevant info for MacOS building process (#1176) --- INSTALL.md | 64 +++++++++++++++++++++++++++++++++++------ cmake/FixHomebrew.cmake | 10 +++++++ 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 0d0707e99..c933f0c19 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -30,7 +30,7 @@ python3 -m pip install pybind11 lit ``` ## macOS / Homebrew - +In order to install the dependencies on macOS, you must have Homebrew installed and on your `PATH`. ```shell brew update brew install \ @@ -40,7 +40,9 @@ brew install \ fmt \ git \ gmp \ + grep \ jemalloc \ + libffi libyaml \ llvm@15 \ maven \ @@ -48,11 +50,60 @@ brew install \ pkg-config \ python3 \ z3 +``` + +To ensure that the backend can use pybind11 correctly, we must create an virtual +environment and install the `pybind11` package: +```shell +python3 -m venv venv +source venv/bin/activate python3 -m pip install pybind11 lit ``` +Guarantee that you have the JDK installed and on your `PATH`: +```shell +export PATH="/opt/homebrew/opt/openjdk/bin:$PATH" +``` + +Some tests rely on GNU Grep options, which are not available on macOS by +default. To ensure that the tests run correctly, you add the path of +GNU Grep to your `PATH` in your shell profile: +```shell +export PATH=/opt/homebrew/Cellar/grep/3.11/libexec/gnubin/:$PATH +``` + # Building +## Environment Variables + +If you're building on macOS, type the following command or epermanently +add it your `env` (`.zshrc`, `.bashrc`, etc.), so that the Homebrew +installation of LLVM gets picked up correctly. We recommend adding it to +your shell profile. +```shell +export LLVM_DIR=$($(brew --prefix llvm@15)/bin/llvm-config --cmakedir) +``` + +If you don't usually use the `clang` from your Homebrew installation as +your default compiler, you can set the following CMake flg to use these +`clang` and `clang++`: +```shell +-DCMAKE_C_COMPILER="$(brew --prefix llvm@15)/bin/clang" \ +-DCMAKE_CXX_COMPILER="$(brew --prefix llvm@15)/bin/clang++" +``` +Once again, we recommend adding them and other llvm binaries to your +`PATH` in your shell profile: +```shell +export PATH="$(brew --prefix llvm@15)/bin:$PATH" +``` + +Some tests rely on GNU Grep options, which are not available on macOS by +default. To ensure that the tests run correctly, you can create an alias +for GNU Grep: +```shell +alias grep=ggrep +``` + Once the system dependencies have been installed, the backend can be built locally with: ```shell @@ -66,12 +117,6 @@ cmake .. \ make -j$(nproc) install ``` -If you're building on macOS, add the following option to your CMake invocation -so that the Homebrew installation of LLVM gets picked up correctly. -```shell --DLLVM_DIR=$($(brew --prefix llvm@15)/bin/llvm-config --cmakedir) -``` - Additionally, to build the pattern-matching compiler, run: ```shell cd matching @@ -91,7 +136,8 @@ To run the integration tests, run: ```shell lit test ``` -from the root source directory. +from the root source directory. You can use `-v` to see which test is being executed +and the output of failling tests. There is also a unit test suite for backend internals; Add the following option to your CMake invocation to enable it: @@ -113,7 +159,7 @@ and conform to best practices. ```shell # Ubuntu -apt install shellcheck clang-format-12 iwyu +apt install shellcheck clang-format-15 iwyu # macOS brew install shellcheck clang-format iwyu diff --git a/cmake/FixHomebrew.cmake b/cmake/FixHomebrew.cmake index 84a82a24d..37595a2c1 100644 --- a/cmake/FixHomebrew.cmake +++ b/cmake/FixHomebrew.cmake @@ -20,5 +20,15 @@ if(APPLE) include_directories(AFTER SYSTEM "${BREW_PREFIX}/include") link_directories(AFTER "${BREW_PREFIX}/lib") set(ENV{PKG_CONFIG_PATH} "${BREW_PREFIX}/opt/libffi/lib/pkgconfig") + + # Use LLD as the linker + # This is necessary as the default linker used by CMake on macOS is + # ld64, which currently has some incompatibilities with Homebrew and XCode15. + # See: https://github.com/orgs/Homebrew/discussions/4794#discussioncomment-7044468 + # Adding this flag avoid the following errors: + # ld: warning: duplicate -rpath ... ignored + # ld: warning: ignoring duplicate libraries ... + add_link_options("-fuse-ld=lld") + endif() # USE_NIX endif() # APPLE