crystal-lang/crystal

Linker warnings in Mac OS Ventura

PascalLeMerrer opened this issue ยท 24 comments

Bug Report

I installed Crystal on MacOS Ventura, using Homebrew.
It worked, however compiling a simple hello world generates 280 warnings like:

ld: warning: no platform load command found in '/Users/pascal/.cache/crystal/Users-pascal-Documents-tutorials-crystal-hello_world.cr/F-loat32.o', assuming: macOS
ld: warning: no platform load command found in '/Users/pascal/.cache/crystal/Users-pascal-Documents-tutorials-crystal-hello_world.cr/C-rystal.o', assuming: macOS

Crystal version: 1.9.2
OS Version: Ventura, then Sonoma (I upgraded it, but it did not change anything)
Brew doctor does not signal anything wrong.

The steps I followed:

brew update
brew install crystal

echo 'puts "Hello, World!"' > hello.cr

crystal hello.cr

This seems to be related to a "new linker" in Xcode 15, see https://projects.blender.org/blender/blender/pulls/110243 for example. I couldn't reproduce this warning but it looks like you could try passing --link-flags=-Wl,-ld_classic to use the old linker

crystal hello.cr --link-flags=-Wl,-ld_classic has no visible effect on this issue

crystal hello.cr --link-flags=-Wl,-ld_classic has no visible effect on this issue

@PascalLeMerrer in order to pass parameters to the run command you need to provide explicitly the command:

crystal run --link-flags=-Wl,-ld_classic hello.cr

It works much better indeed! Thanks @beta-ziliani

Just an observation:
crystal eval has the same issue, and it doesn't take --link-flags.
export LDFLAGS="-Wl,-ld_classic" doesn't seem to get picked up.

You should be able to do CRYSTAL_OPTS="--link-flags=-Wl,-ld_classic"

Just to note that macOS Sonoma (14) is also affected (as one might expect) by the same issue, and that setting the --link-flags as above avoids the warnings.

Out of interest: I happened to notice that binaries created with the "new linker" are about 10% smaller than when using these flags.

Same problem

FYI, I upgraded to Sonoma today and was getting linker warnings like this:

object file (H-ash5858E-ntry40S-tring4432S-tring41.o) was built for newer macOS version (14.0) than being linked (13.3)

There is also an update for xcode-tools that once installed resolved the linker warnings.

@PascalLeMerrer in order to pass parameters to the run command you need to provide explicitly the command:

crystal run --link-flags=-Wl,-ld_classic hello.cr

This works for me. I'm running macOS Sonoma 14 and XCode 15.0 (15A240d).

Narven commented
CRYSTAL_OPTS="--link-flags=-Wl,-ld_classic" crystal run hello.cr

works for me . I'm on:
macOS 14.0 23A344 x86_64 Sonoma

Yes even better is to just put this export in your bashrc or .zshrc

export CRYSTAL_OPTS="--link-flags=-Wl,-ld_classic"

then crystal build, crystal run etc all work without the ld warnings.

I've been investigating this and it's related to the default target triple produced by Crystal during compilation. It seems like in order to fix a bug back in 2015, we stripped the minimum deployment target from the target triple in this commit. Since then, further work has been put into place to normalize target triples from different architectures.

crystal/src/llvm.cr

Lines 91 to 109 in 1675145

def self.default_target_triple : String
chars = LibLLVM.get_default_target_triple
case triple = string_and_dispose(chars)
when .starts_with?("x86_64-apple-macosx"), .starts_with?("x86_64-apple-darwin")
# normalize on `macosx` and remove minimum deployment target version
"x86_64-apple-macosx"
when .starts_with?("aarch64-apple-macosx"), .starts_with?("aarch64-apple-darwin")
# normalize on `macosx` and remove minimum deployment target version
"aarch64-apple-macosx"
when .starts_with?("aarch64-unknown-linux-android")
# remove API version
"aarch64-unknown-linux-android"
when .starts_with?("x86_64-pc-solaris")
# remove API version
"x86_64-pc-solaris"
else
triple
end
end
.

