File PKGBUILD of Package llvm-god-mlgo

# Maintainer: neycrol <330578697@qq.com>

pkgname=llvm-god-mlgo
pkgver=0
pkgrel=13
pkgdesc="LLVM/Clang/LLD (master) with 4-stage PGO + Polly + MLGO, installed under /opt/llvm-god"
arch=('x86_64')
url="https://github.com/llvm/llvm-project"
license=('custom:Apache-2.0 WITH LLVM-exception')
depends=('zlib' 'zstd' 'libxml2' 'ncurses' 'libedit' 'libffi' 'abseil-cpp' 'google-glog' 'gflags' 'protobuf' 'libelf' 'openssl')
optdepends=('tensorflow: optional for custom MLGO training workflows')
makedepends=('cmake' 'ninja' 'python' 'git' 'gcc' 'abseil-cpp' 'google-glog' 'gflags' 'protobuf' 'gtest' 'libelf' 'openssl')
options=('!strip' '!debug' '!lto')
source=('llvm-project.tar.gz' 'autofdo.tar.gz' 'perf_data_converter.tar.gz' 'autofdo-system-llvm.patch')
sha256sums=('SKIP' 'SKIP' 'SKIP' 'SKIP')

_find_llvm_src() {
  local d
  for d in "${srcdir}"/llvm-project*; do
    if [[ -d "${d}" && -f "${d}/llvm/CMakeLists.txt" ]]; then
      echo "${d}"
      return 0
    fi
  done
  return 1
}

_find_autofdo_src() {
  local d
  for d in "${srcdir}"/autofdo*; do
    if [[ -d "${d}" && -f "${d}/CMakeLists.txt" ]]; then
      echo "${d}"
      return 0
    fi
  done
  return 1
}

_find_perf_data_src() {
  local d
  for d in "${srcdir}"/perf_data_converter*; do
    if [[ -d "${d}" && -f "${d}/src/quipper/perf_reader.cc" ]]; then
      echo "${d}"
      return 0
    fi
  done
  return 1
}

_llvm_base_ver() {
  local _src _cm major minor patch
  _src=$(_find_llvm_src) || return 1
  _cm="${_src}/llvm/CMakeLists.txt"

  major=$(sed -n 's/^set(LLVM_VERSION_MAJOR[[:space:]]\+\([0-9]\+\)).*/\1/p' "${_cm}" | head -n1)
  minor=$(sed -n 's/^set(LLVM_VERSION_MINOR[[:space:]]\+\([0-9]\+\)).*/\1/p' "${_cm}" | head -n1)
  patch=$(sed -n 's/^set(LLVM_VERSION_PATCH[[:space:]]\+\([0-9]\+\)).*/\1/p' "${_cm}" | head -n1)

  if [[ -n "${major}" && -n "${minor}" && -n "${patch}" ]]; then
    echo "${major}.${minor}.${patch}"
  else
    echo "0.0.0"
  fi
}

pkgver() {
  local _base _date _rev
  _base=$(_llvm_base_ver)
  _date=$(date -u +%Y%m%d)

  if [[ -f "${srcdir}/._servicedata" ]]; then
    _rev=$(sed -n 's:.*<revision>\([0-9a-f]\+\)</revision>.*:\1:p' "${srcdir}/._servicedata" | head -n1)
    if [[ -z "${_rev}" ]]; then
      _rev=$(sed -n 's:.*revision="\([0-9a-f]\+\)".*:\1:p' "${srcdir}/._servicedata" | head -n1)
    fi
  fi

  if [[ -n "${_rev}" ]]; then
    echo "${_base}.${_date}.g${_rev:0:8}"
  else
    echo "${_base}.${_date}"
  fi
}

