## Summary With the `lto` distro feature enabled and `ld-is-lld` added to `DISTRO_FEATURES`, any clang-toolchain target that links a shared library fails to build with: ```log x86_64-poky-linux-ld: .../recipe-sysroot-native/usr/bin/x86_64-poky-linux/../lib/LLVMgold.so: error loading plugin: ... cannot open shared object file: No such file or directory x86_64-poky-linux-clang++: error: linker command failed with exit code 1 ``` `ld-is-lld` is (allegedly) supposed to make clang default to lld, which reads LTO bitcode directly and needs no `LLVMgold.so` plugin. It does not take effect for the clang **cross-compiler**, so clang keeps defaulting to GNU bfd, and bfd cannot link the LTO bitcode without the missing gold plugin. > As a side note, `ld-is-lld` is not listed as a `DISTRO_FEATURE` in the > reference manual > ([ref-manual/features.html](https://docs.yoctoproject.org/ref-manual/features.html)) > on `wrynose`, nor the manual mentions the need for > `require conf/distro/include/lto.inc`. Even if you only want GCC's LTO and don't use clang directly, this still bites you: the Rust build configures LLVM with `use-libcxx` (`rust_1.94.1.bb`), which pulls in `libcxx`, and `libcxx` is built with clang, so it hits this. I've seen the commit where LLD was separated from the clang recipe (5212b69f892af8c9b080fee2c21533a2bbdd2755) and the rationale was to reduce build time. I dont think 178.93 seconds for compilation (on my machine) is meaningful enough to justify not using LLD. Why would anyone build with clang without LLD? GNU's bfd linker is pretty bad compared with what LLD offers. And if you are already using clang, it seems like a no brainer to use LLD too so you get all the benefits. ## Environment - openembedded-core `wrynose`, reproduced at commit `42fa856a00`; the relevant recipe/class logic is unchanged at branch tip `c2746a4a16` (clang/llvm 22.1.3 to 22.1.8), so this is **not** fixed upstream as of that tip. - clang/llvm recipes as migrated into oe-core (`meta/recipes-devtools/clang/`), i.e. **not** the external `meta-clang` layer. - Default `TC_CXX_RUNTIME` (gnu). Host/build machine is x86-64; the target arch is irrelevant (reproduced on `qemux86-64` and on `cortexa55`/aarch64) because the defect is in the **build-host** clang. ## TL;DR root cause 1. `ld-is-lld` is a **target** `DISTRO_FEATURE`. `clang_git.bb` turns it into `PACKAGECONFIG[lld] = "-DCLANG_DEFAULT_LINKER=lld"`. 2. The clang cross-compiler is produced by the **native** `clang-cross` recipe, which _copies the `clang-native` binary_. A clang binary's default linker is baked in at **its own** build time. 3. Native recipes filter `DISTRO_FEATURES` down to `DISTRO_FEATURES_NATIVE` + `DISTRO_FEATURES_FILTER_NATIVE`. `ld-is-lld` is in **neither** (see `default-distrovars.inc`). 4. So `clang-native` builds **without** `-DCLANG_DEFAULT_LINKER=lld`, the cross compiler defaults to **bfd**, target `-flto` recipes emit bitcode that bfd can't link, resulting in `LLVMgold.so` missing. ## Kas minimal reproduction example ```yaml header: version: 14 build_system: oe distro: poky machine: qemux86-64 target: libcxx repos: bitbake: url: https://github.com/openembedded/bitbake branch: "2.18" commit: a2dd9be788274d9c7280be5b1be5eb5c3990cf54 path: sources/bitbake layers: {} openembedded-core: url: https://github.com/openembedded/openembedded-core branch: wrynose commit: 42fa856a00ac16b2a7a83d7ecfa60a5be192b16c path: sources/openembedded-core layers: meta: meta-yocto: url: https://git.yoctoproject.org/meta-yocto branch: wrynose commit: 904846ae078ee20de073040ebb77c86e19250f56 path: sources/meta-yocto layers: meta-poky: local_conf_header: lto-mre: | require conf/distro/include/lto.inc # Drop `ld-is-lld` to see the baseline failure with just `lto`. DISTRO_FEATURES:append = " lto ld-is-lld" ``` ### Actual vs expected - **Actual:** `libcxx:do_compile` fails at the `libc++`/`libc++abi` shared-object link with the `LLVMgold.so … cannot open shared object file` error above; the linker invoked is the GNU `<triple>-ld` (bfd), never `ld.lld`. - **Expected:** with `ld-is-lld`, clang defaults to lld, links the LTO bitcode natively, and `libcxx` builds. ## Current workaround Expose `ld-is-lld` to native recipes so `clang-native` (and therefore the copied cross compiler) is built with `-DCLANG_DEFAULT_LINKER=lld`. In our case, we appended the following to our distro config: ```bitbake DISTRO_FEATURES_FILTER_NATIVE:append = " ld-is-lld" ``` With this change, the `clang-native` build sees the `ld-is-lld` feature, adds `lld` to its `PACKAGECONFIG`, and sets `-DCLANG_DEFAULT_LINKER=lld`. The copied cross compiler then defaults to lld, and the target `libcxx`/`compiler-rt` LTO links succeed. We are able to boot the resulting image and confirm that LTO was active (target binary sizes shrank). We are not sure if this is the "right" fix, but it works and is the only way we can migrate to wrynose with LTO enabled. ### Appendix: related issues - [#16026](https://bugzilla.yoctoproject.org/show_bug.cgi?id=16026): covers the libstdc++/libc++ runtime mismatch under `TC_CXX_RUNTIME=llvm`; this bug is about the linker-default (bfd vs lld) not propagating to native/cross clang under the default gnu runtime. ### Appendix: build log excerpt Relevant excerpt from `build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/temp/log.do_compile.1387567`: ```text 26431-[1731/1833] : && /opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/recipe-sysroot-native/usr/bin/x86_64-poky-linux/x86_64-poky-linux-clang++ --sysroot=/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/recipe-sysroot -fPIC -m64 -march=x86-64-v3 -fstack-protector-strong -O2 -D_FORTIFY_SOURCE=2 -Wformat -Wformat-security -Werror=format-security --sysroot=/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/recipe-sysroot -O2 -g -flto -ffile-prefix-map=/opt/yocto/imx9/build/tmp/work-shared/llvm-project-source-22.1.3-r0/sources/llvm-project-22.1.3.src=/usr/src/debug/libcxx/22.1.3 -ffile-prefix-map=/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/build=/usr/src/debug/libcxx/22.1.3 -ffile-prefix-map=/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/recipe-sysroot= -ffile-prefix-map=/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/recipe-sysroot-native= -pipe -fvisibility-inlines-hidden -Werror=date-time -Werror=unguarded-availability-new -Wall -Wextra -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wmissing-field-initializers -Wimplicit-fallthrough -Wcovered-switch-default -Wno-noexcept-type -Wnon-virtual-dtor -Wdelete-non-virtual-dtor -Wno-comment -Wstring-conversion -Wno-pass-failed -fdiagnostics-color -ffunction-sections -fdata-sections -O2 -g -DNDEBUG -shared -Wl,-O1 -Wl,--hash-style=gnu -Wl,--as-needed -flto -ffile-prefix-map=/opt/yocto/imx9/build/tmp/work-shared/llvm-project-source-22.1.3-r0/sources/llvm-project-22.1.3.src=/usr/src/debug/libcxx/22.1.3 -ffile-prefix-map=/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/build=/usr/src/debug/libcxx/22.1.3 -ffile-prefix-map=/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/recipe-sysroot= -ffile-prefix-map=/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/recipe-sysroot-native= -Wl,-z,relro,-z,now -Wl,-z,defs -Wl,-z,nodelete -Wl,-soname,libc++abi.so.1 -o lib/libc++abi.so.1.0 libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_aux_runtime.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_default_handlers.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_demangle.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_exception_storage.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_guard.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_handlers.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_vector.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_virtual.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/stdlib_exception.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/stdlib_stdexcept.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/stdlib_typeinfo.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/abort_message.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/fallback_malloc.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/private_typeinfo.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/stdlib_new_delete.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_exception.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_personality.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_thread_atexit.cpp.o && : 26432-FAILED: [code=1] lib/libc++abi.so.1.0 26433-: && /opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/recipe-sysroot-native/usr/bin/x86_64-poky-linux/x86_64-poky-linux-clang++ --sysroot=/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/recipe-sysroot -fPIC -m64 -march=x86-64-v3 -fstack-protector-strong -O2 -D_FORTIFY_SOURCE=2 -Wformat -Wformat-security -Werror=format-security --sysroot=/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/recipe-sysroot -O2 -g -flto -ffile-prefix-map=/opt/yocto/imx9/build/tmp/work-shared/llvm-project-source-22.1.3-r0/sources/llvm-project-22.1.3.src=/usr/src/debug/libcxx/22.1.3 -ffile-prefix-map=/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/build=/usr/src/debug/libcxx/22.1.3 -ffile-prefix-map=/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/recipe-sysroot= -ffile-prefix-map=/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/recipe-sysroot-native= -pipe -fvisibility-inlines-hidden -Werror=date-time -Werror=unguarded-availability-new -Wall -Wextra -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wmissing-field-initializers -Wimplicit-fallthrough -Wcovered-switch-default -Wno-noexcept-type -Wnon-virtual-dtor -Wdelete-non-virtual-dtor -Wno-comment -Wstring-conversion -Wno-pass-failed -fdiagnostics-color -ffunction-sections -fdata-sections -O2 -g -DNDEBUG -shared -Wl,-O1 -Wl,--hash-style=gnu -Wl,--as-needed -flto -ffile-prefix-map=/opt/yocto/imx9/build/tmp/work-shared/llvm-project-source-22.1.3-r0/sources/llvm-project-22.1.3.src=/usr/src/debug/libcxx/22.1.3 -ffile-prefix-map=/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/build=/usr/src/debug/libcxx/22.1.3 -ffile-prefix-map=/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/recipe-sysroot= -ffile-prefix-map=/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/recipe-sysroot-native= -Wl,-z,relro,-z,now -Wl,-z,defs -Wl,-z,nodelete -Wl,-soname,libc++abi.so.1 -o lib/libc++abi.so.1.0 libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_aux_runtime.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_default_handlers.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_demangle.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_exception_storage.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_guard.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_handlers.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_vector.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_virtual.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/stdlib_exception.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/stdlib_stdexcept.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/stdlib_typeinfo.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/abort_message.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/fallback_malloc.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/private_typeinfo.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/stdlib_new_delete.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_exception.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_personality.cpp.o libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_thread_atexit.cpp.o && : 26434:/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/recipe-sysroot-native/usr/bin/x86_64-poky-linux/x86_64-poky-linux-ld: /opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/recipe-sysroot-native/usr/bin/x86_64-poky-linux/../lib/LLVMgold.so: error loading plugin: /opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/recipe-sysroot-native/usr/bin/x86_64-poky-linux/../lib/LLVMgold.so: cannot open shared object file: No such file or directory 26435:x86_64-poky-linux-clang++: error: linker command failed with exit code 1 (use -v to see invocation) 26436-[1732/1833] /opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/recipe-sysroot-native/usr/bin/x86_64-poky-linux/x86_64-poky-linux-clang++ --sysroot=/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/recipe-sysroot -DLIBCXX_BUILDING_LIBCXXABI -DLIBC_NAMESPACE=__llvm_libc_common_utils -D_LIBCPP_AVAILABILITY_MINIMUM_HEADER_VERSION=2 -D_LIBCPP_BUILDING_LIBRARY -D_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER -D_LIBCPP_REMOVE_TRANSITIVE_INCLUDES -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -I/opt/yocto/imx9/build/tmp/work-shared/llvm-project-source-22.1.3-r0/sources/llvm-project-22.1.3.src/libcxx/src -I/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/build/include/c++/v1 -I/opt/yocto/imx9/build/tmp/work-shared/llvm-project-source-22.1.3-r0/sources/llvm-project-22.1.3.src/libcxxabi/include -I/opt/yocto/imx9/build/tmp/work-shared/llvm-project-source-22.1.3-r0/sources/llvm-project-22.1.3.src/cmake/Modules/../../libc -m64 -march=x86-64-v3 -fstack-protector-strong -O2 -D_FORTIFY_SOURCE=2 -Wformat -Wformat-security -Werror=format-security --sysroot=/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/recipe-sysroot -O2 -g -flto -ffile-prefix-map=/opt/yocto/imx9/build/tmp/work-shared/llvm-project-source-22.1.3-r0/sources/llvm-project-22.1.3.src=/usr/src/debug/libcxx/22.1.3 -ffile-prefix-map=/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/build=/usr/src/debug/libcxx/22.1.3 -ffile-prefix-map=/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/recipe-sysroot= -ffile-prefix-map=/opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/recipe-sysroot-native= -pipe -fvisibility-inlines-hidden -Werror=date-time -Werror=unguarded-availability-new -Wall -Wextra -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wmissing-field-initializers -Wimplicit-fallthrough -Wcovered-switch-default -Wno-noexcept-type -Wnon-virtual-dtor -Wdelete-non-virtual-dtor -Wno-comment -Wstring-conversion -Wno-pass-failed -fdiagnostics-color -ffunction-sections -fdata-sections -O2 -g -DNDEBUG -std=c++23 -fPIC -MD -MT libcxx/src/CMakeFiles/cxx_shared.dir/barrier.cpp.o -MF libcxx/src/CMakeFiles/cxx_shared.dir/barrier.cpp.o.d -o libcxx/src/CMakeFiles/cxx_shared.dir/barrier.cpp.o -c /opt/yocto/imx9/build/tmp/work-shared/llvm-project-source-22.1.3-r0/sources/llvm-project-22.1.3.src/libcxx/src/barrier.cpp 26437-In file included from /opt/yocto/imx9/build/tmp/work-shared/llvm-project-source-22.1.3-r0/sources/llvm-project-22.1.3.src/libcxx/src/barrier.cpp:9: 26438-In file included from /opt/yocto/imx9/build/tmp/work/x86-64-v3-poky-linux/libcxx/22.1.3/build/include/c++/v1/barrier:56: ``` ## Appendix: LLM reasoning chain. How this was diagnosed? This issue was debugged with the help of Claude, and a summary of the reasoning chain is provided below: 1. **Symptom vs. real error.** `libcxx:do_compile` exits 1, and the log is dominated by `-Wnullability-completeness` warnings. Those are noise. The real failure is a **link** error far down the log: `LLVMgold.so … cannot open shared object file`, from the GNU **bfd** linker. → _Read to the actual failing command, not the first/most-frequent message._ 2. **First hypothesis (wrong): "disable LTO for the clang libs."** LTO is on distro-wide; the clang recipes link with bfd; bfd needs `LLVMgold.so` for bitcode; `meta-clang`'s `nonclangable.conf` already disables LTO per-recipe for exactly this reason (zlib/bluez5/systemd-boot, with the same LLVMgold comment). So: add `LTO:pn-libcxx = ""` etc. → **Refuted by a good question:** if there's a long LTO-incompatibility list, why aren't clang's _own_ libraries on it? That means the list isn't the mechanism here — something else is off. 3. **Discovery: `meta-clang` isn't even in the build.** `bblayers.conf` has no `meta-clang`; the clang recipes are served from **openembedded-core** itself. In `wrynose`, clang/llvm/libcxx/compiler-rt were **upstreamed from meta-clang into oe-core** (Khem Raj series, Nov 2024; Ross Burton follow-ups, Sep 2025). So `clang-legacy.bbclass` (the old LTO+lld wiring) and `nonclangable.conf` never applied — the whole first hypothesis was chasing a dormant layer. 4. **Second hypothesis: "just enable `ld-is-lld`."** oe-core's clang toolchain selects lld via the `ld-is-lld` distro feature (`clang_git.bb`: `PACKAGECONFIG[lld] → -DCLANG_DEFAULT_LINKER=lld`) — a feature that, notably, isn't documented in the ref manual. With lld, LTO bitcode links with no plugin. Looked like the answer — and `ld-is-lld` was even present already. → **Still failed:** the build kept using bfd. 5. **Narrowing: inspect the actual compiler.** `clang++ -###` from the recipe sysroot showed the cross clang defaulting to `<triple>-ld` (bfd), _despite_ `ld-is-lld`. `clang-cross` turned out to just **copy the `clang-native` binary**, so the default linker is fixed when `clang-native` is built — not by the target-side feature. 6. **Root cause via `bitbake-getvar -r clang-native DISTRO_FEATURES`.** The variable's computed history showed the raw value _did_ contain `ld-is-lld`, but the final op was `class_filter_features(..., "DISTRO_FEATURES_NATIVE", ...)` — native recipes filter `DISTRO_FEATURES` down to `DISTRO_FEATURES_NATIVE` + `DISTRO_FEATURES_FILTER_NATIVE`, and `ld-is-lld` is in neither. So `clang-native`'s `PACKAGECONFIG` never got `lld` → `-DCLANG_DEFAULT_LINKER=lld` was never set → bfd default. 7. **Fix + verification.** `DISTRO_FEATURES_FILTER_NATIVE:append = " ld-is-lld"` → `clang-native` gets the `lld` packageconfig (`bitbake-getvar` confirms `CLANG_DEFAULT_LINKER=lld`) → cross clang defaults to lld → `libcxx`/`compiler-rt` LTO links succeed. Confirmed end-to-end (clean-room MRE reproduces the failure; the fix builds the full image; LTO verified active on the board). **Lessons:** (a) read to the real failing command; (b) verify which layer/recipe actually serves the failing target before trusting a layer's conventions; (c) a distro-level `DISTRO_FEATURE` does not imply the _native_ tooling sees it — check the native feature filters; (d) `bitbake-getvar -r`'s operation history is the fastest way to see _who_ set/filtered a variable.