With the release of Xcode 15, a new linker was introduced and with it came these warning messages. It appears that the new linker is stricter in which target triples it considers valid. Specifically, it looks like the minimum deployment target is required. E.g., aarch64-apple-darwin23.3.0 is valid, while aarch64-apple-darwin is not.

Before I go ahead and submit the pull request, I'd love to discuss what the correct path forward is. We could change the behaviour of LLVM#default_target_triple to only strip the minimum deployment target for macOS versions below a certain threshold or we could drop the stripping behaviour entirely (not sure that's a good idea). An alternative would be to hard-code the minimum deployment target to something like 10.14.6. Judging by the last two available SDKs that sounds reasonable at first sight.

$ cd /Library/Developer/CommandLineTools/SDKs
$ find . -maxdepth 1 ! -type l ! -path . -exec bash -c 'echo {} && gojq ".SupportedTargets.macosx | { MinimumDeploymentTarget, MaximumDeploymentTarget, RecommendedDeploymentTarget }" {}/SDKSettings.json' \;
./MacOSX13.3.sdk
{
  "MaximumDeploymentTarget": "13.3.99",
  "MinimumDeploymentTarget": "10.13",
  "RecommendedDeploymentTarget": "10.14.6"
}
./MacOSX14.4.sdk
{
  "MaximumDeploymentTarget": "14.4.99",
  "MinimumDeploymentTarget": "10.13",
  "RecommendedDeploymentTarget": "10.14.6"
}

10.14.6 would support versions down to Xcode 10, which was publicly released on September 17, 2018.

Does this also explain why I couldn't reproduce this on my M2 since the normalization fails due to #14052?

It does, yes. I'm seeing the same behaviour on my machine since the default triple returned by LLVM is arm64-apple-darwin23.3.0 and thus it doesn't get normalised by LLVM#default_target_triple. The architecture still gets normalized by Crystal::Codegen::Target:

# Perform additional normalization and parsing
case @architecture
when "i486", "i586", "i686"
@architecture = "i386"
when "amd64"
@architecture = "x86_64"
when "arm64"
@architecture = "aarch64"
when .starts_with?("arm")
@architecture = "arm"
else
# no need to tweak the architecture
end

but the new linker seem to accept that anyway.

It turns out we hard-code the target in the distribution-scripts. This is definitely also an issue.

I'm also not entirely sure that comment is correct (at least not any more). Running /usr/bin/clang --print-targets produces:

  Registered Targets:
    aarch64    - AArch64 (little endian)
    aarch64_32 - AArch64 (little endian ILP32)
    aarch64_be - AArch64 (big endian)
    arm        - ARM
    arm64      - ARM64 (little endian)
    arm64_32   - ARM64 (little endian ILP32)
    armeb      - ARM (big endian)
    thumb      - Thumb
    thumbeb    - Thumb (big endian)
    x86        - 32-bit X86: Pentium-Pro and above
    x86-64     - 64-bit X86: EM64T and AMD64

I've also confirmed that the following all compile without any warnings:

$ clang -Xclang '-triple=arm64-apple-macosx14.0.0' hello.c -o hello
$ clang -Xclang '-triple=aarch64-apple-macosx14.0.0' heloc.c -o hello
$ clang -Xclang '-triple=arm64-apple-darwin23.2.0' hello.c -o hello
$ clang -Xclang '-triple=aarch64-apple-darwin23.2.0' hello.c -o hello

while the following does not:

$ clang -Xclang '-triple=arm64-apple-darwin' hello.c -o hello
$ clang -Xclang '-triple=aarch64-apple-darwin' hello.c -o hello
$ clang -Xclang '-triple=arm64-apple-macosx' hello.c -o hello
$ clang -Xclang '-triple=aarch64-apple-macosx' hello.c -o hello

they all produce a warning like the following:

ld: warning: no platform load command found in '/private/var/folders/lp/053qjqyj1cg5gx_23kr9fphc0000gn/T/hello-b3b8c6.o', assuming: macOS