prepare() {
  local _src _autofdo _perf _f
  _src=$(_find_llvm_src) || {
    echo "llvm-project sources not found"
    return 1
  }
  _autofdo=$(_find_autofdo_src) || {
    echo "autofdo sources not found"
    return 1
  }
  _perf=$(_find_perf_data_src) || {
    echo "perf_data_converter sources not found"
    return 1
  }

  rm -rf "${srcdir}/build-stage1" \
         "${srcdir}/build-instrumented" \
         "${srcdir}/build-final" \
         "${srcdir}/build-autofdo" \
         "${srcdir}/profiles" \
         "${srcdir}/merged.profdata"

  rm -rf "${_autofdo}/third_party/perf_data_converter"
  cp -a "${_perf}" "${_autofdo}/third_party/perf_data_converter"

  # Upstream source uses third_party abseil include prefixes; switch to
  # system abseil headers to avoid protobuf/abseil API skew.
  while IFS= read -r -d '' _f; do
    sed -i 's#"third_party/abseil/absl/#"absl/#g' "${_f}"
  done < <(
    find "${_autofdo}" -type f ! -path "${_autofdo}/third_party/*" -print0 \
      | xargs -0 -r grep -lZ 'third_party/abseil/absl/' || true
  )

  cat > "${_autofdo}/absl_nonnull_compat.h" <<'EOF'
#ifndef AUTOFDO_ABSL_NONNULL_COMPAT_H_
#define AUTOFDO_ABSL_NONNULL_COMPAT_H_
#include "absl/base/nullability.h"
namespace absl {
template <typename T>
using Nonnull = T;
}
#endif  // AUTOFDO_ABSL_NONNULL_COMPAT_H_
EOF

  cat > "${_autofdo}/base/commandlineflags.h" <<'EOF'
#ifndef AUTOFDO_BASE_COMMANDLINEFLAGS_H_
#define AUTOFDO_BASE_COMMANDLINEFLAGS_H_
#include <gflags/gflags.h>
#endif  // AUTOFDO_BASE_COMMANDLINEFLAGS_H_
EOF

  # protobuf >= 33 removed Arena::CreateMessage from public API.
  sed -i 's/Arena::CreateMessage<PerfDataProto>(&arena_)/Arena::Create<PerfDataProto>(\&arena_)/' \
    "${_autofdo}/third_party/perf_data_converter/src/quipper/perf_reader.cc"

  # perf_data_converter expects include roots that differ from this integration.
  sed -i 's#"src/quipper/perf_data.pb.h"#"perf_data.pb.h"#g' \
    "${_autofdo}/third_party/perf_data_converter/src/quipper/compat/proto.h" \
    "${_autofdo}/third_party/perf_data_converter/src/quipper/address_context.h" \
    "${_autofdo}/third_party/perf_data_converter/src/quipper/address_context.cc" \
    "${_autofdo}/third_party/perf_data_converter/src/quipper/perf_data_utils.cc"
  sed -i 's#"quipper/perf_data.pb.h"#"perf_data.pb.h"#g' \
    "${_autofdo}/third_party/perf_data_converter/src/quipper/compat/proto.h" \
    "${_autofdo}/third_party/perf_data_converter/src/quipper/address_context.h" \
    "${_autofdo}/third_party/perf_data_converter/src/quipper/address_context.cc" \
    "${_autofdo}/third_party/perf_data_converter/src/quipper/perf_data_utils.cc"

  # LLVM trunk APIs now prefer llvm::Triple over std::string triple forms.
  sed -i 's/triple.getTriple()/triple/g' \
    "${_autofdo}/mini_disassembler.cc"

  patch -d "${_autofdo}" -Np0 -i "${srcdir}/autofdo-system-llvm.patch"
}

