From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtp.gentoo.org (woodpecker.gentoo.org [140.211.166.183]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits)) (No client certificate requested) by finch.gentoo.org (Postfix) with ESMTPS id 202F01584AD for ; Tue, 06 May 2025 08:46:09 +0000 (UTC) Received: from lists.gentoo.org (bobolink.gentoo.org [140.211.166.189]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits)) (No client certificate requested) (Authenticated sender: relay-lists.gentoo.org@gentoo.org) by smtp.gentoo.org (Postfix) with ESMTPSA id 0755C343119 for ; Tue, 06 May 2025 08:46:09 +0000 (UTC) Received: from bobolink.gentoo.org (localhost [127.0.0.1]) by bobolink.gentoo.org (Postfix) with ESMTP id DB8B31103AD; Tue, 06 May 2025 08:46:05 +0000 (UTC) Received: from smtp.gentoo.org (woodpecker.gentoo.org [IPv6:2001:470:ea4a:1:5054:ff:fec7:86e4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits)) (No client certificate requested) by bobolink.gentoo.org (Postfix) with ESMTPS id D15351103AD for ; Tue, 06 May 2025 08:46:05 +0000 (UTC) Received: from oystercatcher.gentoo.org (oystercatcher.gentoo.org [148.251.78.52]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits)) (No client certificate requested) by smtp.gentoo.org (Postfix) with ESMTPS id 5E96E343102 for ; Tue, 06 May 2025 08:46:05 +0000 (UTC) Received: from localhost.localdomain (localhost [IPv6:::1]) by oystercatcher.gentoo.org (Postfix) with ESMTP id F07F61AD5 for ; Tue, 06 May 2025 08:46:03 +0000 (UTC) From: "Sam James" To: gentoo-commits@lists.gentoo.org Content-Transfer-Encoding: 8bit Content-type: text/plain; charset=UTF-8 Reply-To: gentoo-dev@lists.gentoo.org, "Sam James" Message-ID: <1746521106.f4e964aa2024f09da289f71768cc9b09c1ab09cf.sam@gentoo> Subject: [gentoo-commits] repo/gentoo:master commit in: eclass/, eclass/tests/ X-VCS-Repository: repo/gentoo X-VCS-Files: eclass/dot-a.eclass eclass/tests/dot-a.sh X-VCS-Directories: eclass/tests/ eclass/ X-VCS-Committer: sam X-VCS-Committer-Name: Sam James X-VCS-Revision: f4e964aa2024f09da289f71768cc9b09c1ab09cf X-VCS-Branch: master Date: Tue, 06 May 2025 08:46:03 +0000 (UTC) Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-Id: Gentoo Linux mail X-BeenThere: gentoo-commits@lists.gentoo.org X-Auto-Response-Suppress: DR, RN, NRN, OOF, AutoReply X-Archives-Salt: f701da36-075d-4e22-ba9f-e7392e8e2de2 X-Archives-Hash: 4645ee6e7db1ca9f4bd86aac02dc042c commit: f4e964aa2024f09da289f71768cc9b09c1ab09cf Author: Sam James gentoo org> AuthorDate: Thu May 1 18:30:56 2025 +0000 Commit: Sam James gentoo org> CommitDate: Tue May 6 08:45:06 2025 +0000 URL: https://gitweb.gentoo.org/repo/gentoo.git/commit/?id=f4e964aa dot-a.eclass: new eclass for handling LTO in static archives Introduce a new eclass with utility functions for handling LTO bytecode (or internal representation, IR) inside static archives (.a files). Static libraries when built with LTO will contain LTO bytecode which is not portable across compiler versions or compiler vendors. To avoid pessimising the library and always filtering LTO, we can build it with -ffat-lto-objects instead, which builds some components twice. The installed part will then have the LTO contents stripped out, leaving the regular objects in the static archive. It's not feasible to make these work otherwise, as we'd need tracking for whether a library was built by a specific compiler and its version, and that compatibility can vary based on other factors (e.g. with gcc, sys-devel/gcc[zstd] controls if it supports zstd compression for LTO). We also discourage static libraries anyway. Provide two functions: * lto-guarantee-fat If LTO is currently enabled (as determined by `tc-is-lto`, added in 2aea6c3ff2181ad96187e456a3307609fd288d4c), add `-ffat-lto-objects` to CFLAGS and CXXFLAGS if supported. This guarantees that produced archives are "fat" (contain both IR and regular object files) for later pruning. * strip-lto-bytecode Process a given static archive (.a file) and remove its IR component, leaving a regular object. This approach is also taken by Fedora, openSUSE, and Debian/Ubuntu. An honourable mention to `lto-rebuild` which fulfilled the same task for many in the LTO overlay too. We did consider an alternative approach where we'd relink objects using the driver in src_install (or some hook afterwards), but this would be more brittle, as we'd need to extract the right arguments to use (see e.g. the recent Wireshark issues in fad8ff8a45afc83559f8df695cf96dfec51d3e8a for how this can be subtle) and not PM-agnostic given we don't have portable hooks right now (and even if we did, suspect they wouldn't work in a way that facilitated this). It's also not clear if such an approach would've worked for Clang. The tests have been quite helpful in debugging this and making sure things work as expected. They both make sure the eclass does what it ought to, but also try to capture the expected interaction with the toolchain (which is why we have the skips depending on tooling & versions) to allow us to test workarounds and make sure we understand the interactions fully: it made it easy to test e.g. a patch to Binutils to make strip have plugin/LTO integration (PR21479). All of this wasn't worth pursuing until H. J. Lu's patches for Binutils landed, which they have now in binutils-2.44 [0], which made bfd's handling of mixed objects much more robust. [0] https://inbox.sourceware.org/binutils/20250112220244.597636-1-hjl.tools gmail.com/ Bug: https://bugs.gentoo.org/926120 Thanks-to: Arsen Arsenović gentoo.org> Co-authored-by: Eli Schwartz gentoo.org> Signed-off-by: Sam James gentoo.org> eclass/dot-a.eclass | 117 ++++++++++++ eclass/tests/dot-a.sh | 511 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 628 insertions(+) diff --git a/eclass/dot-a.eclass b/eclass/dot-a.eclass new file mode 100644 index 000000000000..541c5b5c1269 --- /dev/null +++ b/eclass/dot-a.eclass @@ -0,0 +1,117 @@ +# Copyright 2025 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 + +# @ECLASS: dot-a.eclass +# @MAINTAINER: +# Toolchain Ninjas +# @AUTHOR: +# Sam James +# Eli Schwartz +# @SUPPORTED_EAPIS: 8 +# @BLURB: Functions to handle stripping LTO bytecode out of static archives. +# @DESCRIPTION: +# This eclass provides functions to strip LTO bytecode out of static archives +# (.a files). +# +# Static libraries when built with LTO will contain LTO bytecode which is +# not portable across compiler versions or compiler vendors. To avoid pessimising +# the library and always filtering LTO, we can build it with -ffat-lto-objects +# instead, which builds some components twice. The installed part will then +# have the LTO contents stripped out, leaving the regular objects in the +# static archive. +# +# Use should be passing calling lto-guarantee-fat before configure-time +# and calling strip-lto-bytecode after installation. +# +# Most packages installing static libraries should be using this eclass, +# though it's not strictly necessary if the package filters LTO. +# +# @EXAMPLE: +# @CODE +# +# inherit dot-a +# +# src_configure() { +# lto-guarantee-fat +# econf +# } +# +# src_install() { +# default +# strip-lto-bytecode +# } +case ${EAPI} in + 8) ;; + *) die "${ECLASS}: EAPI ${EAPI:-0} not supported" ;; +esac + +if [[ -z ${_DOT_A_ECLASS} ]] ; then +_DOT_A_ECLASS=1 + +inherit flag-o-matic toolchain-funcs + +# TODO: QA check + +# @FUNCTION: lto-guarantee-fat +# @DESCRIPTION: +# If LTO is enabled, appends -ffat-lto-objects or any other flags needed +# to provide fat LTO objects. +lto-guarantee-fat() { + tc-is-lto || return + + # We add this for all languages as LTO obviously can't be done + # if different compilers are used for e.g. C vs C++ anyway. + append-flags $(test-flags-CC -ffat-lto-objects) +} + +# @FUNCTION: strip-lto-bytecode +# @USAGE: [library|directory] [...] +# @DESCRIPTION: +# Strips LTO bytecode from libraries (static archives) passed as arguments. +# Defaults to operating on ${ED} as a whole if no arguments are passed. +# +# As an optimisation, if USE=static-libs exists for a package and is disabled, +# the default-searching behaviour with no arguments is suppressed. +strip-lto-bytecode() { + tc-is-lto || return + + local files=() + + if [[ ${#} -eq 0 ]]; then + if ! in_iuse static-libs || use static-libs ; then + # maybe we are USE=static-libs. Alternatively, maybe the ebuild doesn't + # offer such a choice. In both cases, the user specified the function, + # so we expect to be called on *something*, but nothing was explicitly + # passed. Try scanning ${ED} automatically. + set -- "${ED}" + fi + fi + + mapfile -t -d '' files < <(find -H "${@}" -type f \( -name '*.a' -or -name '*.o' \) -print0) + + toolchain_type= + tc-is-gcc && toolchain_type=gnu + tc-is-clang && toolchain_type=llvm + + local file + for file in "${files[@]}" ; do + case ${toolchain_type} in + gnu) + $(tc-getSTRIP) \ + -R .gnu.lto_* \ + -R .gnu.debuglto_* \ + -N __gnu_lto_v1 \ + "${file}" || die "Stripping bytecode in ${file} failed" + ;; + llvm) + llvm-bitcode-strip \ + -r "${file}" \ + -o "${file}" || die "Stripping bytecode in ${file} failed" + ;; + *) + ;; + esac + done +} + +fi diff --git a/eclass/tests/dot-a.sh b/eclass/tests/dot-a.sh new file mode 100755 index 000000000000..de60c4dc7605 --- /dev/null +++ b/eclass/tests/dot-a.sh @@ -0,0 +1,511 @@ +#!/bin/bash +# Copyright 2025 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 + +EAPI=8 + +source tests-common.sh || exit +source version-funcs.sh || exit + +inherit dot-a + +_strip_is_gnu() { + local name=$($(tc-getSTRIP) --version 2>&1 | head -n 1) + + if ! [[ ${name} =~ ^GNU.*strip ]] ; then + return 1 + fi + + return 0 +} + +_check_binutils_version() { + local tool=$1 + # Convert this: + # ``` + # GNU ld (Gentoo 2.38 p4) 2.38 + # Copyright (C) 2022 Free Software Foundation, Inc. + # This program is free software; you may redistribute it under the terms of + # the GNU General Public License version 3 or (at your option) a later version. + # This program has absolutely no warranty. + # ``` + # + # into... + # ``` + # 2.38 + # ``` + local ver=$(${tool} --version 2>&1 | head -n 1 | rev | cut -d' ' -f1 | rev) + + if ! [[ ${ver} =~ [0-9].[0-9][0-9](.[0-9]?) ]] ; then + # Skip if unrecognised format so we don't pass something + # odd into ver_cut. + return + fi + + ver_major=$(ver_cut 1 "${ver}") + ver_minor=$(ver_cut 2 "${ver}") + ver_extra=$(ver_cut 3 "${ver}") + echo ${ver_major}.${ver_minor}${ver_extra:+.${ver_extra}} +} + +_create_test_progs() { + cat <<-EOF > a.c + int foo(); + + int foo() { + return 42; + } + EOF + + cat <<-EOF > main.c + #include + int foo(); + + int main() { + printf("Got magic number: %d\n", foo()); + return 0; + } + EOF +} + +test_lto_guarantee_fat() { + # Check whether lto-guarantee-fat adds -ffat-lto-objects and it + # results in a successful link (and a failed link without it). + LDFLAGS="-fuse-ld=${linker}" + + $(tc-getCC) ${CFLAGS} -flto a.c -o a.o -c || die + $(tc-getCC) ${CFLAGS} ${LDFLAGS} -flto main.c a.o -o main || die + if ./main | grep -q "Got magic number: 42" ; then + :; + else + die "Pure LTO check failed" + fi + + tbegin "lto-guarantee-fat (CC=$(tc-getCC), linker=${linker}): check linking w/ fat LTO object w LTO" + ret=0 + ( + export CFLAGS="-O2 -flto" + lto-guarantee-fat + + $(tc-getCC) ${CFLAGS} a.c -o a.o -c 2>/dev/null || return 1 + $(tc-getCC) ${CFLAGS} ${LDFLAGS} main.c a.o 2>/dev/null || return 1 + ) || ret=1 + tend ${ret} "Linking LTO executable w/ fat object failed" + + tbegin "lto-guarantee-fat (CC=$(tc-getCC), linker=${linker}): check linking w/ fat LTO object w/o LTO" + ret=0 + ( + export CFLAGS="-O2 -flto" + lto-guarantee-fat + + # Linking here will fail if a.o isn't a fat object, as there's nothing + # to fall back on with -fno-lto. + $(tc-getCC) ${CFLAGS} a.c -o a.o -c 2>/dev/null || return 1 + $(tc-getCC) ${CFLAGS} ${LDFLAGS} -fno-lto main.c a.o 2>/dev/null || return 1 + ) || ret=1 + tend ${ret} "Linking non-LTO executable w/ fat object failed" + + tbegin "lto-guarantee-fat (CC=$(tc-getCC), linker=${linker}): check linking w/ fat LTO archive w LTO" + ret=0 + ( + export CFLAGS="-O2 -flto" + lto-guarantee-fat + + rm test.a 2>/dev/null + + $(tc-getCC) ${CFLAGS} a.c -o a.o -c 2>/dev/null || return 1 + $(tc-getAR) r test.a a.o 2>/dev/null || return 1 + $(tc-getCC) ${CFLAGS} ${LDFLAGS} main.c test.a 2>/dev/null || return 1 + ) || ret=1 + tend ${ret} "Linking LTO executable w/ fat archive failed" + + tbegin "lto-guarantee-fat (CC=$(tc-getCC), linker=${linker}): check linking w/ fat LTO archive w/o LTO" + ret=0 + ( + export CFLAGS="-O2 -flto" + lto-guarantee-fat + + rm test.a 2>/dev/null + + # Linking here will fail if a.o (-> test.a) isn't a fat object, as there's nothing + # to fall back on with -fno-lto. + $(tc-getCC) ${CFLAGS} a.c -o a.o -c 2>/dev/null || return 1 + $(tc-getAR) r test.a a.o 2>/dev/null || return 1 + $(tc-getCC) ${CFLAGS} ${LDFLAGS} -fno-lto main.c test.a 2>/dev/null || return 1 + ) || ret=1 + tend ${ret} "Linking non-LTO executable w/ fat archive failed" +} + +test_strip_lto_bytecode() { + # Check whether strip-lto-bytecode does its job on a single argument, but + # focus of this test is more basic, not checking all possible option + # handling. + # + # i.e. If we use strip-lto-bytecode, does it remove the LTO bytecode + # and allow linking? If we use it w/o -ffat-lto-objects, do we get + # a failed link as we expect? + LDFLAGS="-fuse-ld=${linker}" + + tbegin "strip-lto-bytecode (CC=$(tc-getCC), linker=${linker}): check that linking w/ stripped non-fat object breaks" + ret=0 + ( + export CFLAGS="-O2 -flto" + + # strip-lto-bytecode will error out early with LLVM, + # so stop the test here. + tc-is-clang && return 0 + # strip with >= GNU Binutils 2.45 won't corrupt the archive: + # https://sourceware.org/PR21479 + _strip_is_gnu && ver_test $(_check_binutils_version $(tc-getSTRIP)) -gt 2.44 && return 0 + + $(tc-getCC) ${CFLAGS} a.c -o a.o -c 2>/dev/null || return 1 + + # This should corrupt a.o and make linking below fail. + strip-lto-bytecode a.o + + $(tc-getCC) ${CFLAGS} ${LDFLAGS} main.c a.o -o main 2>/dev/null && return 1 + + return 0 + ) || ret=1 + tend ${ret} "Linking corrupted non-fat object unexpectedly worked" + + tbegin "strip-lto-bytecode (CC=$(tc-getCC), linker=${linker}): check that linking w/ stripped fat object works" + ret=0 + ( + export CFLAGS="-O2 -flto" + + lto-guarantee-fat + + $(tc-getCC) ${CFLAGS} a.c -o a.o -c 2>/dev/null || return 1 + + # This should NOT corrupt a.o, so linking below should succeed. + strip-lto-bytecode a.o + + $(tc-getCC) ${CFLAGS} ${LDFLAGS} main.c a.o -o main 2>/dev/null || return 1 + ) || ret=1 + tend ${ret} "Linking stripped fat object failed" + + tbegin "strip-lto-bytecode (CC=$(tc-getCC), linker=${linker}): check that linking w/ stripped non-fat archive breaks" + ret=0 + ( + export CFLAGS="-O2 -flto" + + rm test.a 2>/dev/null + + # strip-lto-bytecode will error out early with LLVM, + # so stop the test here. + tc-is-clang && return 0 + # strip with >= GNU Binutils 2.45 won't corrupt the archive: + # https://sourceware.org/PR21479 + _strip_is_gnu && ver_test $(_check_binutils_version $(tc-getSTRIP)) -gt 2.44 && return 0 + + $(tc-getCC) ${CFLAGS} a.c -o a.o -c 2>/dev/null || return 1 + $(tc-getAR) q test.a a.o 2>/dev/null || return 1 + + # This should corrupt a.o and make linking below fail. + strip-lto-bytecode test.a + + $(tc-getCC) ${CFLAGS} ${LDFLAGS} main.c test.a -o main 2>/dev/null && return 1 + + return 0 + ) || ret=1 + tend ${ret} "Linking corrupted non-fat object unexpectedly worked" + + tbegin "strip-lto-bytecode (CC=$(tc-getCC), linker=${linker}): check that linking w/ stripped fat archive works" + ret=0 + ( + export CFLAGS="-O2 -flto" + + rm test.a 2>/dev/null + + lto-guarantee-fat + + $(tc-getCC) ${CFLAGS} a.c -o a.o -c 2>/dev/null || return 1 + $(tc-getAR) q test.a a.o 2>/dev/null || return 1 + + # This should NOT corrupt a.o, so linking below should succeed. + strip-lto-bytecode test.a + + $(tc-getCC) ${CFLAGS} ${LDFLAGS} main.c test.a -o main 2>/dev/null || return 1 + ) || ret=1 + tend ${ret} "Linking stripped fat archive failed" +} + +test_mixed_objects_after_stripping() { + # Check whether mixing objects from two compilers (${CC_1} and ${CC_2}) + # fails without lto-guarantee-fat and strip-lto-bytecode and works + # once they're used. + LDFLAGS="-fuse-ld=${linker}" + + tbegin "strip-lto-bytecode (CC_1=${CC_1}, CC_2=${CC_2}, linker=${linker}): check that unstripped LTO objects from ${CC_1} fail w/ ${CC_2}" + ret=0 + ( + export CFLAGS="-O2 -flto" + + ${CC_1} ${CFLAGS} a.c -o a.o -c 2>/dev/null || return 1 + # Using CC_1 IR with CC_2 should fail. + ${CC_2} ${CFLAGS} ${LDFLAGS} main.c a.o -o main 2>/dev/null && return 1 + + return 0 + ) || ret=1 + tend ${ret} "Mixing unstripped objects unexpectedly worked" + + tbegin "strip-lto-bytecode (CC_1=${CC_1}, CC_2=${CC_2}, linker=${linker}): check that unstripped LTO objects from ${CC_2} fail w/ ${CC_1}" + ret=0 + ( + export CFLAGS="-O2 -flto" + + ${CC_2} ${CFLAGS} a.c -o a.o -c 2>/dev/null || return 1 + # Using CC_2 IR with CC_1 should fail. + ${CC_1} ${CFLAGS} ${LDFLAGS} main.c a.o -o main 2>/dev/null && return 1 + + return 0 + ) || ret=1 + tend ${ret} "Mixing unstripped objects unexpectedly worked" + + tbegin "strip-lto-bytecode (CC_1=${CC_1}, CC_2=${CC_2}, linker=${linker}): check that stripped LTO objects from ${CC_1} work w/ ${CC_2}" + ret=0 + ( + export CFLAGS="-O2 -flto" + + lto-guarantee-fat + ${CC_1} ${CFLAGS} a.c -o a.o -c 2>/dev/null || return 1 + # The object should now be "vendor-neutral" and work. + CC=${CC_1} strip-lto-bytecode a.o + ${CC_2} ${CFLAGS} ${LDFLAGS} main.c a.o -o main 2>/dev/null || return 1 + ) || ret=1 + tend ${ret} "Mixing stripped objects failed" + + tbegin "strip-lto-bytecode (CC_1=${CC_1}, CC_2=${CC_2}, linker=${linker}): check that stripped LTO objects from ${CC_2} work w/ ${CC_1}" + ret=0 + ( + export CFLAGS="-O2 -flto" + + lto-guarantee-fat + ${CC_2} ${CFLAGS} a.c -o a.o -c 2>/dev/null || return 1 + # The object should now be "vendor-neutral" and work. + CC=${CC_2} strip-lto-bytecode a.o + ${CC_1} ${CFLAGS} ${LDFLAGS} main.c a.o -o main 2>/dev/null || return 1 + ) || ret=1 + tend ${ret} "Mixing stripped objects failed" +} + +test_mixed_archives_after_stripping() { + # Check whether mixing archives from two compilers (${CC_1} and ${CC_2}) + # fails without lto-guarantee-fat and strip-lto-bytecode and works + # once they're used. + LDFLAGS="-fuse-ld=${linker}" + + tbegin "strip-lto-bytecode (CC_1=${CC_1}, CC_2=${CC_2}, linker=${linker}): check that unstripped LTO archives from ${CC_1} fail w/ ${CC_2}" + ret=0 + ( + export CFLAGS="-O2 -flto" + + rm test.a 2>/dev/null + + ${CC_1} ${CFLAGS} a.c -o a.o -c 2>/dev/null || return 1 + ${AR_1} r test.a a.o 2>/dev/null || return 1 + # Using CC_1 IR with CC_2 should fail. + ${CC_2} ${CFLAGS} ${LDFLAGS} main.c test.a -o main 2>/dev/null && return 1 + + return 0 + ) || ret=1 + tend ${ret} "Mixing unstripped archives unexpectedly worked" + + tbegin "strip-lto-bytecode (CC_1=${CC_1}, CC_2=${CC_2}, linker=${linker}): check that unstripped LTO archives from ${CC_2} fail w/ ${CC_1}" + ret=0 + ( + export CFLAGS="-O2 -flto" + + rm test.a 2>/dev/null + + ${CC_2} ${CFLAGS} a.c -o a.o -c 2>/dev/null || return 1 + ${AR_2} r test.a a.o 2>/dev/null || return 1 + # Using CC_2 IR with CC_1 should fail. + ${CC_1} ${CFLAGS} ${LDFLAGS} main.c test.a -o main 2>/dev/null && return 1 + + return 0 + ) || ret=1 + tend ${ret} "Mixing unstripped archives unexpectedly worked" + + tbegin "strip-lto-bytecode (CC_1=${CC_1}, CC_2=${CC_2}, linker=${linker}): check that stripped LTO archives from ${CC_1} work w/ ${CC_2}" + ret=0 + ( + export CFLAGS="-O2 -flto" + + rm test.a 2>/dev/null + + lto-guarantee-fat + ${CC_1} ${CFLAGS} a.c -o a.o -c 2>/dev/null || return 1 + ${AR_1} r test.a a.o 2>/dev/null || return 1 + # The object should now be "vendor-neutral" and work. + CC=${CC_1} strip-lto-bytecode test.a + ${CC_2} ${CFLAGS} ${LDFLAGS} main.c test.a -o main 2>/dev/null || return 1 + ) || ret=1 + tend ${ret} "Mixing stripped archives failed" + + tbegin "strip-lto-bytecode (CC_1=${CC_1}, CC_2=${CC_2}, linker=${linker}): check that stripped LTO archives from ${CC_2} work w/ ${CC_1}" + ret=0 + ( + export CFLAGS="-O2 -flto" + + rm test.a 2>/dev/null + + lto-guarantee-fat + ${CC_2} ${CFLAGS} a.c -o a.o -c 2>/dev/null || return 1 + ${AR_2} r test.a a.o 2>/dev/null || return 1 + # The object should now be "vendor-neutral" and work. + CC=${CC_2} strip-lto-bytecode test.a + ${CC_1} ${CFLAGS} ${LDFLAGS} main.c test.a -o main 2>/dev/null || return 1 + ) || ret=1 + tend ${ret} "Mixing stripped archives failed" +} + +_check_if_lto_object() { + # Adapted from tc-is-lto + local ret=1 + case $(tc-get-compiler-type) in + clang) + # If LTO is used, clang will output bytecode and llvm-bcanalyzer + # will run successfully. Otherwise, it will output plain object + # file and llvm-bcanalyzer will exit with error. + llvm-bcanalyzer "$1" &>/dev/null && ret=0 + ;; + gcc) + [[ $($(tc-getREADELF) -S "$1") == *.gnu.lto* ]] && ret=0 + ;; + esac + return "${ret}" +} + +test_search_recursion() { + # Test whether the argument handling and logic of strip-lto-bytecode + # works as expected. + tbegin "whether default search behaviour of \${ED} works" + ret=0 + ( + CC=gcc + CFLAGS="-O2 -flto" + + rm foo.a 2>/dev/null + + _create_test_progs + lto-guarantee-fat + $(tc-getCC) ${CFLAGS} a.c -o a.o -c 2>/dev/null || return 1 + $(tc-getAR) q foo.a a.o 2>/dev/null || return 1 + + _check_if_lto_object "${tmpdir}/lto/foo.a" || return 1 + # It should search ${ED} if no arguments are passed, find + # the LTO'd foo.o, and strip it. + ED="${tmpdir}/lto" strip-lto-bytecode + # foo.a should be a regular object here. + _check_if_lto_object "${tmpdir}/lto/foo.a" && return 1 + + return 0 + ) || ret=1 + tend ${ret} "Unexpected LTO object found" + + tbegin "whether a single file argument works" + ret=0 + ( + CC=gcc + CFLAGS="-O2 -flto" + + rm foo.a 2>/dev/null + + _create_test_progs + lto-guarantee-fat + $(tc-getCC) ${CFLAGS} a.c -o a.o -c 2>/dev/null || return 1 + $(tc-getAR) r foo.a a.o 2>/dev/null || return 1 + + _check_if_lto_object "${tmpdir}/lto/foo.a" || return 1 + # It should search ${ED} if no arguments are passed, find + # the LTO'd foo.o, and strip it. + ED="${tmpdir}/lto" strip-lto-bytecode "${tmpdir}/lto/foo.a" + # foo.a should be a regular object here. + _check_if_lto_object "${tmpdir}/lto/foo.a" && return 1 + + return 0 + ) || ret=1 + tend ${ret} "Unexpected LTO object found" + + tbegin "whether a directory and file argument works" + ret=0 + ( + mkdir "${tmpdir}"/lto2 || die + + CC=gcc + CFLAGS="-O2 -flto" + + rm foo.a 2>/dev/null + + _create_test_progs + lto-guarantee-fat + $(tc-getCC) ${CFLAGS} "${tmpdir}"/lto/a.c -o "${tmpdir}"/lto/a.o -c 2>/dev/null || return 1 + $(tc-getAR) q foo.a a.o 2>/dev/null || return 1 + $(tc-getAR) q "${tmpdir}"/lto2/foo.a a.o 2>/dev/null || return 1 + + _check_if_lto_object "${tmpdir}/lto/foo.a" || return 1 + _check_if_lto_object "${tmpdir}/lto2/foo.a" || return 1 + # It should search ${ED} if no arguments are passed, find + # the LTO'd foo.o, and strip it. + ED="${tmpdir}/lto" strip-lto-bytecode "${tmpdir}/lto/foo.a" "${tmpdir}/lto2/foo.a" + # foo.a should be a regular object here. + _check_if_lto_object "${tmpdir}/lto/foo.a" && return 1 + _check_if_lto_object "${tmpdir}/lto2/foo.a" && return 1 + + return 0 + ) || ret=1 + tend ${ret} "Unexpected LTO object found" +} + +_repeat_tests_with_compilers() { + # Call test_lto_guarantee_fat and test_strip_lto_bytecode with + # various compilers and linkers. + for toolchain in gcc:ar clang:llvm-ar ; do + CC=${toolchain%:*} + AR=${toolchain#*:} + type -P ${CC} &>/dev/null || continue + type -P ${AR} &>/dev/null || continue + + for linker in gold bfd lld mold gold ; do + # lld doesn't support GCC LTO: https://github.com/llvm/llvm-project/issues/41791 + [[ ${CC} == gcc && ${linker} == lld ]] && continue + # Make sure the relevant linker is actually installed and usable. + LDFLAGS="-fuse-ld=${linker}" tc-ld-is-${linker} || continue + LDFLAGS="-fuse-ld=${linker}" test-compile 'c+ld' 'int main() { return 0; }' || continue + + test_lto_guarantee_fat + test_strip_lto_bytecode + done + done +} + +_repeat_mixed_tests_with_linkers() { + # Call test_mixed_objects_after_stripping with various linkers. + # + # Needs both GCC and Clang to test mixing their outputs. + if type -P gcc &>/dev/null && type -P clang &>/dev/null ; then + for linker in bfd lld mold gold ; do + # lld doesn't support GCC LTO: https://github.com/llvm/llvm-project/issues/41791 + [[ ${CC} == gcc && ${linker} == lld ]] && continue + # Make sure the relevant linker is actually installed and usable. + LDFLAGS="-fuse-ld=${linker}" tc-ld-is-${linker} || continue + LDFLAGS="-fuse-ld=${linker}" test-compile 'c+ld' 'int main() { return 0; }' || continue + + CC_1=gcc AR_1=ar + CC_2=clang AR_2=llvm-ar + test_mixed_objects_after_stripping + test_mixed_archives_after_stripping + done + fi +} + +# TODO: maybe test several files +mkdir -p "${tmpdir}/lto" || die +pushd "${tmpdir}/lto" >/dev/null || die +_create_test_progs +_repeat_tests_with_compilers +_repeat_mixed_tests_with_linkers +test_search_recursion +texit