This is great work, thanks Kevin for digging into this ๐Ÿš€

I guess there's a reason why LLVM doesn't normalize target triples. Our normalization is quite significant in some places.

Before we make changes that bring back version numbers, we need to check where these triples are used and whether some use cases may depend on normalized values. We may have to offer both, the full raw triple as well as a normalized version.

I am running into this issue when I use Intel macs on GitHub Actions since this week.
Moreover, presumably due to the same root cause, I am unable to link against any C libraries that I built myself in CI, whereas just a week ago I was able to. This is an error in addition to the warnings that others already reported.
This is probably because GitHub Actions updated their Xcode.

ld: warning: REFERENCED_DYNAMICALLY flag on symbol '_catch_exception_raise' is deprecated
...
ld: warning: no platform load command found in '/Users/runner/.cache/crystal/Users-runner-work-crystal-chipmunk-crystal-chipmunk-spec/T-uple40U-I-nt6441.o0.o', assuming: macOS
dyld[15199]: Symbol not found: _cpArbiterGetContactPointSet

https://github.com/oprypin/crystal-chipmunk/actions/runs/8859208139/job/24329593971
https://github.com/oprypin/crsfml/actions/runs/8859272671/job/24328862277

The warnings are now always reproducible on GitHub Actions with runs-on: macos-13.
https://github.com/crystal-lang/install-crystal/actions/runs/8859670657/job/24329698775

Back to the C library troubles-
Even if I apply CRYSTAL_OPTS="--link-flags=-Wl,-ld_classic" I am still building my C libraries with the new linker. (Presumably would need to apply such flags to the step that builds the C library as well).
As such, I get this error instead:

ld: warning: dylib (/usr/local/lib/libchipmunk.dylib) was built for newer macOS version (13.6) than being linked (13.0)
dyld[15192]: Symbol not found: _cpArbiterGetContactPointSet

https://github.com/oprypin/crystal-chipmunk/actions/runs/8859789088/job/24329985809

I met the same warnings on GitHub Actions, with the full warning message here.

https://github.com/yanecc/MockGPT/actions/runs/8913041997/job/24477764716#step:4:24

Those warnings should be gone whenever #14466 gets merged.

I am also getting errors in GitHub Actions when using MacOS based runners. Builds on Linux work fine:

[14/13] Codegen (linking)                 
cc _main.o3.o -o /Users/runner/work/runway/runway/bin/runway  -rdynamic -L/Users/runner/work/_temp/crystal-latest-true-undefined/embedded/lib -lssh2 -lxml2 -lz `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libssl || printf %s '-lssl -lcrypto'` `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libcrypto || printf %s '-lcrypto'` -L/opt/homebrew/Cellar/libyaml/0.2.5/lib -lyaml -lpcre2-8 -lgc -L/opt/homebrew/Cellar/libevent/2.1.12_1/lib -levent_pthreads -levent -L/opt/homebrew/Cellar/libevent/2.1.12_1/lib -levent -liconv
E: Error target runway failed to compile:
ld: warning: ignoring duplicate libraries: '-levent'
ld: library 'ssh2' not found
clang: error: linker command failed with exit code 1 (use -v to see invocation)
Error: execution of command failed with exit status 1: cc "${@}" -o /Users/runner/work/runway/runway/bin/runway  -rdynamic -L/Users/runner/work/_temp/crystal-latest-true-undefined/embedded/lib -lssh2 -lxml2 -lz `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libssl || printf %s '-lssl -lcrypto'` `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libcrypto || printf %s '-lcrypto'` -L/opt/homebrew/Cellar/libyaml/0.2.5/lib -lyaml -lpcre2-8 -lgc -L/opt/homebrew/Cellar/libevent/2.1.12_1/lib -levent_pthreads -levent -L/opt/homebrew/Cellar/libevent/2.1.12_1/lib -levent -liconv

source logs


Update

I swapped from macos-latest to macos-13 and now my CI is working. Not ideal, but points to a greater issue in later MacOS versions:

Image