build() {
  local _src _autofdo _prefix _stage1 _inst _final _profile_dir _profile_data _autofdo_build
  local _host_cc _host_cxx _gcc_install_prefix _jobs _nproc _mem_kb _mem_jobs
  local _stage1_profile_glob _stage1_profile_lib _profile_tgt
  local _stage1_llvm_tblgen _stage1_clang_tblgen
  local _base_args _stage1_args _stage2_args _final_args
  local _god_cflags _god_ldflags

  _src=$(_find_llvm_src) || {
    echo "llvm-project sources not found"
    return 1
  }
  _autofdo=$(_find_autofdo_src) || {
    echo "autofdo sources not found"
    return 1
  }

  _prefix="/opt/llvm-god"
  _stage1="${srcdir}/build-stage1"
  _inst="${srcdir}/build-instrumented"
  _final="${srcdir}/build-final"
  _autofdo_build="${srcdir}/build-autofdo"
  _profile_dir="${srcdir}/profiles"
  _profile_data="${srcdir}/merged.profdata"
  _stage1_profile_glob="${_stage1}/lib/clang/*/lib/*/libclang_rt.profile.a"

  if [[ -x /opt/gcc-git-god/bin/gcc && -x /opt/gcc-git-god/bin/g++ ]]; then
    _host_cc="/opt/gcc-git-god/bin/gcc"
    _host_cxx="/opt/gcc-git-god/bin/g++"
    _gcc_install_prefix="/opt/gcc-git-god"
  else
    _host_cc="/usr/bin/gcc"
    _host_cxx="/usr/bin/g++"
    _gcc_install_prefix="/usr"
  fi

  _nproc=$(/usr/bin/getconf _NPROCESSORS_ONLN 2>/dev/null || echo 1)
  _mem_kb=$(awk '/MemTotal/ {print $2}' /proc/meminfo 2>/dev/null)
  if [[ -n "${_mem_kb}" && "${_mem_kb}" -gt 0 ]]; then
    # LLVM full builds are memory-heavy; reserve ~2.5 GiB per compile job.
    _mem_jobs=$(( _mem_kb / 2500000 ))
    (( _mem_jobs < 1 )) && _mem_jobs=1
  else
    _mem_jobs=${_nproc}
  fi
  _jobs=${_nproc}
  (( _jobs > _mem_jobs )) && _jobs=${_mem_jobs}

  _base_args=(
    -G Ninja
    -DCMAKE_BUILD_TYPE=Release
    -DCMAKE_INSTALL_PREFIX="${_prefix}"
    -DLLVM_TARGETS_TO_BUILD="X86;AArch64;ARM;RISCV"
    -DLLVM_ENABLE_RTTI=ON
    -DLLVM_ENABLE_FFI=ON
    -DLLVM_ENABLE_ZLIB=FORCE_ON
    -DLLVM_ENABLE_ZSTD=FORCE_ON
    -DLLVM_INCLUDE_TESTS=OFF
    -DLLVM_INCLUDE_BENCHMARKS=OFF
    -DLLVM_INCLUDE_EXAMPLES=OFF
    -DLLVM_ENABLE_BINDINGS=OFF
    -DCLANG_DEFAULT_PIE_ON_LINUX=ON
    -DLLVM_ENABLE_PROJECTS="clang;lld;polly;bolt"
    -DLLVM_ENABLE_RUNTIMES="compiler-rt;libcxx;libcxxabi;libunwind"
  )

  # Stage 1: Bootstrap clang/llvm-profdata with GCC God toolchain.
  _stage1_args=(
    -DCMAKE_C_COMPILER="${_host_cc}"
    -DCMAKE_CXX_COMPILER="${_host_cxx}"
    -DCMAKE_C_FLAGS="-O2 -pipe"
    -DCMAKE_CXX_FLAGS="-O2 -pipe"
    -DGCC_INSTALL_PREFIX="${_gcc_install_prefix}"
    -DUSE_DEPRECATED_GCC_INSTALL_PREFIX=ON
  )

  cmake -S "${_src}/llvm" -B "${_stage1}" "${_base_args[@]}" "${_stage1_args[@]}"
  # Stage1 must also provide compiler-rt profile runtime for Stage2
  # (-fprofile-generate links against libclang_rt.profile.a).
  ninja -C "${_stage1}" -j"${_jobs}" clang llvm-profdata runtimes

  _stage1_llvm_tblgen="${_stage1}/bin/llvm-tblgen"
  _stage1_clang_tblgen="${_stage1}/bin/clang-tblgen"
  if [[ ! -x "${_stage1_llvm_tblgen}" || ! -x "${_stage1_clang_tblgen}" ]]; then
    ninja -C "${_stage1}" -j"${_jobs}" llvm-tblgen clang-tblgen
  fi
  if [[ ! -x "${_stage1_llvm_tblgen}" || ! -x "${_stage1_clang_tblgen}" ]]; then
    echo "Stage 1 did not produce host tablegen tools"
    return 1
  fi

  if ! compgen -G "${_stage1_profile_glob}" > /dev/null; then
    _profile_tgt=$(ninja -C "${_stage1}" -t targets all \
      | sed -n 's/^\(clang_rt\.profile[^:[:space:]]*\):.*/\1/p' \
      | head -n1)
    if [[ -n "${_profile_tgt}" ]]; then
      ninja -C "${_stage1}" -j"${_jobs}" "${_profile_tgt}"
    fi
  fi

  _stage1_profile_lib=$(compgen -G "${_stage1_profile_glob}" | head -n1 || true)
  if [[ -z "${_stage1_profile_lib}" ]]; then
    echo "Stage 1 did not produce libclang_rt.profile.a (compiler-rt profile runtime)"
    return 1
  fi

  # Stage 2: Instrumented build for PGO profile generation.
  mkdir -p "${_profile_dir}"
  _stage2_args=(
    -DCMAKE_C_COMPILER="${_stage1}/bin/clang"
    -DCMAKE_CXX_COMPILER="${_stage1}/bin/clang++"
    "-DCMAKE_C_FLAGS=-O2 -pipe -fprofile-generate=${_profile_dir}"
    "-DCMAKE_CXX_FLAGS=-O2 -pipe -fprofile-generate=${_profile_dir}"
    -DGCC_INSTALL_PREFIX="${_gcc_install_prefix}"
    -DUSE_DEPRECATED_GCC_INSTALL_PREFIX=ON
  )

  cmake -S "${_src}/llvm" -B "${_inst}" "${_base_args[@]}" "${_stage2_args[@]}"
  ninja -C "${_inst}" -j"${_jobs}"

  # Stage 3: Minimal training pass to produce profile data for clang itself.
  # Use synthetic translation units instead of LLVM sources so this step
  # does not depend on generated headers/layout changes in-tree.
  rm -f "${_profile_data}"
  rm -f "${_profile_dir}"/*.profraw
  export LLVM_PROFILE_FILE="${_profile_dir}/code-%m.profraw"
  cat > "${srcdir}/.mlgo-train.c" <<'EOF'
int add(int a, int b) { return a + b; }
int main(void) { return add(1, 2) - 3; }
EOF
  cat > "${srcdir}/.mlgo-train.cpp" <<'EOF'
template <typename T>
T clamp_like(T v, T lo, T hi) {
  return (v < lo) ? lo : (v > hi ? hi : v);
}
int main() { return clamp_like<int>(7, 1, 5) - 5; }
EOF

  "${_inst}/bin/clang" -O2 -c "${srcdir}/.mlgo-train.c" -o /dev/null
  "${_inst}/bin/clang++" -O2 -std=gnu++20 -c "${srcdir}/.mlgo-train.cpp" -o /dev/null
  rm -f "${srcdir}/.mlgo-train.c" "${srcdir}/.mlgo-train.cpp"

  shopt -s nullglob
  local _raw=("${_profile_dir}"/*.profraw)
  shopt -u nullglob
  if (( ${#_raw[@]} == 0 )); then
    echo "PGO training produced no .profraw files"
    return 1
  fi
  "${_inst}/bin/llvm-profdata" merge -output="${_profile_data}" "${_raw[@]}"

  # Stage 4: Final optimized build (PGO + Polly + MLGO), LTO intentionally OFF.
  _god_cflags="-O3 -march=alderlake -mtune=alderlake -pipe -fno-semantic-interposition -mllvm -polly -mllvm -polly-vectorizer=stripmine"
  _god_ldflags="-Wl,--as-needed"
  local _libstdcpp_a _libgcc_a
  _libstdcpp_a="$("${_host_cxx}" -print-file-name=libstdc++.a 2>/dev/null || true)"
  _libgcc_a="$("${_host_cc}" -print-file-name=libgcc.a 2>/dev/null || true)"

  if [[ -n "${_libstdcpp_a}" && "${_libstdcpp_a}" != "libstdc++.a" && -f "${_libstdcpp_a}" ]]; then
    _god_ldflags="-static-libstdc++ ${_god_ldflags}"
  else
    msg2 "libstdc++.a not found for ${_host_cxx}; disabling -static-libstdc++ in stage4"
  fi
  if [[ -n "${_libgcc_a}" && "${_libgcc_a}" != "libgcc.a" && -f "${_libgcc_a}" ]]; then
    _god_ldflags="-static-libgcc ${_god_ldflags}"
  else
    msg2 "libgcc.a not found for ${_host_cc}; disabling -static-libgcc in stage4"
  fi
  _final_args=(
    # Emergency stability guard: final-stage runtimes bootstrap can fail on
    # some OBS workers (compiler ABI detection breaks under the self-host path).
    # Keep producing clang/lld/polly first; runtimes can be re-enabled after
    # allocator regression fix is fully validated.
    -DLLVM_ENABLE_RUNTIMES=
    -DLLVM_USE_HOST_TOOLS=ON
    -DLLVM_NATIVE_TOOL_DIR="${_stage1}/bin"
    -DLLVM_TABLEGEN="${_stage1_llvm_tblgen}"
    -DCLANG_TABLEGEN="${_stage1_clang_tblgen}"
    -DCMAKE_C_COMPILER="${_stage1}/bin/clang"
    -DCMAKE_CXX_COMPILER="${_stage1}/bin/clang++"
    "-DCMAKE_C_FLAGS=${_god_cflags}"
    "-DCMAKE_CXX_FLAGS=${_god_cflags}"
    "-DCMAKE_EXE_LINKER_FLAGS=${_god_ldflags}"
    "-DCMAKE_SHARED_LINKER_FLAGS=${_god_ldflags}"
    -DLLVM_PROFDATA_FILE="${_profile_data}"
    -DLLVM_ENABLE_LTO=OFF
    -DGCC_INSTALL_PREFIX="${_gcc_install_prefix}"
    -DUSE_DEPRECATED_GCC_INSTALL_PREFIX=ON
    -DLLVM_INLINE_ADVISOR_MODEL=Release
    -DLLVM_RA_EVICTION_ADVISOR_MODEL=Release
  )

  cmake -S "${_src}/llvm" -B "${_final}" "${_base_args[@]}" "${_final_args[@]}"
  ninja -C "${_final}" -j"${_jobs}"

  # Build AutoFDO's create_llvm_prof against the just-built LLVM tree.
  cmake -S "${_autofdo}" -B "${_autofdo_build}" \
    -G Ninja \
    -DCMAKE_BUILD_TYPE=Release \
    -DENABLE_TOOL=llvm \
    -DBUILD_SHARED=TRUE \
    -DLLVM_DIR="${_final}/lib/cmake/llvm" \
    -DCMAKE_POLICY_VERSION_MINIMUM=3.5
  ninja -C "${_autofdo_build}" -j"${_jobs}" create_llvm_prof
}

package() {
  local _src _autofdo _final _autofdo_build
  _src=$(_find_llvm_src) || {
    echo "llvm-project sources not found"
    return 1
  }
  _autofdo=$(_find_autofdo_src) || {
    echo "autofdo sources not found"
    return 1
  }
  _final="${srcdir}/build-final"
  _autofdo_build="${srcdir}/build-autofdo"

  DESTDIR="${pkgdir}" ninja -C "${_final}" install
  install -Dm644 "${_src}/LICENSE.TXT" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
  install -Dm644 "${_autofdo}/LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE.autofdo"

  install -Dm755 "${_autofdo_build}/create_llvm_prof" "${pkgdir}/opt/llvm-god/bin/create_llvm_prof"
  install -Dm755 /dev/stdin "${pkgdir}/opt/llvm-god/bin/llvm_propeller_profile" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
exec "$(dirname "$0")/create_llvm_prof" --format=propeller "$@"
EOF
}
openSUSE Build Service is sponsored by