diff --git a/.github/actions/setup-demo-deps/action.yml b/.github/actions/setup-demo-deps/action.yml index 1e040e9120..1c1b3fded7 100644 --- a/.github/actions/setup-demo-deps/action.yml +++ b/.github/actions/setup-demo-deps/action.yml @@ -13,24 +13,33 @@ inputs: runs: using: "composite" steps: - - name: Set up Python 3.12 on Windows - if: runner.os == 'Windows' + - name: Set up x64 host Python 3.12 on Windows + if: runner.os == 'Windows' && inputs.windows-arch != 'arm64' id: windows_python uses: actions/setup-python@v6 with: - # LLVM 19.1.3's MSVC LLDB embeds the 3.12.7 hosted-tool-cache prefix. - # A newer 3.12 patch is ABI-compatible, but LLDB combines its embedded - # stdlib path with that installation and cannot import encodings. Keep - # the MinGW demo environment on the normal latest 3.12 patch. This is - # the host-tools interpreter: use x64 on Windows ARM, where emulation - # retains access to wheels that upstream projects do not publish for - # native ARM64 Python. The target interpreter is installed below. - python-version: ${{ inputs.windows-abi == 'msvc' && '3.12.7' || '3.12' }} + # Demo tools embed Python 3.12. Cross-architecture lanes install their + # native target and LLDB interpreters below. Asking an ARM64 runner to + # unpack the x64 hosted-tool-cache payload can crash PowerShell's ARM64 + # CLR before emulation is involved, so that native runner is the sole + # exception. + python-version: "3.12" architecture: x64 # setup-python otherwise replaces PKG_CONFIG_PATH, hiding the native # vcpkg metadata installed by setup-deps from LLGo's runtime build. update-environment: false + - name: Set up LLDB Python 3.10 on Windows amd64 + if: runner.os == 'Windows' && inputs.windows-abi == 'msvc' && inputs.windows-arch == 'amd64' + id: windows_lldb_python_amd64 + uses: actions/setup-python@v6 + with: + # LLVM's official 21.1.8 x64 release is built against Python 3.10; + # installing Python 3.12 cannot satisfy its python310.dll import. + python-version: "3.10" + architecture: x64 + update-environment: false + - name: Set up target Python 3.12 on Windows if: runner.os == 'Windows' && inputs.windows-arch != 'amd64' id: windows_target_python @@ -48,7 +57,7 @@ runs: id: windows_lldb_python_386 uses: actions/setup-python@v6 with: - # LLVM's official 19.1.7 Win32 LLDB links python310.dll. Keep this + # LLVM's official 21.1.8 Win32 LLDB links python310.dll. Keep this # debugger runtime separate from the target Python 3.12 demo library. python-version: "3.10" architecture: x86 @@ -59,7 +68,7 @@ runs: id: windows_lldb_python uses: actions/setup-python@v6 with: - # LLVM's official 19.1.7 Windows-on-Arm LLDB links python311.dll. + # LLVM's official 21.1.8 Windows-on-Arm LLDB links python311.dll. # 3.11.9 is the final ARM64 artifact in actions/python-versions. python-version: "3.11.9" architecture: arm64 @@ -89,6 +98,18 @@ runs: } Add-Content -Encoding utf8 $env:GITHUB_ENV "LLGO_WINDOWS_LLDB_PYTHON_HOME=$prefix" + - name: Configure LLDB Python on Windows amd64 + if: runner.os == 'Windows' && inputs.windows-abi == 'msvc' && inputs.windows-arch == 'amd64' + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + $python = "${{ steps.windows_lldb_python_amd64.outputs.python-path }}" + $prefix = (& $python -c "import sys; print(sys.base_prefix)").Trim() + if (-not (Test-Path (Join-Path $prefix "python310.dll"))) { + throw "The native x64 LLDB Python runtime is incomplete: $prefix" + } + Add-Content -Encoding utf8 $env:GITHUB_ENV "LLGO_WINDOWS_LLDB_PYTHON_HOME=$prefix" + - name: Install cargs demo library on Unix if: runner.os != 'Windows' shell: bash @@ -256,7 +277,7 @@ runs: run: | $ErrorActionPreference = "Stop" - $hostPython = "${{ steps.windows_python.outputs.python-path }}" + $hostPython = "${{ steps.windows_python.outputs.python-path || steps.windows_target_python.outputs.python-path }}" $python = "${{ steps.windows_target_python.outputs.python-path || steps.windows_python.outputs.python-path }}" $packages = @("numpy") if ("${{ inputs.windows-arch }}" -eq "amd64") { diff --git a/.github/actions/setup-deps/action.yml b/.github/actions/setup-deps/action.yml index 815ee267c9..215bbbd5cf 100644 --- a/.github/actions/setup-deps/action.yml +++ b/.github/actions/setup-deps/action.yml @@ -4,7 +4,7 @@ inputs: llvm-version: description: "LLVM version to install" required: true - default: "19" + default: "21" install-llvm: description: "Whether to install LLVM" required: false @@ -39,30 +39,41 @@ runs: id: windows_llvm_cache uses: actions/cache/restore@v4 with: - path: ${{ runner.tool_cache }}\llgo-llvm\LLVM-19.1.3-Windows-X64 - key: llgo-llvm-19.1.3-full-target-windows-x64-1077267ca353a1e236055ed4b57d6a404d09c40b01bd27dc882870395cdc1aae + # Cache the hash-pinned release payload rather than its extracted tree. + # GitHub's Windows cache archive did not round-trip every executable + # from the LLVM development package, notably llvm-config.exe. + path: ${{ runner.tool_cache }}\llgo-llvm-archives\clang+llvm-21.1.8-x86_64-pc-windows-msvc.tar.xz + key: llgo-llvm-21.1.8-full-target-windows-x64-749d22f565fcd5718dbed06512572d0e5353b502c03fe1f7f17ee8b8aca21a47-archive-v1 - name: Install full-target Windows LLVM - if: runner.os == 'Windows' && inputs.install-llvm == 'true' && inputs.windows-abi == 'msvc' && steps.windows_llvm_cache.outputs.cache-hit != 'true' + if: runner.os == 'Windows' && inputs.install-llvm == 'true' && inputs.windows-abi == 'msvc' shell: pwsh env: - # 19.1.3 is the last LLVM 19 release with the upstream full-target - # MSVC archive. Later Windows archives omit WebAssembly and therefore - # cannot host LLGo's existing WASI cross-compilation path. - LLVM_VERSION: "19.1.3" - LLVM_ARCHIVE_SHA256: 1077267ca353a1e236055ed4b57d6a404d09c40b01bd27dc882870395cdc1aae + # Use the official development archive rather than the smaller Windows + # installer: LLGo needs llvm-config, headers, static libraries, and the + # WebAssembly backend from the same LLVM 21 build. + LLVM_VERSION: "21.1.8" + LLVM_ARCHIVE_SHA256: 749d22f565fcd5718dbed06512572d0e5353b502c03fe1f7f17ee8b8aca21a47 run: | $ErrorActionPreference = "Stop" - if ("${{ inputs.llvm-version }}" -ne "19") { + if ("${{ inputs.llvm-version }}" -ne "21") { throw "Unsupported Windows LLVM version: ${{ inputs.llvm-version }}" } - $archiveName = "LLVM-$env:LLVM_VERSION-Windows-X64.tar.xz" - $archive = Join-Path $env:RUNNER_TEMP $archiveName - $url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-$env:LLVM_VERSION/$archiveName" - & curl.exe --fail --location --retry 5 --retry-all-errors --output $archive $url - if ($LASTEXITCODE -ne 0) { - throw "Downloading LLVM failed with exit code $LASTEXITCODE" + $archiveName = "clang+llvm-$env:LLVM_VERSION-x86_64-pc-windows-msvc.tar.xz" + $archiveParent = Join-Path $env:RUNNER_TOOL_CACHE "llgo-llvm-archives" + $archive = Join-Path $archiveParent $archiveName + if ("${{ steps.windows_llvm_cache.outputs.cache-hit }}" -ne "true") { + New-Item -ItemType Directory -Force $archiveParent | Out-Null + $urlAsset = $archiveName.Replace("+", "%2B") + $url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-$env:LLVM_VERSION/$urlAsset" + & curl.exe --fail --location --retry 5 --retry-all-errors --output $archive $url + if ($LASTEXITCODE -ne 0) { + throw "Downloading LLVM failed with exit code $LASTEXITCODE" + } + } + if (-not (Test-Path $archive)) { + throw "The cached LLVM archive is missing: $archive" } $actual = (Get-FileHash -Algorithm SHA256 $archive).Hash if (-not $actual.Equals($env:LLVM_ARCHIVE_SHA256, [StringComparison]::OrdinalIgnoreCase)) { @@ -71,11 +82,17 @@ runs: $extractParent = Join-Path $env:RUNNER_TEMP "llgo-llvm-extract" $installParent = Join-Path $env:RUNNER_TOOL_CACHE "llgo-llvm" - $installRoot = Join-Path $installParent "LLVM-$env:LLVM_VERSION-Windows-X64" + $installRoot = Join-Path $installParent "clang+llvm-$env:LLVM_VERSION-x86_64-pc-windows-msvc" $sevenZip = Join-Path $env:ProgramFiles "7-Zip\7z.exe" if (-not (Test-Path $sevenZip)) { throw "7-Zip was not found at $sevenZip" } + if (Test-Path $extractParent) { + Remove-Item -LiteralPath $extractParent -Recurse -Force + } + if (Test-Path $installRoot) { + Remove-Item -LiteralPath $installRoot -Recurse -Force + } New-Item -ItemType Directory -Force $extractParent, $installParent | Out-Null & $sevenZip x -y "-o$extractParent" $archive if ($LASTEXITCODE -ne 0) { @@ -96,30 +113,30 @@ runs: throw "llvm-config.exe was not found in the full-target LLVM archive" } Move-Item -LiteralPath $extractedRoot.FullName -Destination $installRoot - Remove-Item -LiteralPath $archive, $tar.FullName + Remove-Item -LiteralPath $extractParent -Recurse -Force - name: Save full-target Windows LLVM if: runner.os == 'Windows' && inputs.install-llvm == 'true' && inputs.windows-abi == 'msvc' && steps.windows_llvm_cache.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: - path: ${{ runner.tool_cache }}\llgo-llvm\LLVM-19.1.3-Windows-X64 - key: llgo-llvm-19.1.3-full-target-windows-x64-1077267ca353a1e236055ed4b57d6a404d09c40b01bd27dc882870395cdc1aae + path: ${{ runner.tool_cache }}\llgo-llvm-archives\clang+llvm-21.1.8-x86_64-pc-windows-msvc.tar.xz + key: llgo-llvm-21.1.8-full-target-windows-x64-749d22f565fcd5718dbed06512572d0e5353b502c03fe1f7f17ee8b8aca21a47-archive-v1 - name: Cache target-native Windows LLVM support if: runner.os == 'Windows' && inputs.install-llvm == 'true' && inputs.windows-arch != 'amd64' id: windows_target_llvm_cache uses: actions/cache/restore@v4 with: - path: ${{ runner.tool_cache }}\llgo-compiler-rt\19.1.7\${{ inputs.windows-arch }} - key: ${{ inputs.windows-arch == 'arm64' && 'llgo-compiler-rt-19.1.7-windows-arm64-08d871c9a20caf0987ede1a4e44d489e28854c9f2fff3da98b486891f4856e05-native-lldb-v1' || 'llgo-compiler-rt-19.1.7-windows-386-3d7044fa307d3183e4ee2a98e12dd0523ced58f1d1c95c5e057694fb75beb51a-native-lldb-v1' }} + path: ${{ runner.tool_cache }}\llgo-compiler-rt\21.1.8\${{ inputs.windows-arch }} + key: ${{ inputs.windows-arch == 'arm64' && 'llgo-compiler-rt-21.1.8-windows-arm64-1a770a49460b73667c1750b79f90e7027ead468f966c5d30e180cae19f61d91e-native-lldb-v1' || 'llgo-compiler-rt-21.1.8-windows-386-0492c5c292e515ebbbc2533671e54c5895fe209e534812313f64257407233181-native-lldb-v1' }} - name: Install target-native Windows LLVM support if: runner.os == 'Windows' && inputs.install-llvm == 'true' && inputs.windows-arch != 'amd64' && steps.windows_target_llvm_cache.outputs.cache-hit != 'true' shell: pwsh env: - LLVM_VERSION: "19.1.7" + LLVM_VERSION: "21.1.8" LLVM_INSTALLER_ARCH: ${{ inputs.windows-arch == 'arm64' && 'woa64' || 'win32' }} - LLVM_INSTALLER_SHA256: ${{ inputs.windows-arch == 'arm64' && '08d871c9a20caf0987ede1a4e44d489e28854c9f2fff3da98b486891f4856e05' || '3d7044fa307d3183e4ee2a98e12dd0523ced58f1d1c95c5e057694fb75beb51a' }} + LLVM_INSTALLER_SHA256: ${{ inputs.windows-arch == 'arm64' && '1a770a49460b73667c1750b79f90e7027ead468f966c5d30e180cae19f61d91e' || '0492c5c292e515ebbbc2533671e54c5895fe209e534812313f64257407233181' }} LLVM_BUILTINS_ARCH: ${{ inputs.windows-arch == 'arm64' && 'aarch64' || 'i386' }} run: | $ErrorActionPreference = "Stop" @@ -146,7 +163,7 @@ runs: if (-not (Test-Path $sevenZip)) { throw "7-Zip was not found at $sevenZip" } - $builtinsRelative = "lib\clang\19\lib\windows\clang_rt.builtins-$env:LLVM_BUILTINS_ARCH.lib" + $builtinsRelative = "lib\clang\21\lib\windows\clang_rt.builtins-$env:LLVM_BUILTINS_ARCH.lib" $extractPaths = @( $builtinsRelative, "bin\lldb.exe", @@ -184,8 +201,8 @@ runs: if: runner.os == 'Windows' && inputs.install-llvm == 'true' && inputs.windows-arch != 'amd64' && steps.windows_target_llvm_cache.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: - path: ${{ runner.tool_cache }}\llgo-compiler-rt\19.1.7\${{ inputs.windows-arch }} - key: ${{ inputs.windows-arch == 'arm64' && 'llgo-compiler-rt-19.1.7-windows-arm64-08d871c9a20caf0987ede1a4e44d489e28854c9f2fff3da98b486891f4856e05-native-lldb-v1' || 'llgo-compiler-rt-19.1.7-windows-386-3d7044fa307d3183e4ee2a98e12dd0523ced58f1d1c95c5e057694fb75beb51a-native-lldb-v1' }} + path: ${{ runner.tool_cache }}\llgo-compiler-rt\21.1.8\${{ inputs.windows-arch }} + key: ${{ inputs.windows-arch == 'arm64' && 'llgo-compiler-rt-21.1.8-windows-arm64-1a770a49460b73667c1750b79f90e7027ead468f966c5d30e180cae19f61d91e-native-lldb-v1' || 'llgo-compiler-rt-21.1.8-windows-386-0492c5c292e515ebbbc2533671e54c5895fe209e534812313f64257407233181-native-lldb-v1' }} - name: Cache Windows MSVC dependencies if: runner.os == 'Windows' && inputs.install-llvm == 'true' && inputs.windows-abi == 'msvc' @@ -246,13 +263,13 @@ runs: if: runner.os == 'Windows' && inputs.install-llvm == 'true' && inputs.windows-abi == 'msvc' shell: pwsh env: - LLVM_VERSION: "19.1.3" - LLVM_TARGET_VERSION: "19.1.7" + LLVM_VERSION: "21.1.8" + LLVM_TARGET_VERSION: "21.1.8" VCPKG_MANIFEST_HASH: ${{ hashFiles('.github/windows/vcpkg/vcpkg.json') }} run: | $ErrorActionPreference = "Stop" - $llvmRoot = Join-Path $env:RUNNER_TOOL_CACHE "llgo-llvm\LLVM-$env:LLVM_VERSION-Windows-X64" + $llvmRoot = Join-Path $env:RUNNER_TOOL_CACHE "llgo-llvm\clang+llvm-$env:LLVM_VERSION-x86_64-pc-windows-msvc" if (-not (Test-Path (Join-Path $llvmRoot "bin\llvm-config.exe"))) { throw "The cached full-target LLVM installation is incomplete: $llvmRoot" } @@ -271,8 +288,7 @@ runs: throw "The native pkgconf executable was not installed at $pkgconf" } - # LLVM 19 predates the Visual Studio 18 STL compatibility floor. Keep - # this pinned lane on VS 2022 instead of selecting a newer toolset + # Keep this pinned lane on VS 2022 instead of selecting a newer toolset # merely because the runner image also happens to contain one. # LLGo itself and the LLVM Go bindings are x64 host programs even when # this job will later execute ARM64 or 386 output. Target SDK variables @@ -324,13 +340,15 @@ runs: # ordered static link as standard -L/-l flags so pkgconf does not treat # backslashes as escapes and Go's CGO flag validation remains intact. $ldflags = (@("-L$llvmLibDir") + @($libraryFlags)) -join " " - @" + $llvmPC = @" Name: LLVM ${{ inputs.llvm-version }} Description: LLVM ${{ inputs.llvm-version }} host compiler and linker flags Version: $env:LLVM_VERSION Cflags: $cflags Libs: $ldflags - "@ | Set-Content -Encoding ascii (Join-Path $pcDir "llvm-${{ inputs.llvm-version }}.pc") + "@ + $llvmPC | Set-Content -Encoding ascii (Join-Path $pcDir "llvm-${{ inputs.llvm-version }}.pc") + $llvmPC | Set-Content -Encoding ascii (Join-Path $pcDir "llvm.pc") # Upstream Windows LLVM release archives contain LLVMFileCheck.lib but # omit the FileCheck executable used by LLGo's IR regression tests. @@ -364,7 +382,7 @@ runs: & (Join-Path $llvmBin "clang++.exe") ` --target=x86_64-pc-windows-msvc ` -fuse-ld=lld ` - -fms-runtime-lib=dll ` + -fms-runtime-lib=static ` -std=c++17 ` "-I$llvmIncludeDir" ` $fileCheckSource ` @@ -406,6 +424,13 @@ runs: MSYS2_ARG_CONV_EXCL='*' exec "$pkgconfUnix" --dont-define-prefix "--with-path=$pcDirUnix" "--with-path=$vcpkgLibPCUnix" "--with-path=$vcpkgSharePCUnix" "`$@" "@ | Set-Content -Encoding ascii $pkgconfShell + foreach ($package in @("llvm", "llvm-${{ inputs.llvm-version }}")) { + & $pkgconfWrapper --modversion $package + if ($LASTEXITCODE -ne 0) { + throw "MSVC pkg-config metadata for $package is unavailable" + } + } + $gcLibFlags = ((& $pkgconfWrapper --libs bdw-gc) -join " ").Trim() -split '\s+' if ($LASTEXITCODE -ne 0) { throw "pkgconf failed to resolve bdw-gc libraries" @@ -422,11 +447,11 @@ runs: # Go's Windows external linker detects LLD through the compiler command # prefix. Without this option it emits a GNU ld linker script that the # native MSVC linker dialect cannot consume. - # The upstream full-target LLVM archive uses the dynamic MSVC runtime. - # Make host cgo objects match it so lld-link does not reject mixed /MT - # and /MD RuntimeLibrary directives. - Add-Content -Encoding utf8 $env:GITHUB_ENV "CC=clang --target=x86_64-pc-windows-msvc -fuse-ld=lld -fms-runtime-lib=dll" - Add-Content -Encoding utf8 $env:GITHUB_ENV "CXX=clang++ --target=x86_64-pc-windows-msvc -fuse-ld=lld -fms-runtime-lib=dll -std=c++17" + # The upstream LLVM 21 development archive builds its static libraries + # against the static MSVC runtime. Match it in FileCheck and all host + # cgo objects so lld-link sees one RuntimeLibrary directive. + Add-Content -Encoding utf8 $env:GITHUB_ENV "CC=clang --target=x86_64-pc-windows-msvc -fuse-ld=lld -fms-runtime-lib=static" + Add-Content -Encoding utf8 $env:GITHUB_ENV "CXX=clang++ --target=x86_64-pc-windows-msvc -fuse-ld=lld -fms-runtime-lib=static -std=c++17" Add-Content -Encoding utf8 $env:GITHUB_ENV "CGO_ENABLED=1" Add-Content -Encoding utf8 $env:GITHUB_ENV "LLGO_WINDOWS_TARGET_TRIPLE=$($target.Triple)" Add-Content -Encoding utf8 $env:GITHUB_ENV "LLGO_WINDOWS_VCPKG_TRIPLET=$($target.VcpkgTriplet)" @@ -445,6 +470,8 @@ runs: $lldbRoot = Join-Path $env:RUNNER_TOOL_CACHE "llgo-compiler-rt\$env:LLVM_TARGET_VERSION\$($target.GoArch)\lldb" $lldb = Join-Path $lldbRoot "bin\lldb.exe" Add-Content -Encoding utf8 $env:GITHUB_ENV "LLGO_WINDOWS_LLDB_PYTHONPATH=$(Join-Path $lldbRoot 'lib\site-packages')" + } else { + Add-Content -Encoding utf8 $env:GITHUB_ENV "LLGO_WINDOWS_LLDB_PYTHONPATH=$(Join-Path $llvmRoot 'lib\site-packages')" } if (-not (Test-Path $lldb)) { throw "lldb.exe was not found for Windows $($target.GoArch)" @@ -509,9 +536,10 @@ runs: llvm_major="${{ inputs.llvm-version }}" case "$llvm_major" in - 19) - llvm_version=19.1.7 - package_version=19.1.7-1 + 21) + llvm_version=21.1.8 + package_version=21.1.8-4 + runtime_package_version=21.1.8-1 ;; *) echo "unsupported Windows LLVM version: $llvm_major" >&2 @@ -540,26 +568,21 @@ runs: "llvm-$package_version" "llvm-libs-$package_version" "lld-$package_version" - "libc++-$package_version" - "libunwind-$package_version" - # LLVM 19 imports libxml2-2.dll. Current MSYS2 libxml2 releases use - # a newer DLL ABI, so keep the matching archived runtime package. - "libxml2-2.12.9-2" + "llvm-tools-$package_version" + "libc++-$runtime_package_version" + "libunwind-$runtime_package_version" ) urls=() for package in "${packages[@]}"; do urls+=("$repo/$prefix-$package-any.pkg.tar.zst") done - # LLVM 19 predates the compiler-runtime dependency rename from - # gcc-libs to cc-libs. libc++ supplies the required runtime; satisfy - # only the renamed package metadata while installing the pinned set. - assumed_cc_runtime="$prefix-cc-libs=$llvm_version" - pacman --noconfirm -U \ - --assume-installed "$assumed_cc_runtime" \ - "${urls[@]}" + # Keep only the mutually versioned LLVM split packages on archived + # URLs. Their ordinary runtime dependencies have no version constraint + # in the package metadata, so let pacman resolve current versions + # instead of relying on old package files that MSYS2 eventually prunes. + pacman --noconfirm -U "${urls[@]}" pacman --noconfirm -S --needed \ - --assume-installed "$assumed_cc_runtime" \ "$prefix-pkgconf" \ "$prefix-sqlite3" \ "$prefix-libuv" \ @@ -572,21 +595,28 @@ runs: "$prefix-make" \ make - for package in clang clang-libs compiler-rt llvm llvm-libs lld libc++ libunwind; do + for package in clang clang-libs compiler-rt llvm llvm-libs lld llvm-tools; do installed="$(pacman -Q "$prefix-$package" | awk '{print $2}')" if [[ "$installed" != "$package_version" ]]; then echo "expected $package $package_version, got $installed" >&2 exit 1 fi done - installed_libxml2="$(pacman -Q "$prefix-libxml2" | awk '{print $2}')" - if [[ "$installed_libxml2" != "2.12.9-2" ]]; then - echo "expected libxml2 2.12.9-2, got $installed_libxml2" >&2 - exit 1 - fi + for package in libc++ libunwind; do + installed="$(pacman -Q "$prefix-$package" | awk '{print $2}')" + if [[ "$installed" != "$runtime_package_version" ]]; then + echo "expected $package $runtime_package_version, got $installed" >&2 + exit 1 + fi + done + for package in libxml2 libuv gc libatomic_ops cjson; do + pacman -Q "$prefix-$package" + done - # MSYS2 does not ship llvm.pc. Generate the versioned metadata from - # its authoritative llvm-config in CLANG64's normal package root. + # MSYS2 does not ship llvm.pc. Generate both a selected-version file + # and a stable alias from its authoritative llvm-config in CLANG64's + # normal package root. Libraries can use the alias while this setup + # step remains responsible for enforcing the requested LLVM version. cflags="$(llvm-config --cflags)" ldflags="$(llvm-config --ldflags --libs all --system-libs)" cflags="${cflags//$'\r'/}" @@ -601,7 +631,7 @@ runs: "Version: $llvm_version" \ "Cflags: $cflags" \ "Libs: $ldflags" \ - > "$pc_dir/llvm-$llvm_major.pc" + | tee "$pc_dir/llvm-$llvm_major.pc" > "$pc_dir/llvm.pc" - name: Configure independent Windows MinGW profile if: runner.os == 'Windows' && inputs.install-llvm == 'true' && inputs.windows-abi == 'mingw' @@ -617,7 +647,12 @@ runs: $clang = Join-Path $mingwBin "clang.exe" $pkgconf = Join-Path $mingwBin "pkgconf.exe" $pcDir = Join-Path $mingwRoot "lib\pkgconfig" - foreach ($path in @($clang, $pkgconf, (Join-Path $pcDir "llvm-${{ inputs.llvm-version }}.pc"))) { + foreach ($path in @( + $clang, + $pkgconf, + (Join-Path $pcDir "llvm-${{ inputs.llvm-version }}.pc"), + (Join-Path $pcDir "llvm.pc") + )) { if (-not (Test-Path $path)) { throw "The MinGW profile is incomplete: $path was not found" } @@ -650,7 +685,7 @@ runs: MSYS2_ARG_CONV_EXCL='*' exec "$pkgconfUnix" --dont-define-prefix "--with-path=$profilePCUnix" "--with-path=$pcDirUnix" "`$@" "@ | Set-Content -Encoding ascii $pkgConfigShell - foreach ($package in @("llvm-${{ inputs.llvm-version }}", "bdw-gc", "libffi", "libuv", "openssl", "libcjson", "sqlite3", "zlib")) { + foreach ($package in @("llvm", "llvm-${{ inputs.llvm-version }}", "bdw-gc", "libffi", "libuv", "openssl", "libcjson", "sqlite3", "zlib")) { & $pkgConfig --modversion $package if ($LASTEXITCODE -ne 0) { throw "MinGW pkg-config metadata for $package is unavailable" @@ -664,7 +699,7 @@ runs: Add-Content -Encoding utf8 $env:GITHUB_ENV "LLGO_MINGW_HOST_PKGCONF=$pkgconf" Add-Content -Encoding utf8 $env:GITHUB_ENV "LLGO_MINGW_HOST_ROOT=$mingwRoot" if ("${{ inputs.windows-arch }}" -ne "amd64") { - $lldbRoot = Join-Path $env:RUNNER_TOOL_CACHE "llgo-compiler-rt\19.1.7\${{ inputs.windows-arch }}\lldb" + $lldbRoot = Join-Path $env:RUNNER_TOOL_CACHE "llgo-compiler-rt\21.1.8\${{ inputs.windows-arch }}\lldb" $lldb = Join-Path $lldbRoot "bin\lldb.exe" if (-not (Test-Path $lldb)) { throw "lldb.exe was not found for Windows ${{ inputs.windows-arch }}" diff --git a/.github/actions/setup-goreleaser/action.yml b/.github/actions/setup-goreleaser/action.yml index feb2cff60f..03dd185a4c 100644 --- a/.github/actions/setup-goreleaser/action.yml +++ b/.github/actions/setup-goreleaser/action.yml @@ -17,6 +17,9 @@ runs: steps: - name: Set up Go uses: ./.github/actions/setup-go + - name: Load LLVM payload contract + run: go run ./internal/llvmpayload/cmd/llvmpayload >> "$GITHUB_ENV" + shell: bash - name: Restore Linux sysroot cache id: cache-linux-sysroot uses: actions/cache/restore@v5 @@ -31,7 +34,7 @@ runs: uses: actions/cache/restore@v5 with: path: ${{ inputs.esp-clang-cache-path }} - key: esp-clang-${{ hashFiles('.github/workflows/download_esp_clang.sh') }} + key: esp-clang-${{ hashFiles('.github/workflows/download_esp_clang.sh', 'internal/llvmpayload/**') }} - name: Download ESP Clang (if cache miss) if: steps.cache-esp-clang.outputs.cache-hit != 'true' run: bash .github/workflows/download_esp_clang.sh @@ -41,7 +44,7 @@ runs: uses: actions/cache/save@v5 with: path: ${{ inputs.esp-clang-cache-path }} - key: esp-clang-${{ hashFiles('.github/workflows/download_esp_clang.sh') }} + key: esp-clang-${{ hashFiles('.github/workflows/download_esp_clang.sh', 'internal/llvmpayload/**') }} - name: Check file run: tree .sysroot shell: bash diff --git a/.github/actions/setup-llvm-mingw/action.yml b/.github/actions/setup-llvm-mingw/action.yml index 237701e12f..83dec050ed 100644 --- a/.github/actions/setup-llvm-mingw/action.yml +++ b/.github/actions/setup-llvm-mingw/action.yml @@ -7,7 +7,7 @@ inputs: llvm-version: description: "LLGo host LLVM major version" required: false - default: "19" + default: "21" runs: using: "composite" @@ -16,8 +16,8 @@ runs: if: runner.os == 'Windows' shell: pwsh run: | - if ("${{ inputs.llvm-version }}" -ne "19") { - throw "llvm-mingw 20250114 is paired with LLVM 19, not ${{ inputs.llvm-version }}" + if ("${{ inputs.llvm-version }}" -ne "21") { + throw "llvm-mingw 20251216 is paired with LLVM 21, not ${{ inputs.llvm-version }}" } if ("${{ inputs.windows-arch }}" -ne "386") { throw "The native llvm-mingw target currently qualifies windows/386, not windows/${{ inputs.windows-arch }}" @@ -28,8 +28,8 @@ runs: shell: bash run: | set -euo pipefail - if [[ "${{ inputs.llvm-version }}" != 19 ]]; then - echo "llvm-mingw 20250114 is paired with LLVM 19, not ${{ inputs.llvm-version }}" >&2 + if [[ "${{ inputs.llvm-version }}" != 21 ]]; then + echo "llvm-mingw 20251216 is paired with LLVM 21, not ${{ inputs.llvm-version }}" >&2 exit 1 fi case "${{ inputs.windows-arch }}" in @@ -45,15 +45,15 @@ runs: id: llvm_mingw_cache uses: actions/cache/restore@v4 with: - path: ${{ runner.tool_cache }}\llvm-mingw\20250114\ucrt-x86_64 - key: llvm-mingw-20250114-ucrt-x86_64-9395d95c089611e6cd728c9a07219ffc5ac706e0c719833a2f7e64a706a81541 + path: ${{ runner.tool_cache }}\llvm-mingw\20251216\ucrt-x86_64 + key: llvm-mingw-20251216-ucrt-x86_64-2d96a4b758f7f8deaec5065833fe025aa53cfc5f704d0524002510984da0ccf4 - name: Install llvm-mingw if: runner.os == 'Windows' && steps.llvm_mingw_cache.outputs.cache-hit != 'true' shell: pwsh env: - LLVM_MINGW_VERSION: "20250114" - LLVM_MINGW_SHA256: 9395d95c089611e6cd728c9a07219ffc5ac706e0c719833a2f7e64a706a81541 + LLVM_MINGW_VERSION: "20251216" + LLVM_MINGW_SHA256: 2d96a4b758f7f8deaec5065833fe025aa53cfc5f704d0524002510984da0ccf4 run: | $ErrorActionPreference = "Stop" $asset = "llvm-mingw-$env:LLVM_MINGW_VERSION-ucrt-x86_64.zip" @@ -84,8 +84,8 @@ runs: if: runner.os == 'Windows' && steps.llvm_mingw_cache.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: - path: ${{ runner.tool_cache }}\llvm-mingw\20250114\ucrt-x86_64 - key: llvm-mingw-20250114-ucrt-x86_64-9395d95c089611e6cd728c9a07219ffc5ac706e0c719833a2f7e64a706a81541 + path: ${{ runner.tool_cache }}\llvm-mingw\20251216\ucrt-x86_64 + key: llvm-mingw-20251216-ucrt-x86_64-2d96a4b758f7f8deaec5065833fe025aa53cfc5f704d0524002510984da0ccf4 - name: Restore llvm-mingw target dependencies if: runner.os == 'Windows' @@ -93,7 +93,7 @@ runs: uses: actions/cache/restore@v4 with: path: ${{ runner.tool_cache }}\llgo-mingw-vcpkg\${{ hashFiles('.github/windows/vcpkg/vcpkg.json') }}\386 - key: llgo-llvm-mingw-20250114-ucrt-386-${{ hashFiles('.github/windows/vcpkg/vcpkg.json') }} + key: llgo-llvm-mingw-20251216-ucrt-386-${{ hashFiles('.github/windows/vcpkg/vcpkg.json') }} - name: Build llvm-mingw target dependencies if: runner.os == 'Windows' && steps.llvm_mingw_vcpkg_cache.outputs.cache-hit != 'true' @@ -106,7 +106,7 @@ runs: $sourceRoot = Join-Path $env:RUNNER_TEMP "llgo-mingw-vcpkg-source" $installRoot = Join-Path $env:RUNNER_TOOL_CACHE "llgo-mingw-vcpkg\$env:VCPKG_MANIFEST_HASH\386" $manifestRoot = Join-Path $env:GITHUB_WORKSPACE ".github\windows\vcpkg" - $llvmMingwRoot = Join-Path $env:RUNNER_TOOL_CACHE "llvm-mingw\20250114\ucrt-x86_64" + $llvmMingwRoot = Join-Path $env:RUNNER_TOOL_CACHE "llvm-mingw\20251216\ucrt-x86_64" $llvmMingwBin = Join-Path $llvmMingwRoot "bin" $env:PATH = "$llvmMingwBin;$env:PATH" @@ -137,7 +137,7 @@ runs: uses: actions/cache/save@v4 with: path: ${{ runner.tool_cache }}\llgo-mingw-vcpkg\${{ hashFiles('.github/windows/vcpkg/vcpkg.json') }}\386 - key: llgo-llvm-mingw-20250114-ucrt-386-${{ hashFiles('.github/windows/vcpkg/vcpkg.json') }} + key: llgo-llvm-mingw-20251216-ucrt-386-${{ hashFiles('.github/windows/vcpkg/vcpkg.json') }} - name: Configure llvm-mingw target if: runner.os == 'Windows' @@ -146,7 +146,7 @@ runs: VCPKG_MANIFEST_HASH: ${{ hashFiles('.github/windows/vcpkg/vcpkg.json') }} run: | $ErrorActionPreference = "Stop" - $llvmMingwRoot = Join-Path $env:RUNNER_TOOL_CACHE "llvm-mingw\20250114\ucrt-x86_64" + $llvmMingwRoot = Join-Path $env:RUNNER_TOOL_CACHE "llvm-mingw\20251216\ucrt-x86_64" $llvmMingwBin = Join-Path $llvmMingwRoot "bin" $llvmMingwRuntimeBin = Join-Path $llvmMingwRoot "i686-w64-mingw32\bin" $compiler = Join-Path $llvmMingwBin "i686-w64-mingw32-clang.exe" @@ -237,24 +237,24 @@ runs: id: cross_host_llvm_mingw_cache uses: actions/cache/restore@v4 with: - path: ${{ runner.tool_cache }}/llvm-mingw/20250114/ucrt-${{ runner.os }}-${{ runner.arch }} - key: llvm-mingw-20250114-ucrt-${{ runner.os }}-${{ runner.arch }}-${{ runner.os == 'macOS' && '80b2e7ade71ba2dfe9e8d27fe47ae5738b1fb8d34e057faa2beef3070392f2d6' || 'a16f52dee819797248e6c7d63b8b1e50a92119f45767ecd8e9633d1733b896e2' }} + path: ${{ runner.tool_cache }}/llvm-mingw/20251216/ucrt-${{ runner.os }}-${{ runner.arch }} + key: llvm-mingw-20251216-ucrt-${{ runner.os }}-${{ runner.arch }}-${{ runner.os == 'macOS' && '38bb5e48ec3e0ce95fb901f4efff0bd6ffa966d1b32539a6510af816a959d160' || 'db561734a05eb4a0695d5e901aadd6c1cda74bba4c9e102b677f81c6fd59849a' }} - name: Install cross-host llvm-mingw if: runner.os != 'Windows' && env.LLGO_MINGW_CROSS_ROOT == '' && steps.cross_host_llvm_mingw_cache.outputs.cache-hit != 'true' shell: bash env: - LLVM_MINGW_VERSION: "20250114" + LLVM_MINGW_VERSION: "20251216" run: | set -euo pipefail case "$RUNNER_OS/$RUNNER_ARCH" in Linux/X64) - asset="llvm-mingw-${LLVM_MINGW_VERSION}-ucrt-ubuntu-20.04-x86_64.tar.xz" - sha256=a16f52dee819797248e6c7d63b8b1e50a92119f45767ecd8e9633d1733b896e2 + asset="llvm-mingw-${LLVM_MINGW_VERSION}-ucrt-ubuntu-22.04-x86_64.tar.xz" + sha256=db561734a05eb4a0695d5e901aadd6c1cda74bba4c9e102b677f81c6fd59849a ;; macOS/ARM64|macOS/X64) asset="llvm-mingw-${LLVM_MINGW_VERSION}-ucrt-macos-universal.tar.xz" - sha256=80b2e7ade71ba2dfe9e8d27fe47ae5738b1fb8d34e057faa2beef3070392f2d6 + sha256=38bb5e48ec3e0ce95fb901f4efff0bd6ffa966d1b32539a6510af816a959d160 ;; *) echo "llvm-mingw has no qualified $RUNNER_OS/$RUNNER_ARCH host archive" >&2 @@ -288,8 +288,8 @@ runs: if: runner.os != 'Windows' && env.LLGO_MINGW_CROSS_ROOT == '' && steps.cross_host_llvm_mingw_cache.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: - path: ${{ runner.tool_cache }}/llvm-mingw/20250114/ucrt-${{ runner.os }}-${{ runner.arch }} - key: llvm-mingw-20250114-ucrt-${{ runner.os }}-${{ runner.arch }}-${{ runner.os == 'macOS' && '80b2e7ade71ba2dfe9e8d27fe47ae5738b1fb8d34e057faa2beef3070392f2d6' || 'a16f52dee819797248e6c7d63b8b1e50a92119f45767ecd8e9633d1733b896e2' }} + path: ${{ runner.tool_cache }}/llvm-mingw/20251216/ucrt-${{ runner.os }}-${{ runner.arch }} + key: llvm-mingw-20251216-ucrt-${{ runner.os }}-${{ runner.arch }}-${{ runner.os == 'macOS' && '38bb5e48ec3e0ce95fb901f4efff0bd6ffa966d1b32539a6510af816a959d160' || 'db561734a05eb4a0695d5e901aadd6c1cda74bba4c9e102b677f81c6fd59849a' }} - name: Restore cross-host target dependencies if: runner.os != 'Windows' @@ -297,7 +297,7 @@ runs: uses: actions/cache/restore@v4 with: path: ${{ runner.tool_cache }}/llgo-mingw-vcpkg/${{ hashFiles('.github/windows/vcpkg/vcpkg.json') }}/${{ runner.os }}-${{ runner.arch }}/${{ inputs.windows-arch }} - key: llgo-llvm-mingw-20250114-ucrt-${{ runner.os }}-${{ runner.arch }}-${{ inputs.windows-arch }}-${{ hashFiles('.github/windows/vcpkg/vcpkg.json') }} + key: llgo-llvm-mingw-20251216-ucrt-${{ runner.os }}-${{ runner.arch }}-${{ inputs.windows-arch }}-${{ hashFiles('.github/windows/vcpkg/vcpkg.json') }} - name: Build cross-host target dependencies if: runner.os != 'Windows' && steps.cross_host_vcpkg_cache.outputs.cache-hit != 'true' @@ -316,7 +316,7 @@ runs: source_root="$RUNNER_TEMP/llgo-mingw-vcpkg-source" install_root="$RUNNER_TOOL_CACHE/llgo-mingw-vcpkg/$VCPKG_MANIFEST_HASH/${RUNNER_OS}-${RUNNER_ARCH}/${{ inputs.windows-arch }}" manifest_root="$GITHUB_WORKSPACE/.github/windows/vcpkg" - llvm_mingw_root="${LLGO_MINGW_CROSS_ROOT:-$RUNNER_TOOL_CACHE/llvm-mingw/20250114/ucrt-${RUNNER_OS}-${RUNNER_ARCH}}" + llvm_mingw_root="${LLGO_MINGW_CROSS_ROOT:-$RUNNER_TOOL_CACHE/llvm-mingw/20251216/ucrt-${RUNNER_OS}-${RUNNER_ARCH}}" export PATH="$llvm_mingw_root/bin:$PATH" # Do not let target-specific compiler variables from the invoking job @@ -347,7 +347,7 @@ runs: uses: actions/cache/save@v4 with: path: ${{ runner.tool_cache }}/llgo-mingw-vcpkg/${{ hashFiles('.github/windows/vcpkg/vcpkg.json') }}/${{ runner.os }}-${{ runner.arch }}/${{ inputs.windows-arch }} - key: llgo-llvm-mingw-20250114-ucrt-${{ runner.os }}-${{ runner.arch }}-${{ inputs.windows-arch }}-${{ hashFiles('.github/windows/vcpkg/vcpkg.json') }} + key: llgo-llvm-mingw-20251216-ucrt-${{ runner.os }}-${{ runner.arch }}-${{ inputs.windows-arch }}-${{ hashFiles('.github/windows/vcpkg/vcpkg.json') }} - name: Configure cross-host llvm-mingw target if: runner.os != 'Windows' @@ -374,7 +374,7 @@ runs: ;; esac - llvm_mingw_root="${LLGO_MINGW_CROSS_ROOT:-$RUNNER_TOOL_CACHE/llvm-mingw/20250114/ucrt-${RUNNER_OS}-${RUNNER_ARCH}}" + llvm_mingw_root="${LLGO_MINGW_CROSS_ROOT:-$RUNNER_TOOL_CACHE/llvm-mingw/20251216/ucrt-${RUNNER_OS}-${RUNNER_ARCH}}" target_bin="$llvm_mingw_root/bin" runtime_bin="$llvm_mingw_root/$prefix/bin" compiler="$target_bin/$prefix-clang" diff --git a/.github/actions/test-windows-cross/action.yml b/.github/actions/test-windows-cross/action.yml index ead139a597..5523f9d4a0 100644 --- a/.github/actions/test-windows-cross/action.yml +++ b/.github/actions/test-windows-cross/action.yml @@ -10,7 +10,7 @@ inputs: llvm-version: description: "LLGo host LLVM major version" required: false - default: "19" + default: "21" runs: using: "composite" diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 3b6f26bc5f..7c0c2ca660 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -111,7 +111,7 @@ jobs: - name: Install dependencies uses: ./.github/actions/setup-deps with: - llvm-version: 19 + llvm-version: 21 windows-abi: ${{ matrix.windows_abi || 'msvc' }} windows-arch: ${{ matrix.windows_arch || 'amd64' }} @@ -123,6 +123,11 @@ jobs: - name: Build host compilers for a cross-architecture benchmark if: runner.os == 'Windows' && matrix.windows_arch != 'amd64' shell: bash + # The checked-out baseline may still default its Go LLVM binding to an + # older major. Build both benchmark compilers against the LLVM 21 host + # toolchain installed for this job. + env: + GOFLAGS: -tags=llvm21 run: | set -euo pipefail mkdir -p "$GITHUB_WORKSPACE/.benchmark" @@ -201,7 +206,9 @@ jobs: # base contains Windows support. continue-on-error: ${{ startsWith(matrix.id, 'windows-') }} shell: bash + # Compare both revisions with the same LLVM 21 host toolchain. env: + GOFLAGS: -tags=llvm21 LLGO_BENCHMARK_PREBUILT_LLGO: ${{ matrix.windows_arch && matrix.windows_arch != 'amd64' && '.benchmark/base-llgo.exe' || '' }} run: | benchmark/baseline/run.sh \ diff --git a/.github/workflows/build-cache.yml b/.github/workflows/build-cache.yml index d27b0eddb7..cbc0663ec4 100644 --- a/.github/workflows/build-cache.yml +++ b/.github/workflows/build-cache.yml @@ -22,14 +22,14 @@ jobs: matrix: include: - os: macos-latest - llvm: 19 + llvm: 21 - os: ubuntu-latest - llvm: 19 + llvm: 21 - os: windows-2022 - llvm: 19 + llvm: 21 windows_abi: msvc - os: windows-2022 - llvm: 19 + llvm: 21 windows_abi: mingw runs-on: ${{matrix.os}} steps: diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index 2d6ad6c51b..a4cd487b62 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -153,7 +153,7 @@ jobs: set -e set -x source doc/_readme/scripts/install_ubuntu.sh - echo "PATH=/usr/lib/llvm-19/bin:$PATH" >> $GITHUB_ENV + echo "PATH=/usr/lib/llvm-21/bin:$PATH" >> $GITHUB_ENV - name: Set up Python 3.12 on Windows if: startsWith(matrix.os, 'windows') diff --git a/.github/workflows/download_esp_clang.sh b/.github/workflows/download_esp_clang.sh index 8f0d99a60e..2cf8fee96c 100755 --- a/.github/workflows/download_esp_clang.sh +++ b/.github/workflows/download_esp_clang.sh @@ -1,9 +1,20 @@ #!/bin/bash -set -e +set -euo pipefail -ESP_CLANG_VERSION="19.1.2_20250905-3" -BASE_URL="https://github.com/goplus/espressif-llvm-project-prebuilt/releases/download/${ESP_CLANG_VERSION}" LLVM_LICENSE="LICENSES/XGo-LLVM-Apache-2.0-WITH-LLVM-exception.txt" +payload_env=$(mktemp) +archive_file="" +cleanup() { + rm -f "${payload_env}" + if [[ -n "${archive_file}" ]]; then + rm -f "${archive_file}" + fi +} +trap cleanup EXIT + +go run ./internal/llvmpayload/cmd/llvmpayload > "${payload_env}" +# shellcheck disable=SC1090 +source "${payload_env}" get_esp_clang_platform() { local platform="$1" @@ -34,22 +45,56 @@ get_esp_clang_platform() { get_filename() { local platform="$1" - local platform_suffix=$(get_esp_clang_platform "${platform}") + local platform_suffix + platform_suffix=$(get_esp_clang_platform "${platform}") echo "clang-esp-${ESP_CLANG_VERSION}-${platform_suffix}.tar.xz" } +get_checksum() { + case "$1" in + "darwin-amd64") echo "${ESP_CLANG_SHA256_DARWIN_AMD64}" ;; + "darwin-arm64") echo "${ESP_CLANG_SHA256_DARWIN_ARM64}" ;; + "linux-amd64") echo "${ESP_CLANG_SHA256_LINUX_AMD64}" ;; + "linux-arm64") echo "${ESP_CLANG_SHA256_LINUX_ARM64}" ;; + *) echo "Error: Unsupported checksum platform: $1" >&2; exit 1 ;; + esac +} + +verify_checksum() { + local filename="$1" + local expected="$2" + local actual + if command -v sha256sum >/dev/null 2>&1; then + actual=$(sha256sum "${filename}" | awk '{print $1}') + else + actual=$(shasum -a 256 "${filename}" | awk '{print $1}') + fi + if [[ "${actual}" != "${expected}" ]]; then + echo "Error: checksum mismatch for ${filename}: got ${actual}, want ${expected}" >&2 + exit 1 + fi +} + download_and_extract() { local platform="$1" local os="${platform%-*}" local arch="${platform##*-}" - local filename=$(get_filename "${platform}") - local download_url="${BASE_URL}/${filename}" + local filename + local checksum + filename=$(get_filename "${platform}") + checksum=$(get_checksum "${platform}") + local download_url="${ESP_CLANG_BASE_URL}/${filename}" echo "Downloading ESP Clang for ${platform}..." echo " URL: ${download_url}" + archive_file=$(mktemp) + curl -fsSL "${download_url}" -o "${archive_file}" + verify_checksum "${archive_file}" "${checksum}" mkdir -p ".sysroot/${os}/${arch}/crosscompile/clang" - curl -fsSL "${download_url}" | tar -xJ -C ".sysroot/${os}/${arch}/crosscompile/clang" --strip-components=1 + tar -xJf "${archive_file}" -C ".sysroot/${os}/${arch}/crosscompile/clang" --strip-components=1 + rm -f "${archive_file}" + archive_file="" if [[ ! -f ".sysroot/${os}/${arch}/crosscompile/clang/bin/clang++" ]]; then echo "Error: clang++ not found in ${platform} toolchain" @@ -71,6 +116,10 @@ if [[ ! -f "${LLVM_LICENSE}" ]]; then exit 1 fi +if [[ -n "${GITHUB_ENV:-}" ]]; then + echo "LLGO_LLVM_MAJOR=${ESP_CLANG_LLVM_MAJOR}" >> "${GITHUB_ENV}" +fi + for platform in "darwin-amd64" "darwin-arm64" "linux-amd64" "linux-arm64"; do download_and_extract "${platform}" done diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index bf3419cbf9..24405391db 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -28,14 +28,14 @@ jobs: # release. User-code compatibility is covered by LLGo's test matrix. include: - os: macos-latest - llvm: 19 + llvm: 21 - os: ubuntu-latest - llvm: 19 + llvm: 21 - os: windows-2022 - llvm: 19 + llvm: 21 windows_abi: msvc - os: windows-2022 - llvm: 19 + llvm: 21 windows_abi: mingw runs-on: ${{matrix.os}} steps: @@ -170,7 +170,7 @@ jobs: - name: Install dependencies uses: ./.github/actions/setup-deps with: - llvm-version: 19 + llvm-version: 21 - name: Set up Go uses: ./.github/actions/setup-go @@ -189,7 +189,7 @@ jobs: run: | set -euo pipefail - llvm_config="$(command -v llvm-config || command -v llvm-config-19)" + llvm_config="$(command -v llvm-config || command -v llvm-config-21)" cmake -S ltoplugin -B ltoplugin/build \ -DLLVM_DIR="$("${llvm_config}" --cmakedir)" \ -DCMAKE_BUILD_TYPE=Release @@ -251,7 +251,7 @@ jobs: - name: Install dependencies uses: ./.github/actions/setup-deps with: - llvm-version: 19 + llvm-version: 21 - name: Set up Go uses: ./.github/actions/setup-go diff --git a/.github/workflows/goroot.yml b/.github/workflows/goroot.yml index abcdf8dd14..c6e14820cf 100644 --- a/.github/workflows/goroot.yml +++ b/.github/workflows/goroot.yml @@ -58,7 +58,7 @@ jobs: - name: Install dependencies uses: ./.github/actions/setup-deps with: - llvm-version: 19 + llvm-version: 21 windows-abi: ${{ matrix.windows_abi }} windows-arch: ${{ matrix.windows_arch }} diff --git a/.github/workflows/llgo.yml b/.github/workflows/llgo.yml index a3157725f3..a358183c56 100644 --- a/.github/workflows/llgo.yml +++ b/.github/workflows/llgo.yml @@ -25,36 +25,36 @@ jobs: # native toolchain profile, always with .go-version. include: - os: ubuntu-latest - llvm: 19 + llvm: 21 - os: macos-latest - llvm: 19 + llvm: 21 - os: windows-2022 - llvm: 19 + llvm: 21 windows_abi: msvc windows_arch: amd64 host_go_arch: x64 - os: windows-2022 - llvm: 19 + llvm: 21 windows_abi: mingw windows_arch: amd64 host_go_arch: x64 - os: windows-11-arm - llvm: 19 + llvm: 21 windows_abi: msvc windows_arch: arm64 host_go_arch: x64 - os: windows-11-arm - llvm: 19 + llvm: 21 windows_abi: mingw windows_arch: arm64 host_go_arch: arm64 - os: windows-2022 - llvm: 19 + llvm: 21 windows_abi: msvc windows_arch: "386" host_go_arch: x64 - os: windows-2022 - llvm: 19 + llvm: 21 windows_abi: mingw windows_arch: "386" host_go_arch: x64 @@ -202,14 +202,27 @@ jobs: fi done fi - # esptool inspects target firmware but runs on the CI host. For a - # cross-architecture Windows lane, use the x64 host Python so pip - # can install its native dependencies without cross-building them. - if [[ "$RUNNER_OS" == "Windows" && -n "${LLGO_WINDOWS_PYTHON_HOME:-}" ]]; then - host_python_home="$(cygpath -u "$LLGO_WINDOWS_PYTHON_HOME")" - export PATH="$host_python_home:$PATH" + # esptool inspects target firmware but runs on the CI host. PyPI has + # no Windows ARM64 cryptography wheel, and this image-info test does + # not import espsecure (the only esptool component that uses it). + # Install the pure test dependency closure instead of trying to + # build cryptography and OpenSSL on the runner. The 386 lane uses + # x64 host Python and its normal wheels. + if [[ "$RUNNER_OS" == "Windows" && "${{ matrix.windows_arch }}" == "arm64" ]]; then + python -m pip install --no-deps esptool==5.1.0 + python -m pip install \ + 'bitstring!=4.2.0,>=3.1.6' \ + 'pyserial>=3.3' \ + 'reedsolo<1.8,>=1.5.3' \ + 'PyYAML>=5.1' \ + intelhex rich_click 'click<9' + else + if [[ "$RUNNER_OS" == "Windows" && -n "${LLGO_WINDOWS_PYTHON_HOME:-}" ]]; then + host_python_home="$(cygpath -u "$LLGO_WINDOWS_PYTHON_HOME")" + export PATH="$host_python_home:$PATH" + fi + python -m pip install esptool==5.1.0 fi - python -m pip install esptool==5.1.0 cd _demo/embed chmod +x test_esp32c3_startup.sh args=() @@ -241,8 +254,8 @@ jobs: if: ${{ matrix.os == 'windows-2022' && matrix.windows_abi == 'mingw' && matrix.windows_arch == 'amd64' }} shell: msys2 {0} env: - MSYS2_LLDB_PACKAGE_VERSION: "19.1.7-1" - MSYS2_PYTHON_PACKAGE_VERSION: "3.12.8-2" + MSYS2_LLDB_PACKAGE_VERSION: "21.1.8-3" + MSYS2_PYTHON_PACKAGE_VERSION: "3.14.6-2" run: | set -euo pipefail repo=https://repo.msys2.org/mingw/clang64 @@ -279,9 +292,11 @@ jobs: $env:PYTHONIOENCODING = "utf-8" $env:PATH = "$env:LLGO_WINDOWS_LLDB_PYTHON_HOME;$env:PATH" } else { - # The upstream x64 LLVM archive records its build runner's Python - # home. setup-demo-deps installs that exact Python patch release. - $env:PYTHONHOME = $env:LLGO_WINDOWS_PYTHON_HOME + # LLVM's official x64 release links python310.dll even though the + # demos embed Python 3.12. Keep the debugger runtime independent. + $env:PYTHONHOME = $env:LLGO_WINDOWS_LLDB_PYTHON_HOME + $env:PYTHONPATH = $env:LLGO_WINDOWS_LLDB_PYTHONPATH + $env:PATH = "$env:LLGO_WINDOWS_LLDB_PYTHON_HOME;$env:PATH" } Write-Host "Test lldb with llgo plugin on ${{matrix.os}} with LLVM ${{matrix.llvm}}" bash cmd/llgo/lldbtest/runtest.sh -v @@ -374,14 +389,14 @@ jobs: # runs take about 25 minutes each and would exceed 30 minutes combined. include: - os: ubuntu-latest - llvm: 19 + llvm: 21 go_version: "1.20-1.26" lane: compatibility shard_index: "0" shard_total: "1" check_symbols: true - os: ubuntu-latest - llvm: 19 + llvm: 21 go_version: "1.27" lane: full shard_index: "0" @@ -389,7 +404,7 @@ jobs: check_symbols: true std_buildmodes: true - os: ubuntu-latest - llvm: 19 + llvm: 21 go_version: "1.27" lane: full shard_index: "1" @@ -397,14 +412,14 @@ jobs: check_symbols: true std_buildmodes: true - os: macos-latest - llvm: 19 + llvm: 21 go_version: "1.27" lane: full shard_index: "0" shard_total: "1" # Each Windows ABI profile runs the latest complete set with -p=2. - os: windows-2022 - llvm: 19 + llvm: 21 go_version: "1.27" lane: full shard_index: "0" @@ -413,7 +428,7 @@ jobs: windows_arch: amd64 host_go_arch: x64 - os: windows-2022 - llvm: 19 + llvm: 21 go_version: "1.27" lane: full shard_index: "0" @@ -422,7 +437,7 @@ jobs: windows_arch: amd64 host_go_arch: x64 - os: windows-11-arm - llvm: 19 + llvm: 21 go_version: "1.27" lane: full shard_index: "0" @@ -431,7 +446,7 @@ jobs: windows_arch: arm64 host_go_arch: x64 - os: windows-11-arm - llvm: 19 + llvm: 21 go_version: "1.27" lane: full shard_index: "0" @@ -440,7 +455,7 @@ jobs: windows_arch: arm64 host_go_arch: arm64 - os: windows-2022 - llvm: 19 + llvm: 21 go_version: "1.27" lane: full shard_index: "0" @@ -449,7 +464,7 @@ jobs: windows_arch: "386" host_go_arch: x64 - os: windows-2022 - llvm: 19 + llvm: 21 go_version: "1.27" lane: full shard_index: "0" @@ -552,7 +567,7 @@ jobs: # Native Darwin coverage remains in the primary LLGo lanes and release # artifact smoke tests. os: [ubuntu-latest] - llvm: [19] + llvm: [21] runs-on: ${{matrix.os}} steps: - uses: actions/checkout@v7 @@ -602,7 +617,7 @@ jobs: - name: Install host dependencies uses: ./.github/actions/setup-deps with: - llvm-version: 19 + llvm-version: 21 - name: Set up Go for building llgo uses: ./.github/actions/setup-go @@ -633,7 +648,7 @@ jobs: - name: Install dependencies uses: ./.github/actions/setup-deps with: - llvm-version: 19 + llvm-version: 21 - name: Set up Emscripten uses: emscripten-core/setup-emsdk@v15 diff --git a/.github/workflows/model-demo.yml b/.github/workflows/model-demo.yml index 89af920fcc..421d88ca72 100644 --- a/.github/workflows/model-demo.yml +++ b/.github/workflows/model-demo.yml @@ -19,7 +19,7 @@ jobs: - name: Install dependencies uses: ./.github/actions/setup-deps with: - llvm-version: 19 + llvm-version: 21 - name: Set up Go uses: ./.github/actions/setup-go diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 4e507ab5f2..18a296b05f 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -81,6 +81,7 @@ jobs: docker run \ --rm \ -e GITHUB_TOKEN=${GITHUB_TOKEN} \ + -e LLGO_LLVM_MAJOR=${LLGO_LLVM_MAJOR} \ -v /var/run/docker.sock:/var/run/docker.sock \ -v $(pwd):/go/src/llgo \ -w /go/src/llgo \ @@ -203,6 +204,7 @@ jobs: docker run \ --rm \ -e GITHUB_TOKEN=${GITHUB_TOKEN} \ + -e LLGO_LLVM_MAJOR=${LLGO_LLVM_MAJOR} \ -v /var/run/docker.sock:/var/run/docker.sock \ -v $(pwd):/go/src/llgo \ -w /go/src/llgo \ diff --git a/.github/workflows/targets.yml b/.github/workflows/targets.yml index 49ce34aa6c..314573dfea 100644 --- a/.github/workflows/targets.yml +++ b/.github/workflows/targets.yml @@ -18,7 +18,7 @@ jobs: matrix: os: - ubuntu-latest - llvm: [19] + llvm: [21] runs-on: ${{matrix.os}} steps: - uses: actions/checkout@v7 diff --git a/.github/workflows/test_windows_toolchain_profile.ps1 b/.github/workflows/test_windows_toolchain_profile.ps1 index d6e4c7c3c2..24ef0d04c8 100644 --- a/.github/workflows/test_windows_toolchain_profile.ps1 +++ b/.github/workflows/test_windows_toolchain_profile.ps1 @@ -103,7 +103,7 @@ if ($env:PKG_CONFIG -or $env:PKG_CONFIG_PATH) { if ($Profile -eq "msvc" -and $env:LLGO_MSYS2_LOCATION) { throw "The MSVC lane still exports LLGO_MSYS2_LOCATION" } -& pkg-config --modversion llvm-19 | Out-Null +& pkg-config --modversion llvm-21 | Out-Null Assert-Success "Reading LLVM metadata through the profile-local pkg-config" $compilerArgs = @() diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 4db7374c70..f3512934af 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -27,7 +27,7 @@ builds: - CC=o64-clang - CXX=o64-clang++ - CGO_CPPFLAGS=-I{{.Env.SYSROOT_DARWIN_AMD64}}/crosscompile/clang/include -mmacosx-version-min=10.13 -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS - - CGO_LDFLAGS=-L{{.Env.SYSROOT_DARWIN_AMD64}}/crosscompile/clang/lib -mmacosx-version-min=10.13 -Wl,-search_paths_first -Wl,-headerpad_max_install_names -lLLVM-19 -lz -lm -Wl,-rpath,@executable_path/../crosscompile/clang/lib + - CGO_LDFLAGS=-L{{.Env.SYSROOT_DARWIN_AMD64}}/crosscompile/clang/lib -mmacosx-version-min=10.13 -Wl,-search_paths_first -Wl,-headerpad_max_install_names -lLLVM-{{.Env.LLGO_LLVM_MAJOR}} -lz -lm -Wl,-rpath,@executable_path/../crosscompile/clang/lib targets: - darwin_amd64 mod_timestamp: "{{.CommitTimestamp}}" @@ -43,7 +43,7 @@ builds: - CC=oa64-clang - CXX=oa64-clang++ - CGO_CPPFLAGS=-I{{.Env.SYSROOT_DARWIN_ARM64}}/crosscompile/clang/include -mmacosx-version-min=10.13 -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS - - CGO_LDFLAGS=-L{{.Env.SYSROOT_DARWIN_ARM64}}/crosscompile/clang/lib -mmacosx-version-min=10.13 -Wl,-search_paths_first -Wl,-headerpad_max_install_names -lLLVM-19 -lz -lm -Wl,-rpath,@executable_path/../crosscompile/clang/lib + - CGO_LDFLAGS=-L{{.Env.SYSROOT_DARWIN_ARM64}}/crosscompile/clang/lib -mmacosx-version-min=10.13 -Wl,-search_paths_first -Wl,-headerpad_max_install_names -lLLVM-{{.Env.LLGO_LLVM_MAJOR}} -lz -lm -Wl,-rpath,@executable_path/../crosscompile/clang/lib targets: - darwin_arm64 mod_timestamp: "{{.CommitTimestamp}}" @@ -62,7 +62,7 @@ builds: - CXX={{.Env.SYSROOT_LINUX_AMD64}}/crosscompile/clang/bin/clang++ - CGO_CPPFLAGS=--target=x86_64-linux-gnu --gcc-toolchain={{.Env.SYSROOT_LINUX_AMD64}}/usr --sysroot={{.Env.SYSROOT_LINUX_AMD64}} -I{{.Env.SYSROOT_LINUX_AMD64}}/crosscompile/clang/include -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS - CGO_CXXFLAGS=-std=c++17 -nostdinc++ -isystem {{.Env.SYSROOT_LINUX_AMD64}}/usr/include/c++/10 -isystem {{.Env.SYSROOT_LINUX_AMD64}}/usr/include/x86_64-linux-gnu/c++/10 -isystem {{.Env.SYSROOT_LINUX_AMD64}}/usr/include/c++/10/backward - - CGO_LDFLAGS=--target=x86_64-linux-gnu --gcc-toolchain={{.Env.SYSROOT_LINUX_AMD64}}/usr --sysroot={{.Env.SYSROOT_LINUX_AMD64}} -L{{.Env.SYSROOT_LINUX_AMD64}}/crosscompile/clang/lib -L{{.Env.SYSROOT_LINUX_AMD64}}/usr/lib/gcc/x86_64-linux-gnu/10 -L{{.Env.SYSROOT_LINUX_AMD64}}/usr/lib/x86_64-linux-gnu -L{{.Env.SYSROOT_LINUX_AMD64}}/lib/x86_64-linux-gnu -lLLVM-19 -lz + - CGO_LDFLAGS=--target=x86_64-linux-gnu --gcc-toolchain={{.Env.SYSROOT_LINUX_AMD64}}/usr --sysroot={{.Env.SYSROOT_LINUX_AMD64}} -L{{.Env.SYSROOT_LINUX_AMD64}}/crosscompile/clang/lib -L{{.Env.SYSROOT_LINUX_AMD64}}/usr/lib/gcc/x86_64-linux-gnu/10 -L{{.Env.SYSROOT_LINUX_AMD64}}/usr/lib/x86_64-linux-gnu -L{{.Env.SYSROOT_LINUX_AMD64}}/lib/x86_64-linux-gnu -lLLVM-{{.Env.LLGO_LLVM_MAJOR}} -lz - CGO_LDFLAGS_ALLOW=(--target=.*|--gcc-toolchain=.*|--sysroot.*) targets: - linux_amd64 @@ -82,7 +82,7 @@ builds: - CXX={{.Env.SYSROOT_LINUX_AMD64}}/crosscompile/clang/bin/clang++ - CGO_CPPFLAGS=--target=aarch64-linux-gnu --gcc-toolchain={{.Env.SYSROOT_LINUX_ARM64}}/usr --sysroot={{.Env.SYSROOT_LINUX_ARM64}} -I{{.Env.SYSROOT_LINUX_ARM64}}/crosscompile/clang/include -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS - CGO_CXXFLAGS=-std=c++17 -nostdinc++ -isystem {{.Env.SYSROOT_LINUX_ARM64}}/usr/include/c++/10 -isystem {{.Env.SYSROOT_LINUX_ARM64}}/usr/include/aarch64-linux-gnu/c++/10 -isystem {{.Env.SYSROOT_LINUX_ARM64}}/usr/include/c++/10/backward - - CGO_LDFLAGS=--target=aarch64-linux-gnu --gcc-toolchain={{.Env.SYSROOT_LINUX_ARM64}}/usr --sysroot={{.Env.SYSROOT_LINUX_ARM64}} -L{{.Env.SYSROOT_LINUX_ARM64}}/crosscompile/clang/lib -L{{.Env.SYSROOT_LINUX_ARM64}}/usr/lib/gcc/aarch64-linux-gnu/10 -L{{.Env.SYSROOT_LINUX_ARM64}}/usr/lib/aarch64-linux-gnu -L{{.Env.SYSROOT_LINUX_ARM64}}/lib/aarch64-linux-gnu -lLLVM-19 -lz + - CGO_LDFLAGS=--target=aarch64-linux-gnu --gcc-toolchain={{.Env.SYSROOT_LINUX_ARM64}}/usr --sysroot={{.Env.SYSROOT_LINUX_ARM64}} -L{{.Env.SYSROOT_LINUX_ARM64}}/crosscompile/clang/lib -L{{.Env.SYSROOT_LINUX_ARM64}}/usr/lib/gcc/aarch64-linux-gnu/10 -L{{.Env.SYSROOT_LINUX_ARM64}}/usr/lib/aarch64-linux-gnu -L{{.Env.SYSROOT_LINUX_ARM64}}/lib/aarch64-linux-gnu -lLLVM-{{.Env.LLGO_LLVM_MAJOR}} -lz - CGO_LDFLAGS_ALLOW=(--target=.*|--gcc-toolchain=.*|--sysroot.*) targets: - linux_arm64 diff --git a/README.md b/README.md index feb7f83d95..d2d8e186d1 100644 --- a/README.md +++ b/README.md @@ -165,8 +165,7 @@ func main() { Additional demos are available in the `_demo` directory (prefixed with `_` so the `go` command skips them): -* [hello](_demo/c/hello/hello.go): call C `printf` to print `Hello world` -* [concat](_demo/c/concat/concat.go): call C `fprintf` with `stderr` +* [hello](_demo/c/hello/main.go): call C `printf` and `fprintf` with standard output and standard error * [qsort](_demo/c/qsort/qsort.go): call a C function that takes a callback (e.g. `qsort`) To run a demo (see [How to install](#how-to-install) if `llgo` isn't installed yet): @@ -291,9 +290,7 @@ Here we define two 3x3 matrices a and b, add them to get x, and then print the r The `_demo/py/` directory contains some python related demos: -* [callpy](_demo/py/callpy/callpy.go): call Python standard library function `math.sqrt` -* [pi](_demo/py/pi/pi.go): print python constants `math.pi` -* [statistics](_demo/py/statistics/statistics.go): define a python list and call `statistics.mean` to get the mean +* [basic](_demo/py/basic/main.go): use Python standard library functions, constants, and statistics * [matrix](_demo/py/matrix/matrix.go): a basic `numpy` demo To run these demos (If you haven't installed `llgo` yet, please refer to [How to install](#how-to-install)): @@ -306,9 +303,9 @@ llgo run . ## Dependencies - [Go 1.27](https://go.dev) for building LLGo; CI validates user packages separately with Go 1.20 through Go 1.27 -- [LLVM 19](https://llvm.org) -- [Clang 19](https://clang.llvm.org) -- [LLD 19](https://lld.llvm.org) +- [LLVM 21](https://llvm.org) +- [Clang 21](https://clang.llvm.org) +- [LLD 21](https://lld.llvm.org) - [pkg-config 0.29+](https://gitlab.freedesktop.org/pkg-config/pkg-config) - [bdwgc/libgc 8.0+](https://www.hboehm.info/gc/) - [libffi](https://sourceware.org/libffi/) @@ -326,9 +323,9 @@ Follow these steps to install the `llgo` command, whose usage is similar to the ```sh brew update -brew install llvm@19 lld@19 bdw-gc openssl cjson libffi pkg-config +brew install llvm@21 lld@21 bdw-gc openssl cjson libffi libuv pkg-config brew install python@3.12 # optional -brew link --overwrite llvm@19 lld@19 libffi +brew link --overwrite llvm@21 lld@21 libffi # curl https://raw.githubusercontent.com/xgo-dev/llgo/refs/heads/main/install.sh | bash ./install.sh ``` @@ -340,10 +337,10 @@ brew link --overwrite llvm@19 lld@19 libffi ```sh -echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-19 main" | sudo tee /etc/apt/sources.list.d/llvm.list +echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-21 main" | sudo tee /etc/apt/sources.list.d/llvm.list wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - sudo apt-get update -sudo apt-get install -y llvm-19-dev clang-19 libclang-19-dev lld-19 libunwind-19-dev libc++-19-dev pkg-config libgc-dev libssl-dev zlib1g-dev libffi-dev libcjson-dev libsqlite3-dev +sudo apt-get install -y llvm-21-dev clang-21 libclang-21-dev lld-21 libunwind-21-dev libc++-21-dev pkg-config libgc-dev libssl-dev zlib1g-dev libffi-dev libcjson-dev libsqlite3-dev libuv1-dev sudo apt-get install -y python3.12-dev # optional #curl https://raw.githubusercontent.com/xgo-dev/llgo/refs/heads/main/install.sh | bash ./install.sh @@ -352,10 +349,10 @@ sudo apt-get install -y python3.12-dev # optional #### Alpine Linux ```sh -apk add go llvm19-dev clang19-dev lld19 pkgconf gc-dev libunwind-dev openssl-dev zlib-dev +apk add go llvm21-dev clang21-dev lld21 pkgconf gc-dev libunwind-dev openssl-dev zlib-dev apk add python3-dev # optional apk add g++ # build only -export LLVM_CONFIG=/usr/lib/llvm19/bin/llvm-config +export LLVM_CONFIG=/usr/lib/llvm21/bin/llvm-config export CGO_CPPFLAGS="$($LLVM_CONFIG --cppflags)" export CGO_CXXFLAGS=-std=c++17 export CGO_LDFLAGS="$($LLVM_CONFIG --ldflags) $($LLVM_CONFIG --libs all)" diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 95198b491b..b5bdba2857 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -74,10 +74,10 @@ development- and test-only modules are not included. ## LLVM/Clang -LLGo can download the Espressif-maintained ESP LLVM/Clang -`19.1.2_20250905-3` toolchain on demand from -[`goplus/espressif-llvm-project-prebuilt`](https://github.com/goplus/espressif-llvm-project-prebuilt/releases/tag/19.1.2_20250905-3). -Current LLGo release archives also include this toolchain under +LLGo can download the Espressif-maintained ESP LLVM/Clang 21 toolchain +[`21.1.3_20260816`](https://github.com/goplus/espressif-llvm-project-prebuilt/releases/tag/21.1.3_20260816). + +Current LLGo release archives use the LLVM 21 payload and include it under `crosscompile/clang`, because the shipped `llgo` executable dynamically links its LLVM library. @@ -109,7 +109,7 @@ firmware. | WASI SDK 25 | [`WebAssembly/wasi-sdk`](https://github.com/WebAssembly/wasi-sdk/releases/tag/wasi-sdk-25) | upstream `LICENSE` and license files in the SDK | | picolibc/newlib sources | [`goplus/picolibc`](https://github.com/goplus/picolibc) | upstream `COPYING.picolibc` and `COPYING.NEWLIB` | | ESP newlib sources | [`goplus/newlib`](https://github.com/goplus/newlib/tree/esp-4.3.0_20250211-patch7) | upstream `COPYING.NEWLIB` and applicable file notices | -| compiler-rt sources | [`goplus/compiler-rt`](https://github.com/goplus/compiler-rt/tree/xtensa_release_19.1.2) | upstream `LICENSE.TXT` (Apache-2.0 WITH LLVM-exception) | +| compiler-rt sources | [`goplus/compiler-rt` LLVM 21](https://github.com/goplus/compiler-rt/tree/xtensa_release_21.1.3_20260408) | upstream `LICENSE.TXT` (Apache-2.0 WITH LLVM-exception) | Firmware or other binaries built from downloaded C library sources may carry their own redistribution requirements. Distributors of those outputs should diff --git a/cmd/llgo/lldbtest/common.sh b/cmd/llgo/lldbtest/common.sh index 68358deb6d..c333fa57a9 100644 --- a/cmd/llgo/lldbtest/common.sh +++ b/cmd/llgo/lldbtest/common.sh @@ -19,7 +19,7 @@ find_lldb() { if command -v "$lldb_path" >/dev/null 2>&1; then local version version=$("$lldb_path" --version | grep -oE '[0-9]+' | head -1) - if [ "$version" -ge 18 ]; then + if [[ "$version" =~ ^[0-9]+$ ]] && [ "$version" -ge 18 ]; then echo "$lldb_path" return 0 fi diff --git a/dev/Dockerfile.dev b/dev/Dockerfile.dev index f819994af2..c3462f81dd 100644 --- a/dev/Dockerfile.dev +++ b/dev/Dockerfile.dev @@ -5,7 +5,7 @@ ARG BASE_IMAGE=ubuntu:${UBUNTU_VERSION} FROM ${BASE_IMAGE} AS base ARG DEBIAN_FRONTEND=noninteractive -ARG LLVM_VERSION=19 +ARG LLVM_VERSION=21 ARG GO_VERSION=1.24.2 ARG CARGS_VERSION=1.0.0 diff --git a/doc/_readme/scripts/install_macos.sh b/doc/_readme/scripts/install_macos.sh index 540dd1c7ab..c6ceba7147 100644 --- a/doc/_readme/scripts/install_macos.sh +++ b/doc/_readme/scripts/install_macos.sh @@ -1,7 +1,7 @@ # shellcheck disable=all brew update -brew install llvm@19 lld@19 bdw-gc openssl cjson libffi pkg-config +brew install llvm@21 lld@21 bdw-gc openssl cjson libffi libuv pkg-config brew install python@3.12 # optional -brew link --overwrite llvm@19 lld@19 libffi +brew link --overwrite llvm@21 lld@21 libffi # curl https://raw.githubusercontent.com/xgo-dev/llgo/refs/heads/main/install.sh | bash ./install.sh diff --git a/doc/_readme/scripts/install_ubuntu.sh b/doc/_readme/scripts/install_ubuntu.sh index 14833128d7..d7d74e92c1 100644 --- a/doc/_readme/scripts/install_ubuntu.sh +++ b/doc/_readme/scripts/install_ubuntu.sh @@ -1,8 +1,8 @@ # shellcheck disable=all -echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-19 main" | sudo tee /etc/apt/sources.list.d/llvm.list +echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-21 main" | sudo tee /etc/apt/sources.list.d/llvm.list wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - sudo apt-get update -sudo apt-get install -y llvm-19-dev clang-19 libclang-19-dev lld-19 libunwind-19-dev libc++-19-dev pkg-config libgc-dev libssl-dev zlib1g-dev libffi-dev libcjson-dev libsqlite3-dev +sudo apt-get install -y llvm-21-dev clang-21 libclang-21-dev lld-21 libunwind-21-dev libc++-21-dev pkg-config libgc-dev libssl-dev zlib1g-dev libffi-dev libcjson-dev libsqlite3-dev libuv1-dev sudo apt-get install -y python3.12-dev # optional #curl https://raw.githubusercontent.com/xgo-dev/llgo/refs/heads/main/install.sh | bash ./install.sh diff --git a/go.mod b/go.mod index 926ad44e14..862cab0696 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/mattn/go-tty v0.0.8 github.com/qiniu/x v1.18.3 github.com/xgo-dev/llgo/runtime v0.0.0-00010101000000-000000000000 - github.com/xgo-dev/llvm v0.9.7 + github.com/xgo-dev/llvm v0.9.8 github.com/xgo-dev/plan9asm v0.5.1 go.bug.st/serial v1.6.4 go.yaml.in/yaml/v3 v3.0.5 @@ -26,3 +26,6 @@ require ( ) replace github.com/xgo-dev/llgo/runtime => ./runtime + +// Temporary: use goplus/lib#27 until its LLVM 21 metadata is merged. +replace github.com/goplus/lib => github.com/zhouguangyuan0718/lib v0.0.0-20260901051643-a8f1f8266c99 diff --git a/go.sum b/go.sum index 228514c6e8..cc16505e1d 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,6 @@ github.com/goplus/cobra v1.9.12 h1:0F9EdEbeGyITGz+mqoHoJ5KpUw97p1CkxV74IexHw5s= github.com/goplus/cobra v1.9.12/go.mod h1:p4LhfNJDKEpiGjGiNn0crUXL5dUPA5DX2ztYpEJR34E= github.com/goplus/gogen v1.23.5 h1:76w3zmAHI+ECI7bPr0enUd0du9+t1IYyXmp43CbIpSs= github.com/goplus/gogen v1.23.5/go.mod h1:Y7ulYW3wonQ3d9er00b0uGFEV/IUZa6okWJZh892ACQ= -github.com/goplus/lib v0.5.0 h1:zM8LAPatB4m8D/RflgDCSk6bJU1dEf7vzY6wqetO1WM= -github.com/goplus/lib v0.5.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0= github.com/goplus/mod v0.22.0 h1:knZCdR5m2Nr1/cU1XqG1lND4USG6mXxx/Ca272RYjQk= github.com/goplus/mod v0.22.0/go.mod h1:APrczG2FtFcQelU4vTq9xw+GrVs4sPPKnRfGidWBlXY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -22,10 +20,12 @@ github.com/qiniu/x v1.18.3 h1:trrBKBNszHGwV8XynnbddJr+A7Vca8/xlWP4F/Z4my8= github.com/qiniu/x v1.18.3/go.mod h1:Sx3Wy+0GI9OsX4a53mYj6A0o7mHJ94PUvraqGYb4EIs= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/xgo-dev/llvm v0.9.7 h1:nmFjc3tp7uuGve9vtKErt9DTKkMdvLqI2pbeR0nYnr0= -github.com/xgo-dev/llvm v0.9.7/go.mod h1:42vav2/cI5BAIcL543DZSMO9do8/aCK2z7JERH+AE+M= +github.com/xgo-dev/llvm v0.9.8 h1:tk83Jq5q+8LWmOX8i+y9sjOO99cDvmSYVPZpkjaAWns= +github.com/xgo-dev/llvm v0.9.8/go.mod h1:42vav2/cI5BAIcL543DZSMO9do8/aCK2z7JERH+AE+M= github.com/xgo-dev/plan9asm v0.5.1 h1:5gzTOAkqb+VYaCGIJybUVuE+pj3JwbZ5qfgWjX4dxf4= github.com/xgo-dev/plan9asm v0.5.1/go.mod h1:0yM4CCIp2PyT8h+Ro3Ukro3lHL8ji9mzHEv5yfhOckc= +github.com/zhouguangyuan0718/lib v0.0.0-20260901051643-a8f1f8266c99 h1:+oJtAsQMzlbzby9zRub9zKfvHsAKKRRmqHMUVHeTZEM= +github.com/zhouguangyuan0718/lib v0.0.0-20260901051643-a8f1f8266c99/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0= go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A= go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= diff --git a/internal/build/build.go b/internal/build/build.go index be4dbda5c2..ed25bdc3a7 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -64,6 +64,7 @@ import ( "github.com/xgo-dev/llgo/internal/typepatch" "github.com/xgo-dev/llgo/ssa/abi" xenv "github.com/xgo-dev/llgo/xtool/env" + envllvm "github.com/xgo-dev/llgo/xtool/env/llvm" gllvm "github.com/xgo-dev/llvm" llruntime "github.com/xgo-dev/llgo/runtime" @@ -502,6 +503,9 @@ func Build(inv Invocation) (result []Package, resultErr error) { if err != nil { return nil, fmt.Errorf("failed to setup crosscompile: %w", err) } + if err := validateLLVMToolchain(export); err != nil { + return nil, fmt.Errorf("invalid LLVM toolchain: %w", err) + } // Update GOOS/GOARCH from export if target was used if conf.Target != "" && export.GOOS != "" { conf.Goos = export.GOOS @@ -964,6 +968,22 @@ func parseNativeToolchainInput(commands commandEnv, options LinkOptions, resolve return input, nil } +func validateLLVMToolchain(export crosscompile.Export) error { + if export.ClangRoot != "" { + binDir := filepath.Join(export.ClangRoot, "bin") + return envllvm.ValidateToolchainMajor(gllvm.Version, + filepath.Join(binDir, "llvm-config"), + filepath.Join(binDir, "clang"), + filepath.Join(binDir, "ld.lld"), + ) + } + compiler := filepath.Base(export.CC) + if compiler != "clang" && compiler != "clang++" { + return nil + } + return envllvm.ValidateToolchainMajor(gllvm.Version, "llvm-config", export.CC, "ld.lld") +} + // cHeaderPackages excludes the patched standard runtime implementation. Its // //export callbacks are linker implementation details and may use internal C // types that are deliberately not representable in a public generated header. @@ -1463,7 +1483,7 @@ func appendExternalLinkArgs(ctx *context, aPkg *aPackage, spec string) { } else { linkFile := expdArgs[0] dir, lib := filepath.Split(linkFile) - pkgLinkArgs = append(pkgLinkArgs, "-l"+lib) + pkgLinkArgs = append(pkgLinkArgs, externalLibraryLinkArg(ctx.crossCompile.Toolchain, lib)) if dir != "" { pkgLinkArgs = append(pkgLinkArgs, "-L"+dir) atomic.AddInt32(&ctx.nLibdir, 1) @@ -1477,6 +1497,16 @@ func appendExternalLinkArgs(ctx *context, aPkg *aPackage, spec string) { aPkg.LinkArgs = append(aPkg.LinkArgs, pkgLinkArgs...) } +func externalLibraryLinkArg(toolchain crosscompile.NativeToolchain, lib string) string { + // LLGo packages use "c++" as the portable spelling for the target C++ + // standard library. The MSVC ABI provides that library as msvcprt.lib; + // spelling it as -lc++ instead asks lld-link for the unrelated c++.lib. + if lib == "c++" && toolchain.CXXRuntime == crosscompile.CXXRuntimeMSVC { + return "-lmsvcprt" + } + return "-l" + lib +} + var ( errXflags = errors.New("-X flag requires argument of the form importpath.name=value") ) diff --git a/internal/build/build_test.go b/internal/build/build_test.go index e037d6c3d8..94b0c57f87 100644 --- a/internal/build/build_test.go +++ b/internal/build/build_test.go @@ -121,6 +121,31 @@ func TestConfigCloneDoesNotAliasInput(t *testing.T) { } } +func TestValidateLLVMToolchain(t *testing.T) { + if err := validateLLVMToolchain(crosscompile.Export{CC: "gcc"}); err != nil { + t.Fatalf("non-Clang compiler rejected: %v", err) + } + if runtime.GOOS == "windows" { + t.Skip("test helper uses shell scripts") + } + + root := t.TempDir() + binDir := filepath.Join(root, "bin") + if err := os.MkdirAll(binDir, 0o755); err != nil { + t.Fatal(err) + } + for _, name := range []string{"llvm-config", "clang", "ld.lld"} { + tool := filepath.Join(binDir, name) + contents := "#!/bin/sh\nprintf '%s\\n' 'LLVM " + llvm.Version + "'\n" + if err := os.WriteFile(tool, []byte(contents), 0o755); err != nil { + t.Fatal(err) + } + } + if err := validateLLVMToolchain(crosscompile.Export{ClangRoot: root}); err != nil { + t.Fatalf("matching ClangRoot rejected: %v", err) + } +} + func TestResolveBuildConfigDefaultsAndValidation(t *testing.T) { resolved, err := resolveBuildConfig(&Config{ BuildMode: BuildModeCArchive, diff --git a/internal/build/package_build_test.go b/internal/build/package_build_test.go index d90dfbd921..287e3af40a 100644 --- a/internal/build/package_build_test.go +++ b/internal/build/package_build_test.go @@ -31,6 +31,7 @@ import ( "time" "github.com/xgo-dev/llgo/cl" + "github.com/xgo-dev/llgo/internal/crosscompile" "github.com/xgo-dev/llgo/internal/env" "github.com/xgo-dev/llgo/internal/packages" llssa "github.com/xgo-dev/llgo/ssa" @@ -203,6 +204,31 @@ func TestPrePackageBuildSkipsExternalLinkOnlyPackage(t *testing.T) { } } +func TestPrePackageBuildMapsCXXRuntimeLibraryForMSVC(t *testing.T) { + pkg := &aPackage{Package: &packages.Package{ + ID: "example.com/cxx", + PkgPath: "example.com/cxx", + Name: "cxx", + Types: types.NewPackage("example.com/cxx", "cxx"), + }} + ctx := &context{ + buildConf: &Config{}, + crossCompile: crosscompile.Export{Toolchain: crosscompile.NativeToolchain{ + CXXRuntime: crosscompile.CXXRuntimeMSVC, + }}, + } + task := &packageBuildTask{pkg: pkg, kind: cl.PkgLinkExtern, kindParam: "c++"} + if err := prePackageBuild(ctx, task, false); err != nil { + t.Fatal(err) + } + if !task.skip { + t.Fatal("external C++ runtime package was not skipped") + } + if got, want := strings.Join(pkg.LinkArgs, " "), "-lmsvcprt"; got != want { + t.Fatalf("external C++ runtime link args = %q, want %q", got, want) + } +} + func TestFinalizePackageBuildPreservesCachedRuntimeNeeds(t *testing.T) { pkg := &aPackage{Package: &packages.Package{ ID: "example.com/cached", diff --git a/internal/crosscompile/compile/rtlib/compiler_rt.go b/internal/crosscompile/compile/rtlib/compiler_rt.go index c2ba773f13..7237d7c87d 100644 --- a/internal/crosscompile/compile/rtlib/compiler_rt.go +++ b/internal/crosscompile/compile/rtlib/compiler_rt.go @@ -6,6 +6,8 @@ import ( "strings" "github.com/xgo-dev/llgo/internal/crosscompile/compile" + "github.com/xgo-dev/llgo/internal/llvmpayload" + gllvm "github.com/xgo-dev/llvm" ) func platformSpecifiedFiles(builtinsDir, target string) []string { @@ -104,13 +106,22 @@ func withPlatformSpecifiedFiles(baseDir, target string, files []string) []string return append(files, platformSpecifiedFiles(builtinsDir, target)...) } -func GetCompilerRTConfig() compile.LibConfig { +func GetCompilerRTConfig() (compile.LibConfig, error) { + return compilerRTConfigForLLVMVersion(gllvm.Version) +} + +func compilerRTConfigForLLVMVersion(llvmVersion string) (compile.LibConfig, error) { + payload, err := llvmpayload.ForLLVMVersion(llvmVersion) + if err != nil { + return compile.LibConfig{}, err + } + version := payload.CompilerRTVersion() return compile.LibConfig{ Name: "compiler-rt", - Url: "https://github.com/goplus/compiler-rt/archive/refs/tags/xtensa_release_19.1.2.tar.gz", - Version: "xtensa_release_19.1.2", - ResourceSubDir: "compiler-rt-xtensa_release_19.1.2", - } + Url: "https://github.com/goplus/compiler-rt/archive/refs/tags/" + version + ".tar.gz", + Version: version, + ResourceSubDir: "compiler-rt-" + version, + }, nil } func GetCompilerRTCompileConfig(baseDir, target string) compile.CompileConfig { diff --git a/internal/crosscompile/compile/rtlib/rt_test.go b/internal/crosscompile/compile/rtlib/rt_test.go index 8f4ea33d30..aea08f9f7c 100644 --- a/internal/crosscompile/compile/rtlib/rt_test.go +++ b/internal/crosscompile/compile/rtlib/rt_test.go @@ -4,36 +4,48 @@ import ( "path/filepath" "strings" "testing" + + gllvm "github.com/xgo-dev/llvm" ) func TestGetCompilerRTConfig_LibConfig(t *testing.T) { - config := GetCompilerRTConfig() - - // Test basic configuration fields - expectedName := "compiler-rt" - if config.Name != expectedName { - t.Errorf("Expected Name '%s', got '%s'", expectedName, config.Name) + const llvmVersion = "21.1.8" + const want = "xtensa_release_21.1.3_20260408" + config, err := compilerRTConfigForLLVMVersion(llvmVersion) + if err != nil { + t.Fatal(err) } - - expectedVersion := "xtensa_release_19.1.2" - if config.Version != expectedVersion { - t.Errorf("Expected Version '%s', got '%s'", expectedVersion, config.Version) + if config.Name != "compiler-rt" { + t.Errorf("LLVM %s name = %q", llvmVersion, config.Name) } - - expectedUrl := "https://github.com/goplus/compiler-rt/archive/refs/tags/xtensa_release_19.1.2.tar.gz" - if config.Url != expectedUrl { - t.Errorf("Expected Url '%s', got '%s'", expectedUrl, config.Url) + if config.Version != want { + t.Errorf("LLVM %s version = %q, want %q", llvmVersion, config.Version, want) } - - expectedArchiveSrcDir := "compiler-rt-xtensa_release_19.1.2" - if config.ResourceSubDir != expectedArchiveSrcDir { - t.Errorf("Expected ResourceSubDir '%s', got '%s'", expectedArchiveSrcDir, config.ResourceSubDir) + wantURL := "https://github.com/goplus/compiler-rt/archive/refs/tags/" + want + ".tar.gz" + if config.Url != wantURL { + t.Errorf("LLVM %s URL = %q, want %q", llvmVersion, config.Url, wantURL) + } + wantString := "compiler-rt-" + want + if config.ResourceSubDir != wantString || config.String() != wantString { + t.Errorf("LLVM %s archive identity = %q / %q, want %q", llvmVersion, config.ResourceSubDir, config.String(), wantString) + } + if _, err := compilerRTConfigForLLVMVersion("development"); err == nil { + t.Fatal("invalid LLVM version accepted") + } + if _, err := compilerRTConfigForLLVMVersion("19.1.7"); err == nil { + t.Fatal("unsupported LLVM version accepted") } - // Test String() method - expectedString := "compiler-rt-xtensa_release_19.1.2" - if config.String() != expectedString { - t.Errorf("Expected String() '%s', got '%s'", expectedString, config.String()) + defaultConfig, err := GetCompilerRTConfig() + if strings.HasPrefix(gllvm.Version, "21.") { + if err != nil { + t.Fatal(err) + } + if defaultConfig.Version != want { + t.Errorf("default compiler-rt version = %q, want %q", defaultConfig.Version, want) + } + } else if err == nil { + t.Fatalf("unsupported linked LLVM %s unexpectedly resolved to %q", gllvm.Version, defaultConfig.Version) } } diff --git a/internal/crosscompile/crosscompile.go b/internal/crosscompile/crosscompile.go index 50c75656d5..ca35ecb994 100644 --- a/internal/crosscompile/crosscompile.go +++ b/internal/crosscompile/crosscompile.go @@ -14,11 +14,13 @@ import ( "github.com/xgo-dev/llgo/internal/crosscompile/compile" "github.com/xgo-dev/llgo/internal/env" "github.com/xgo-dev/llgo/internal/flash" + "github.com/xgo-dev/llgo/internal/llvmpayload" "github.com/xgo-dev/llgo/internal/lto" "github.com/xgo-dev/llgo/internal/optlevel" "github.com/xgo-dev/llgo/internal/targets" "github.com/xgo-dev/llgo/internal/xtool/llvm" envllvm "github.com/xgo-dev/llgo/xtool/env/llvm" + gllvm "github.com/xgo-dev/llvm" ) type Export struct { @@ -51,6 +53,8 @@ type Export struct { ClangBinPath string // Path to clang binary directory LLVMTarget string // LLVM Target + CPU string // LLVM target CPU used by external code generation and cache identity + Features string // LLVM target features used by external code generation and cache identity TargetABI string // RISC-V Target ABI (e.g., "lp64", "lp64d") BinaryFormat string // Binary format (e.g., "elf", "esp", "uf2") FormatDetail string // For uf2, it's uf2FamilyID @@ -183,18 +187,11 @@ var ( wasiMacosSubdir = "wasi-sdk-25.0-x86_64-macos" ) -var ( - espClangBaseUrl = "https://github.com/goplus/espressif-llvm-project-prebuilt/releases/download/19.1.2_20250905-3" - espClangVersion = "19.1.2_20250905-3" - espClangWindowsBaseUrl = "https://github.com/espressif/llvm-project/releases/download/esp-19.1.2_20250312" - espClangWindowsVersion = "19.1.2_20250312" -) - -const espClangWindowsPlatform = "x86_64-w64-mingw32" - // cacheRoot can be overridden for testing var cacheRoot = env.LLGoCacheDir +var resolveESPClangArtifact = llvmpayload.Manifest.Artifact + func cacheDir() string { return filepath.Join(cacheRoot(), "crosscompile") } @@ -258,17 +255,25 @@ func getESPClangRoot(forceEspClang bool) (clangRoot string, err error) { return "", nil } + payload, err := llvmpayload.ForLLVMVersion(gllvm.Version) + if err != nil { + return "", err + } + // Try to download ESP Clang if platform is supported platformSuffix := getESPClangPlatform(runtime.GOOS, runtime.GOARCH) if platformSuffix != "" { - baseURL, version := espClangDownload(platformSuffix) - cacheClangDir := filepath.Join(cacheRoot(), "crosscompile", "esp-clang-"+version) + artifact, artifactErr := resolveESPClangArtifact(payload, platformSuffix) + if artifactErr != nil { + return "", artifactErr + } + cacheClangDir := filepath.Join(cacheRoot(), "crosscompile", "esp-clang-"+artifact.Version) if _, err = os.Stat(cacheClangDir); err != nil { if !errors.Is(err, fs.ErrNotExist) { return } fmt.Fprintln(os.Stderr, "ESP Clang not found in LLGO_ROOT or cache, will download.") - if err = checkDownloadAndExtractESPClang(baseURL, version, platformSuffix, cacheClangDir); err != nil { + if err = checkDownloadAndExtractESPClang(artifact, cacheClangDir); err != nil { return } } @@ -282,42 +287,8 @@ func getESPClangRoot(forceEspClang bool) (clangRoot string, err error) { // getESPClangPlatform returns the platform suffix for ESP Clang downloads func getESPClangPlatform(goos, goarch string) string { - switch goos { - case "darwin": - switch goarch { - case "amd64": - return "x86_64-apple-darwin" - case "arm64": - return "aarch64-apple-darwin" - } - case "linux": - switch goarch { - case "amd64": - return "x86_64-linux-gnu" - case "arm64": - return "aarch64-linux-gnu" - case "arm": - return "arm-linux-gnueabihf" - } - case "windows": - switch goarch { - case "amd64", "arm64": - // Espressif publishes an x86-64 Windows host toolchain. Windows on - // ARM64 runs it through the system's x64 emulation layer. - return espClangWindowsPlatform - } - } - return "" -} - -func espClangDownload(platformSuffix string) (baseURL, version string) { - if platformSuffix == espClangWindowsPlatform { - // The LLGo-hosted 20250905 build does not publish a Windows archive. - // Use Espressif's official LLVM 19 Windows build instead of constructing - // a URL that can only return 404. - return espClangWindowsBaseUrl, espClangWindowsVersion - } - return espClangBaseUrl, espClangVersion + platform, _ := llvmpayload.PlatformSuffix(goos, goarch) + return platform } // ldFlagsFromFileName extracts the library name from a filename for use in linker flags @@ -326,12 +297,28 @@ func ldFlagsFromFileName(fileName string) string { return strings.TrimPrefix(strings.TrimSuffix(fileName, ".a"), "lib") } +func lldLTOOptFlag(level optlevel.Level) (string, error) { + switch level { + case optlevel.O0, optlevel.O1, optlevel.O2, optlevel.O3: + return "--lto-" + level.Name(), nil + case optlevel.Os, optlevel.Oz: + // ld.lld only accepts numeric LTO optimization levels. Clang maps its + // size-oriented modes to O2 for the link-time optimization pipeline. + return "--lto-O2", nil + default: + return "", fmt.Errorf("invalid LTO optimization level %q", level) + } +} + // compileWithConfig compiles libraries according to the provided configuration // and returns the necessary linker flags for linking against the compiled libraries func compileWithConfig( compileConfig compile.CompileConfig, outputDir string, options compile.CompileOptions, ) (ldflags []string, err error) { + if err = os.MkdirAll(outputDir, 0o755); err != nil { + return nil, fmt.Errorf("create compiled library cache %q: %w", outputDir, err) + } ldflags = append(ldflags, "-nostdlib", "-L"+outputDir) for _, group := range compileConfig.Groups { @@ -742,6 +729,8 @@ func UseTarget(targetName string, level optlevel.Level, ltoMode lto.Mode) (expor export.GOARCH = config.GOARCH export.ExtraFiles = config.ExtraFiles export.LLVMTarget = config.LLVMTarget + export.CPU = config.CPU + export.Features = config.Features export.TargetABI = config.TargetABI export.BinaryFormat = config.BinaryFormat export.FormatDetail = config.FormatDetail() @@ -901,9 +890,17 @@ func UseTarget(targetName string, level optlevel.Level, ltoMode lto.Mode) (expor if config.LinkerScript != "" { ldflags = append(ldflags, "-T", config.LinkerScript) } - ldflags = append(ldflags, "-L", env.LLGoROOT()) // search targets/*.ld - var libcIncludeDir []string + var compiledLibraryKey string + if config.Libc != "" || config.RTLib != "" { + var compilerKey string + compilerKey, err = compilerCacheKey(export.CC) + if err != nil { + return + } + compiledLibraryKey = compiledLibraryCacheKey(compilerKey, ccflags, ldflags) + } + ldflags = append(ldflags, "-L", env.LLGoROOT()) // search targets/*.ld if config.Libc != "" { var outputDir string @@ -911,7 +908,7 @@ func UseTarget(targetName string, level optlevel.Level, ltoMode lto.Mode) (expor var compileConfig compile.CompileConfig baseDir := filepath.Join(cacheRoot(), "crosscompile") - outputDir, compileConfig, err = getLibcCompileConfigByName(baseDir, config.Libc, config.LLVMTarget, config.CPU) + outputDir, compileConfig, err = getLibcCompileConfigByName(baseDir, config.Libc, config.LLVMTarget, config.CPU, compiledLibraryKey) if err != nil { return } @@ -937,7 +934,7 @@ func UseTarget(targetName string, level optlevel.Level, ltoMode lto.Mode) (expor var compileConfig compile.CompileConfig baseDir := filepath.Join(cacheRoot(), "crosscompile") - outputDir, compileConfig, err = getRTCompileConfigByName(baseDir, config.RTLib, config.LLVMTarget) + outputDir, compileConfig, err = getRTCompileConfigByName(baseDir, config.RTLib, config.LLVMTarget, compiledLibraryKey) if err != nil { return } diff --git a/internal/crosscompile/crosscompile_test.go b/internal/crosscompile/crosscompile_test.go index 7055e35f63..67e757e5cc 100644 --- a/internal/crosscompile/crosscompile_test.go +++ b/internal/crosscompile/crosscompile_test.go @@ -4,6 +4,7 @@ package crosscompile import ( + "errors" "net/http" "net/http/httptest" "os" @@ -13,9 +14,12 @@ import ( "strings" "testing" + "github.com/xgo-dev/llgo/internal/crosscompile/compile" + "github.com/xgo-dev/llgo/internal/llvmpayload" "github.com/xgo-dev/llgo/internal/lto" "github.com/xgo-dev/llgo/internal/optlevel" "github.com/xgo-dev/llgo/internal/xtool/llvm" + gllvm "github.com/xgo-dev/llvm" ) const ( @@ -26,15 +30,19 @@ const ( ) func TestESPClangHostDownload(t *testing.T) { + payload, err := llvmpayload.Default() + if err != nil { + t.Fatal(err) + } tests := []struct { goos, goarch string wantPlatform string wantVersion string }{ - {"darwin", "arm64", "aarch64-apple-darwin", espClangVersion}, - {"linux", "amd64", "x86_64-linux-gnu", espClangVersion}, - {"windows", "amd64", espClangWindowsPlatform, espClangWindowsVersion}, - {"windows", "arm64", espClangWindowsPlatform, espClangWindowsVersion}, + {"darwin", "arm64", "aarch64-apple-darwin", payload.Version()}, + {"linux", "amd64", "x86_64-linux-gnu", payload.Version()}, + {"windows", "amd64", "x86_64-w64-mingw32", "21.1.3_20260408"}, + {"windows", "arm64", "x86_64-w64-mingw32", "21.1.3_20260408"}, } for _, test := range tests { platform := getESPClangPlatform(test.goos, test.goarch) @@ -42,13 +50,50 @@ func TestESPClangHostDownload(t *testing.T) { t.Errorf("getESPClangPlatform(%q, %q) = %q, want %q", test.goos, test.goarch, platform, test.wantPlatform) continue } - _, version := espClangDownload(platform) - if version != test.wantVersion { - t.Errorf("espClangDownload(%q) version = %q, want %q", platform, version, test.wantVersion) + artifact, err := payload.Artifact(platform) + if err != nil { + t.Errorf("Artifact(%q) error = %v", platform, err) + continue + } + if artifact.Version != test.wantVersion { + t.Errorf("Artifact(%q) version = %q, want %q", platform, artifact.Version, test.wantVersion) } } } +func TestESPClangArtifactError(t *testing.T) { + if _, err := llvmpayload.ForLLVMVersion(gllvm.Version); err != nil { + t.Skipf("linked LLVM is not the release payload: %v", err) + } + + t.Setenv("LLGO_ROOT", t.TempDir()) + originalCacheRoot := cacheRoot + originalResolver := resolveESPClangArtifact + cacheRoot = func() string { return t.TempDir() } + want := errors.New("artifact unavailable") + resolveESPClangArtifact = func(llvmpayload.Manifest, string) (llvmpayload.Artifact, error) { + return llvmpayload.Artifact{}, want + } + t.Cleanup(func() { + cacheRoot = originalCacheRoot + resolveESPClangArtifact = originalResolver + }) + + if _, err := getESPClangRoot(true); !errors.Is(err, want) { + t.Fatalf("getESPClangRoot error = %v, want %v", err, want) + } +} + +func TestCompileWithConfigRejectsFileAsOutputDir(t *testing.T) { + output := filepath.Join(t.TempDir(), "output") + if err := os.WriteFile(output, nil, 0o644); err != nil { + t.Fatal(err) + } + if _, err := compileWithConfig(compile.CompileConfig{}, output, compile.CompileOptions{}); err == nil || !strings.Contains(err.Error(), "create compiled library cache") { + t.Fatalf("compileWithConfig error = %v", err) + } +} + func TestUseCrossCompileSDK(t *testing.T) { // Skip long-running tests unless explicitly enabled if testing.Short() { @@ -288,6 +333,9 @@ func TestUseTarget(t *testing.T) { if !slices.Contains(export.LDFLAGS, "-S") { t.Fatalf("target %s declares AlwaysOmit without linker -S: %v", tc.targetName, export.LDFLAGS) } + if export.CPU != tc.expectCPU { + t.Fatalf("target %s exported CPU = %q, want %q", tc.targetName, export.CPU, tc.expectCPU) + } // Check if LLVM target is in CCFLAGS if tc.expectLLVM != "" { @@ -402,16 +450,19 @@ func TestUseTargetESPClangDownloadError(t *testing.T) { t.Setenv("LLGO_ROOT", llgoRoot) originalCacheRoot := cacheRoot - originalBaseURL := espClangBaseUrl - originalWindowsBaseURL := espClangWindowsBaseUrl + originalResolver := resolveESPClangArtifact cacheDir := t.TempDir() cacheRoot = func() string { return cacheDir } - espClangBaseUrl = server.URL - espClangWindowsBaseUrl = server.URL + resolveESPClangArtifact = func(_ llvmpayload.Manifest, platform string) (llvmpayload.Artifact, error) { + return llvmpayload.Artifact{ + Platform: platform, + Version: "test", + URL: server.URL + "/clang-esp-test.tar.xz", + }, nil + } t.Cleanup(func() { cacheRoot = originalCacheRoot - espClangBaseUrl = originalBaseURL - espClangWindowsBaseUrl = originalWindowsBaseURL + resolveESPClangArtifact = originalResolver }) _, err := UseTarget("esp-test", optlevel.Oz, lto.Thin) @@ -842,6 +893,29 @@ func hasFlagValue(flags []string, flag, value string) bool { return false } +func TestLLDLTOOptFlag(t *testing.T) { + tests := []struct { + level optlevel.Level + want string + }{ + {optlevel.O0, "--lto-O0"}, + {optlevel.O1, "--lto-O1"}, + {optlevel.O2, "--lto-O2"}, + {optlevel.O3, "--lto-O3"}, + {optlevel.Os, "--lto-O2"}, + {optlevel.Oz, "--lto-O2"}, + } + for _, test := range tests { + got, err := lldLTOOptFlag(test.level) + if err != nil || got != test.want { + t.Errorf("lldLTOOptFlag(%v) = %q, %v; want %q", test.level, got, err, test.want) + } + } + if _, err := lldLTOOptFlag(optlevel.Unset); err == nil { + t.Fatal("lldLTOOptFlag accepted an unset level") + } +} + func TestUseTargetCodegenFlagsOnlyAddedToLDFlagsWithLTO(t *testing.T) { const target = "k210" diff --git a/internal/crosscompile/fetch.go b/internal/crosscompile/fetch.go index 4277527a3c..08eb897571 100644 --- a/internal/crosscompile/fetch.go +++ b/internal/crosscompile/fetch.go @@ -5,6 +5,8 @@ import ( "archive/zip" "bytes" "compress/gzip" + "crypto/sha256" + "encoding/hex" "fmt" "io" "net/http" @@ -16,6 +18,7 @@ import ( "strings" "github.com/xgo-dev/llgo/internal/env" + "github.com/xgo-dev/llgo/internal/llvmpayload" ) const espClangLicenseFile = "XGo-LLVM-Apache-2.0-WITH-LLVM-exception.txt" @@ -47,7 +50,7 @@ func checkDownloadAndExtractWasiSDK(dir string) (wasiSdkRoot string, err error) } // checkDownloadAndExtractESPClang downloads and extracts ESP Clang binaries and libraries -func checkDownloadAndExtractESPClang(baseURL, version, platformSuffix, dir string) error { +func checkDownloadAndExtractESPClang(artifact llvmpayload.Artifact, dir string) error { // Check if already exists if _, err := os.Stat(dir); err == nil { return nil @@ -66,12 +69,11 @@ func checkDownloadAndExtractESPClang(baseURL, version, platformSuffix, dir strin return nil } - clangUrl := fmt.Sprintf("%s/clang-esp-%s-%s.tar.xz", baseURL, version, platformSuffix) - description := fmt.Sprintf("ESP Clang %s-%s", version, platformSuffix) + description := fmt.Sprintf("ESP Clang %s-%s", artifact.Version, artifact.Platform) // Use temporary extraction directory for ESP Clang special handling tempExtractDir := dir + ".extract" - if err := downloadAndExtractArchive(clangUrl, tempExtractDir, description); err != nil { + if err := downloadAndExtractArchiveWithChecksum(artifact.URL, tempExtractDir, description, artifact.SHA256); err != nil { return err } defer os.RemoveAll(tempExtractDir) @@ -191,6 +193,10 @@ func lockReleaseError(unlockErr, closeErr error) error { // downloadAndExtractArchive downloads and extracts an archive to the destination directory (without locking) func downloadAndExtractArchive(url, destDir, description string) error { + return downloadAndExtractArchiveWithChecksum(url, destDir, description, "") +} + +func downloadAndExtractArchiveWithChecksum(url, destDir, description, expectedSHA256 string) error { fmt.Fprintf(os.Stderr, "Downloading %s...\n", description) // Use temporary extraction directory @@ -208,6 +214,15 @@ func downloadAndExtractArchive(url, destDir, description string) error { if err := downloadFile(url, localFile); err != nil { return fmt.Errorf("failed to download %s from %s: %w", description, url, err) } + if expectedSHA256 != "" { + actualSHA256, err := fileSHA256(localFile) + if err != nil { + return fmt.Errorf("calculate %s checksum: %w", description, err) + } + if !strings.EqualFold(actualSHA256, expectedSHA256) { + return fmt.Errorf("%s checksum mismatch: got %s, want %s", description, actualSHA256, expectedSHA256) + } + } // Extract the archive fmt.Fprintf(os.Stderr, "Extracting %s...\n", description) @@ -238,6 +253,23 @@ func downloadAndExtractArchive(url, destDir, description string) error { return nil } +func fileSHA256(filename string) (string, error) { + file, err := os.Open(filename) + if err != nil { + return "", err + } + defer file.Close() + return readerSHA256(file) +} + +func readerSHA256(reader io.Reader) (string, error) { + hash := sha256.New() + if _, err := io.Copy(hash, reader); err != nil { + return "", err + } + return hex.EncodeToString(hash.Sum(nil)), nil +} + func downloadFile(url, filepath string) error { out, err := os.Create(filepath) if err != nil { diff --git a/internal/crosscompile/fetch_test.go b/internal/crosscompile/fetch_test.go index e9bdf5eaea..6d41b5f306 100644 --- a/internal/crosscompile/fetch_test.go +++ b/internal/crosscompile/fetch_test.go @@ -21,7 +21,10 @@ import ( "sync" "sync/atomic" "testing" + "testing/iotest" "time" + + "github.com/xgo-dev/llgo/internal/llvmpayload" ) // Helper function to create a test tar.gz archive @@ -828,7 +831,7 @@ func TestESPClangExtractionLogic(t *testing.T) { } // Test that function skips download for existing directory - err = checkDownloadAndExtractESPClang(espClangBaseUrl, espClangVersion, "linux", espClangDir) + err = checkDownloadAndExtractESPClang(llvmpayload.Artifact{}, espClangDir) if err != nil { t.Fatalf("checkDownloadAndExtractESPClang failed: %v", err) } @@ -918,9 +921,8 @@ func TestESPClangDownloadWhenNotExists(t *testing.T) { t.Fatalf("Failed to read test archive: %v", err) } - server := createTestServer(t, map[string]string{ - fmt.Sprintf("clang-esp-%s-linux.tar.xz", espClangVersion): string(archiveContent), - }) + const filename = "clang-esp-test-linux.tar.xz" + server := createTestServer(t, map[string]string{filename: string(archiveContent)}) defer server.Close() // Override cacheRoot to use a temporary directory @@ -929,16 +931,21 @@ func TestESPClangDownloadWhenNotExists(t *testing.T) { cacheRoot = func() string { return tempCacheRoot } defer func() { cacheRoot = originalCacheRoot }() - // Override espClangBaseUrl to use our test server - originalEspClangBaseUrl := espClangBaseUrl - espClangBaseUrl = server.URL - defer func() { espClangBaseUrl = originalEspClangBaseUrl }() - // Use a fresh temp directory that doesn't have ESP Clang espClangDir := filepath.Join(tempCacheRoot, "esp-clang-test") + checksum, err := fileSHA256(archivePath) + if err != nil { + t.Fatal(err) + } + artifact := llvmpayload.Artifact{ + Platform: "linux", + Version: "test", + URL: server.URL + "/" + filename, + SHA256: checksum, + } // Test download and extract when directory doesn't exist - err = checkDownloadAndExtractESPClang(espClangBaseUrl, espClangVersion, "linux", espClangDir) + err = checkDownloadAndExtractESPClang(artifact, espClangDir) if err != nil { t.Fatalf("checkDownloadAndExtractESPClang failed: %v", err) } @@ -978,19 +985,31 @@ func TestExtractTarXzError(t *testing.T) { } } +func TestFileSHA256MissingFile(t *testing.T) { + if _, err := fileSHA256(filepath.Join(t.TempDir(), "missing")); err == nil { + t.Fatal("fileSHA256 accepted a missing file") + } + if _, err := readerSHA256(iotest.ErrReader(errors.New("read failed"))); err == nil { + t.Fatal("readerSHA256 accepted a read failure") + } +} + func TestESPClangDownloadLicenseFailure(t *testing.T) { archivePath := createTestTarXz(t, map[string]string{ "esp-clang/bin/clang": "fake esp clang binary", }) - + defer os.Remove(archivePath) archiveContent, err := os.ReadFile(archivePath) if err != nil { t.Fatal(err) } - server := createTestServer(t, map[string]string{ - fmt.Sprintf("clang-esp-%s-linux.tar.xz", espClangVersion): string(archiveContent), - }) + const filename = "clang-esp-test-linux.tar.xz" + server := createTestServer(t, map[string]string{filename: string(archiveContent)}) defer server.Close() + checksum, err := fileSHA256(archivePath) + if err != nil { + t.Fatal(err) + } llgoRoot := t.TempDir() if err := os.MkdirAll(filepath.Join(llgoRoot, "runtime"), 0o755); err != nil { @@ -1005,7 +1024,12 @@ func TestESPClangDownloadLicenseFailure(t *testing.T) { t.Setenv("LLGO_ROOT", llgoRoot) destDir := filepath.Join(t.TempDir(), "esp-clang") - err = checkDownloadAndExtractESPClang(server.URL, espClangVersion, "linux", destDir) + err = checkDownloadAndExtractESPClang(llvmpayload.Artifact{ + Platform: "linux", + Version: "test", + URL: server.URL + "/" + filename, + SHA256: checksum, + }, destDir) if err == nil || !strings.Contains(err.Error(), "read ESP Clang license") { t.Fatalf("checkDownloadAndExtractESPClang() error = %v, want license read error", err) } @@ -1104,6 +1128,33 @@ func TestInstallESPClangLicenseWriteFailure(t *testing.T) { } } +func TestESPClangRejectsChecksumMismatch(t *testing.T) { + archivePath := createTestTarGz(t, map[string]string{"esp-clang/bin/clang": "fake"}) + defer os.Remove(archivePath) + + archiveContent, err := os.ReadFile(archivePath) + if err != nil { + t.Fatal(err) + } + server := createTestServer(t, map[string]string{"clang-esp-test-linux.tar.xz": string(archiveContent)}) + defer server.Close() + + destination := filepath.Join(t.TempDir(), "esp-clang") + artifact := llvmpayload.Artifact{ + Platform: "linux", + Version: "test", + URL: server.URL + "/clang-esp-test-linux.tar.xz", + SHA256: strings.Repeat("0", 64), + } + err = checkDownloadAndExtractESPClang(artifact, destination) + if err == nil || !strings.Contains(err.Error(), "checksum mismatch") { + t.Fatalf("checksum mismatch error = %v", err) + } + if _, statErr := os.Stat(destination); !os.IsNotExist(statErr) { + t.Fatalf("destination exists after rejected download: %v", statErr) + } +} + func TestExtractZip(t *testing.T) { // Create temporary test directory tempDir := t.TempDir() diff --git a/internal/crosscompile/libc.go b/internal/crosscompile/libc.go index 994ad3ad63..aa26ed52b2 100644 --- a/internal/crosscompile/libc.go +++ b/internal/crosscompile/libc.go @@ -1,8 +1,14 @@ package crosscompile import ( + "crypto/sha256" + "errors" "fmt" + "os" + "os/exec" "path/filepath" + "regexp" + "strings" "github.com/xgo-dev/llgo/internal/crosscompile/compile" "github.com/xgo-dev/llgo/internal/crosscompile/compile/libc" @@ -12,67 +18,114 @@ import ( // for testing, in testing env, we use fake path, it will cause downloading failure var needSkipDownload = false +var llvmVersionPattern = regexp.MustCompile(`[0-9]+\.[0-9]+(?:\.[0-9]+)?`) + +func compilerVersionCacheKey(versionOutput string, payloadContract []byte) (string, error) { + versionLine := strings.TrimSpace(versionOutput) + if i := strings.IndexByte(versionLine, '\n'); i >= 0 { + versionLine = versionLine[:i] + } + version := llvmVersionPattern.FindString(versionLine) + if version == "" { + return "", fmt.Errorf("parse compiler version from %q", versionLine) + } + identity := append([]byte(versionLine+"\x00"), payloadContract...) + digest := sha256.Sum256(identity) + return fmt.Sprintf("llvm-%s-%x", version, digest[:6]), nil +} + +func compilerCacheKey(cc string) (string, error) { + output, err := exec.Command(cc, "--version").CombinedOutput() + if err != nil { + return "", fmt.Errorf("query compiler %q version: %w: %s", cc, err, strings.TrimSpace(string(output))) + } + payloadContract, err := os.ReadFile(filepath.Join(filepath.Dir(filepath.Dir(cc)), "LLGO-LLVM-MANIFEST.txt")) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return "", fmt.Errorf("read compiler payload contract: %w", err) + } + return compilerVersionCacheKey(string(output), payloadContract) +} + +func compiledLibraryCacheKey(compilerKey string, flagGroups ...[]string) string { + identity := compilerKey + for _, flags := range flagGroups { + identity += "\x00" + strings.Join(flags, "\x00") + "\x01" + } + digest := sha256.Sum256([]byte(identity)) + return fmt.Sprintf("%s-%x", compilerKey, digest[:6]) +} + +func compiledLibraryDir(baseDir string, config compile.LibConfig, compilerKey string) string { + return filepath.Join(baseDir, config.String()+"-"+compilerKey) +} + // getLibcCompileConfigByName retrieves libc compilation configuration by name // Returns the actual libc output dir, compilation config and err -func getLibcCompileConfigByName(baseDir, libcName, target, mcpu string) (outputDir string, cfg compile.CompileConfig, err error) { +func getLibcCompileConfigByName(baseDir, libcName, target, mcpu, compilerKey string) (outputDir string, cfg compile.CompileConfig, err error) { if libcName == "" { err = fmt.Errorf("libc name cannot be empty") return } - var libcDir string + var sourceDir string var config compile.LibConfig var compileConfig compile.CompileConfig switch libcName { case "picolibc": config = libc.GetPicolibcConfig() - libcDir = filepath.Join(baseDir, config.String()) - compileConfig = libc.GetPicolibcCompileConfig(libcDir, target) + sourceDir = filepath.Join(baseDir, config.String()) + compileConfig = libc.GetPicolibcCompileConfig(sourceDir, target) case "newlib-esp32": config = libc.GetNewlibESP32Config() - libcDir = filepath.Join(baseDir, config.String()) - compileConfig = libc.GetNewlibESP32CompileConfig(libcDir, target, mcpu) + sourceDir = filepath.Join(baseDir, config.String()) + compileConfig = libc.GetNewlibESP32CompileConfig(sourceDir, target, mcpu) default: err = fmt.Errorf("unsupported libc: %s", libcName) return } + outputDir = compiledLibraryDir(baseDir, config, compilerKey) if needSkipDownload { - return libcDir, compileConfig, err + return outputDir, compileConfig, err } - if err = checkDownloadAndExtractLib(config.Url, libcDir, config.ResourceSubDir); err != nil { + if err = checkDownloadAndExtractLib(config.Url, sourceDir, config.ResourceSubDir); err != nil { return } - return libcDir, compileConfig, nil + return outputDir, compileConfig, nil } // getRTCompileConfigByName retrieves runtime library compilation configuration by name // Returns the actual libc output dir, compilation config and err -func getRTCompileConfigByName(baseDir, rtName, target string) (outputDir string, cfg compile.CompileConfig, err error) { +func getRTCompileConfigByName(baseDir, rtName, target, compilerKey string) (outputDir string, cfg compile.CompileConfig, err error) { if rtName == "" { err = fmt.Errorf("rt name cannot be empty") return } - var rtDir string + var sourceDir string var config compile.LibConfig var compileConfig compile.CompileConfig switch rtName { case "compiler-rt": - config = rtlib.GetCompilerRTConfig() - rtDir = filepath.Join(baseDir, config.String()) - compileConfig = rtlib.GetCompilerRTCompileConfig(rtDir, target) + config, err = rtlib.GetCompilerRTConfig() + if err != nil { + return + } + sourceDir = filepath.Join(baseDir, config.String()) + compileConfig = rtlib.GetCompilerRTCompileConfig(sourceDir, target) default: err = fmt.Errorf("unsupported rt: %s", rtName) + return } + outputDir = compiledLibraryDir(baseDir, config, compilerKey) if needSkipDownload { - return rtDir, compileConfig, err + return outputDir, compileConfig, err } - if err = checkDownloadAndExtractLib(config.Url, rtDir, config.ResourceSubDir); err != nil { + if err = checkDownloadAndExtractLib(config.Url, sourceDir, config.ResourceSubDir); err != nil { return } - return rtDir, compileConfig, nil + return outputDir, compileConfig, nil } diff --git a/internal/crosscompile/libc_test.go b/internal/crosscompile/libc_test.go index 9dbb94cb3e..5a821a840e 100644 --- a/internal/crosscompile/libc_test.go +++ b/internal/crosscompile/libc_test.go @@ -4,7 +4,9 @@ package crosscompile import ( "fmt" + "os" "path/filepath" + "runtime" "slices" "strings" "testing" @@ -13,6 +15,67 @@ import ( "github.com/xgo-dev/llgo/internal/crosscompile/compile/rtlib" ) +const testCompilerKey = "llvm-21.1.3-deadbeef" + +func TestCompilerVersionCacheKey(t *testing.T) { + first, err := compilerVersionCacheKey("clang version 21.1.3 (https://example.test/llvm abc123)\nInstalledDir: /one", nil) + if err != nil { + t.Fatal(err) + } + second, err := compilerVersionCacheKey("clang version 21.1.3 (https://example.test/llvm abc123)\nInstalledDir: /two", nil) + if err != nil { + t.Fatal(err) + } + if first != second { + t.Fatalf("cache key depends on install directory: %q != %q", first, second) + } + different, err := compilerVersionCacheKey("clang version 21.1.3 (https://example.test/llvm def456)", nil) + if err != nil { + t.Fatal(err) + } + if different == first { + t.Fatalf("cache key ignores compiler revision: %q", first) + } + patched, err := compilerVersionCacheKey("clang version 21.1.3 (https://example.test/llvm abc123)", []byte("llvm_source_patch_sha256=abc")) + if err != nil { + t.Fatal(err) + } + if patched == first { + t.Fatalf("cache key ignores payload contract: %q", first) + } + if a, b := compiledLibraryCacheKey(first, []string{"-Oz", "-flto=thin"}), compiledLibraryCacheKey(first, []string{"-Oz"}); a == b { + t.Fatalf("compiled library cache key ignores code-generation flags: %q", a) + } + if _, err := compilerVersionCacheKey("not a compiler version", nil); err == nil { + t.Fatal("compilerVersionCacheKey accepted output without a version") + } +} + +func TestCompilerCacheKeyErrors(t *testing.T) { + if _, err := compilerCacheKey(filepath.Join(t.TempDir(), "missing-clang")); err == nil { + t.Fatal("compilerCacheKey accepted a missing compiler") + } + if runtime.GOOS == "windows" { + t.Skip("test helper uses a shell script") + } + + root := t.TempDir() + binDir := filepath.Join(root, "bin") + if err := os.MkdirAll(binDir, 0o755); err != nil { + t.Fatal(err) + } + clang := filepath.Join(binDir, "clang") + if err := os.WriteFile(clang, []byte("#!/bin/sh\nprintf '%s\\n' 'clang version 21.1.3'\n"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.Mkdir(filepath.Join(root, "LLGO-LLVM-MANIFEST.txt"), 0o755); err != nil { + t.Fatal(err) + } + if _, err := compilerCacheKey(clang); err == nil || !strings.Contains(err.Error(), "read compiler payload contract") { + t.Fatalf("compilerCacheKey unreadable manifest error = %v", err) + } +} + func TestGetLibcCompileConfigByName(t *testing.T) { baseDir := filepath.FromSlash("/test/base") target := "armv7" @@ -20,21 +83,21 @@ func TestGetLibcCompileConfigByName(t *testing.T) { needSkipDownload = true t.Run("EmptyName", func(t *testing.T) { - _, _, err := getLibcCompileConfigByName(baseDir, "", target, mcpu) + _, _, err := getLibcCompileConfigByName(baseDir, "", target, mcpu, testCompilerKey) if err == nil || err.Error() != "libc name cannot be empty" { t.Errorf("Expected empty name error, got: %v", err) } }) t.Run("UnsupportedLibc", func(t *testing.T) { - _, _, err := getLibcCompileConfigByName(baseDir, "invalid", target, mcpu) + _, _, err := getLibcCompileConfigByName(baseDir, "invalid", target, mcpu, testCompilerKey) if err == nil || err.Error() != "unsupported libc: invalid" { t.Errorf("Expected unsupported libc error, got: %v", err) } }) t.Run("Picolibc", func(t *testing.T) { - _, cfg, err := getLibcCompileConfigByName(baseDir, "picolibc", target, mcpu) + outputDir, cfg, err := getLibcCompileConfigByName(baseDir, "picolibc", target, mcpu, testCompilerKey) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -44,19 +107,23 @@ func TestGetLibcCompileConfigByName(t *testing.T) { } group := cfg.Groups[0] + expectedDir := compiledLibraryDir(baseDir, libc.GetPicolibcConfig(), testCompilerKey) + if outputDir != expectedDir { + t.Fatalf("output dir = %q, want %q", outputDir, expectedDir) + } expectedFile := filepath.Join(baseDir, libc.GetPicolibcConfig().String(), "newlib", "libc", "string", "memmem.c") if !slices.Contains(group.Files, expectedFile) { t.Errorf("Expected files [%s], got: %v", expectedFile, group.Files) } - expectedFlag := "-I" + filepath.Join("/test", "base", libc.GetPicolibcConfig().String()) + expectedFlag := "-I" + filepath.Join(baseDir, libc.GetPicolibcConfig().String()) if !slices.Contains(group.CFlags, expectedFlag) { t.Errorf("Expected flags [%s], got: %v", expectedFlag, group.CFlags) } }) t.Run("NewlibESP32", func(t *testing.T) { - _, cfg, err := getLibcCompileConfigByName(baseDir, "newlib-esp32", target, mcpu) + outputDir, cfg, err := getLibcCompileConfigByName(baseDir, "newlib-esp32", target, mcpu, testCompilerKey) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -66,6 +133,10 @@ func TestGetLibcCompileConfigByName(t *testing.T) { } group := cfg.Groups[0] + expectedDir := compiledLibraryDir(baseDir, libc.GetNewlibESP32Config(), testCompilerKey) + if outputDir != expectedDir { + t.Fatalf("output dir = %q, want %q", outputDir, expectedDir) + } expectedFile := filepath.Join(baseDir, libc.GetNewlibESP32Config().String(), "libgloss", "xtensa", "crt1-boards.S") if !slices.Contains(group.Files, expectedFile) { t.Errorf("Expected files [%s], got: %v", expectedFile, group.Files) @@ -84,21 +155,21 @@ func TestGetRTCompileConfigByName(t *testing.T) { needSkipDownload = true t.Run("EmptyName", func(t *testing.T) { - _, _, err := getRTCompileConfigByName(baseDir, "", target) + _, _, err := getRTCompileConfigByName(baseDir, "", target, testCompilerKey) if err == nil || err.Error() != "rt name cannot be empty" { t.Errorf("Expected empty name error, got: %v", err) } }) t.Run("UnsupportedRT", func(t *testing.T) { - _, _, err := getRTCompileConfigByName(baseDir, "invalid", target) + _, _, err := getRTCompileConfigByName(baseDir, "invalid", target, testCompilerKey) if err == nil || err.Error() != "unsupported rt: invalid" { t.Errorf("Expected unsupported rt error, got: %v", err) } }) t.Run("CompilerRT", func(t *testing.T) { - _, cfg, err := getRTCompileConfigByName(baseDir, "compiler-rt", target) + outputDir, cfg, err := getRTCompileConfigByName(baseDir, "compiler-rt", target, testCompilerKey) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -108,7 +179,15 @@ func TestGetRTCompileConfigByName(t *testing.T) { } group := cfg.Groups[0] - expectedFile := filepath.Join(baseDir, rtlib.GetCompilerRTConfig().String(), "lib", "builtins", "absvdi2.c") + compilerRTConfig, err := rtlib.GetCompilerRTConfig() + if err != nil { + t.Fatal(err) + } + expectedDir := compiledLibraryDir(baseDir, compilerRTConfig, testCompilerKey) + if outputDir != expectedDir { + t.Fatalf("output dir = %q, want %q", outputDir, expectedDir) + } + expectedFile := filepath.Join(baseDir, compilerRTConfig.String(), "lib", "builtins", "absvdi2.c") if !slices.Contains(group.Files, expectedFile) { t.Errorf("Expected files [%s], got: %v", expectedFile, group.Files) } diff --git a/internal/dcepass/dcepass.go b/internal/dcepass/dcepass.go index 36f179c5a9..23437f2f24 100644 --- a/internal/dcepass/dcepass.go +++ b/internal/dcepass/dcepass.go @@ -224,7 +224,7 @@ func (e *overrideEmitter) cloneType(src llvm.Type) llvm.Type { dst = ctx.StructCreateNamed(name) } e.types[src] = dst - if dst.StructElementTypesCount() == 0 && src.StructElementTypesCount() != 0 { + if dst.IsStructOpaque() && !src.IsStructOpaque() { dst.StructSetBody(e.cloneTypes(src.StructElementTypes()), src.IsStructPacked()) } return dst diff --git a/internal/dcepass/dcepass_test.go b/internal/dcepass/dcepass_test.go index d05d1145de..261183b62d 100644 --- a/internal/dcepass/dcepass_test.go +++ b/internal/dcepass/dcepass_test.go @@ -121,6 +121,9 @@ func TestCloneTypesAcrossContexts(t *testing.T) { emitter := newOverrideEmitter(dst) named := srcCtx.StructCreateNamed("named") named.StructSetBody([]llvm.Type{srcCtx.Int32Type()}, false) + empty := srcCtx.StructCreateNamed("empty") + empty.StructSetBody(nil, false) + opaque := srcCtx.StructCreateNamed("opaque") existing := srcCtx.StructCreateNamed("existing") existing.StructSetBody([]llvm.Type{srcCtx.Int32Type()}, false) dstExisting := dstCtx.StructCreateNamed("existing") @@ -137,6 +140,8 @@ func TestCloneTypesAcrossContexts(t *testing.T) { srcCtx.IntType(17), llvm.FunctionType(srcCtx.VoidType(), []llvm.Type{srcCtx.Int32Type()}, true), named, + empty, + opaque, existing, srcCtx.StructType([]llvm.Type{srcCtx.Int8Type(), srcCtx.Int16Type()}, true), llvm.ArrayType(srcCtx.Int32Type(), 3), @@ -162,6 +167,12 @@ func TestCloneTypesAcrossContexts(t *testing.T) { if clonedNamed.C == named.C || clonedNamed.StructElementTypesCount() != 1 { t.Fatalf("identified struct was not recreated in the destination context: %s", clonedNamed) } + if got := emitter.cloneType(empty); got.IsStructOpaque() || got.StructElementTypesCount() != 0 { + t.Fatalf("defined empty struct was not preserved: %s", got) + } + if got := emitter.cloneType(opaque); !got.IsStructOpaque() { + t.Fatalf("opaque struct was not preserved: %s", got) + } if got := emitter.cloneType(existing); got.C != dstExisting.C { t.Fatalf("existing destination type was not reused: got %s, want %s", got, dstExisting) } @@ -256,7 +267,8 @@ func TestCloneConstantsRejectsUnsupportedLLVMForms(t *testing.T) { @target = external global i8 @bitcast_expr = global double bitcast (i64 ptrtoint (ptr @target to i64) to double) @trunc_expr = global i32 trunc (i64 add (i64 ptrtoint (ptr @target to i64), i64 1) to i32) -@unsupported_expr = global i64 mul (i64 ptrtoint (ptr @target to i64), i64 2) +@addrspace_target = external addrspace(1) global i8 +@unsupported_expr = global ptr addrspacecast (ptr addrspace(1) @addrspace_target to ptr) @block_addr = global ptr blockaddress(@block_target, %entry) define void @block_target() { diff --git a/internal/dcepass/testdata/method_slots/expect.ll b/internal/dcepass/testdata/method_slots/expect.ll index d24b3b08ac..184a66fa05 100644 --- a/internal/dcepass/testdata/method_slots/expect.ll +++ b/internal/dcepass/testdata/method_slots/expect.ll @@ -9,7 +9,7 @@ source_filename = "dst" %"github.com/xgo-dev/llgo/runtime/abi.Method" = type { %runtime.String, ptr, ptr, ptr } %"runtime/abi.PtrType" = type { %"runtime/abi.Type", ptr } %"runtime/abi.FuncType" = type { %"runtime/abi.Type", %runtime.Slice, %runtime.Slice } -%Task = type opaque +%Task = type {} @_llgo_main.Task = constant { %"runtime/abi.StructType", %"runtime/abi.UncommonType", [2 x %"github.com/xgo-dev/llgo/runtime/abi.Method"] } { %"runtime/abi.StructType" { %"runtime/abi.Type" { i64 0, i64 0, i32 1, i8 13, i8 1, i8 1, i8 25, { ptr, ptr } { ptr @memequal0, ptr @_llgo_main.Task }, ptr null, %runtime.String { ptr @0, i64 9 }, ptr @"*_llgo_main.Task" }, %runtime.String zeroinitializer, %runtime.Slice zeroinitializer }, %"runtime/abi.UncommonType" { %runtime.String { ptr @1, i64 4 }, i16 2, i16 2, i32 24 }, [2 x %"github.com/xgo-dev/llgo/runtime/abi.Method"] [%"github.com/xgo-dev/llgo/runtime/abi.Method" { %runtime.String { ptr @2, i64 4 }, ptr @"_llgo_func$run", ptr @"github.com/xgo-dev/llgo/runtime/internal/runtime.unreachableMethod", ptr @"github.com/xgo-dev/llgo/runtime/internal/runtime.unreachableMethod" }, %"github.com/xgo-dev/llgo/runtime/abi.Method" { %runtime.String { ptr @3, i64 3 }, ptr @"_llgo_func$run", ptr @"main.(*Task).Run", ptr @main.Task.Run }] }, align 8 @0 = private constant [9 x i8] c"main.Task", align 1 diff --git a/internal/littest/littest.go b/internal/littest/littest.go index 7c092cfac5..1170dac6ef 100644 --- a/internal/littest/littest.go +++ b/internal/littest/littest.go @@ -59,12 +59,22 @@ func LoadSpec(pkgDir string) (Spec, error) { } func Check(spec Spec, actual string, targetPrefixes ...string) error { + actual = CanonicalizeLLVMIR(actual) if len(targetPrefixes) == 0 { return filecheck.Match(spec.Path, actual) } return filecheck.MatchWithTargetPrefixes(spec.Path, actual, targetPrefixes...) } +// CanonicalizeLLVMIR removes LLVM-version-specific spellings that are not the +// semantic contract of LLGo's IR tests. Verifier, object, link, and runtime +// tests still consume the original IR. +func CanonicalizeLLVMIR(ir string) string { + ir = strings.ReplaceAll(ir, "getelementptr inbounds nuw ", "getelementptr inbounds ") + ir = strings.ReplaceAll(ir, " captures(none)", " nocapture") + return ir +} + func FindMarkedSourceFile(dir string) (string, bool, error) { spec, ok, err := FindSpec(dir) return spec.Path, ok, err diff --git a/internal/littest/littest_test.go b/internal/littest/littest_test.go index 316147443d..6f78e75cd7 100644 --- a/internal/littest/littest_test.go +++ b/internal/littest/littest_test.go @@ -200,6 +200,15 @@ package main } } +func TestCanonicalizeLLVMIRVersionSpellings(t *testing.T) { + input := " %1 = getelementptr inbounds nuw { ptr }, ptr %0, i32 0\n" + + "declare void @f(ptr captures(none) readonly)\n" + want := " %1 = getelementptr inbounds { ptr }, ptr %0, i32 0\n" + + "declare void @f(ptr nocapture readonly)\n" + if got := CanonicalizeLLVMIR(input); got != want { + t.Fatalf("CanonicalizeLLVMIR() = %q, want %q", got, want) + } +} func TestHasMarker(t *testing.T) { dir := t.TempDir() diff --git a/internal/llvmpayload/cmd/llvmpayload/main.go b/internal/llvmpayload/cmd/llvmpayload/main.go new file mode 100644 index 0000000000..fe5347c7d9 --- /dev/null +++ b/internal/llvmpayload/cmd/llvmpayload/main.go @@ -0,0 +1,67 @@ +// Command llvmpayload prints the checked-in LLVM payload contract for release +// scripts that cannot import Go constants directly. +package main + +import ( + "flag" + "fmt" + "io" + "os" + "strings" + + "github.com/xgo-dev/llgo/internal/llvmpayload" +) + +func main() { + if run(os.Args[1:], os.Stdout, os.Stderr) != 0 { + os.Exit(1) + } +} + +func run(args []string, stdout, stderr io.Writer) int { + flags := flag.NewFlagSet("llvmpayload", flag.ContinueOnError) + flags.SetOutput(stderr) + major := flags.Int("major", 0, "LLVM major (defaults to the release payload)") + if err := flags.Parse(args); err != nil { + return 1 + } + + var ( + manifest llvmpayload.Manifest + err error + ) + if *major == 0 { + manifest, err = llvmpayload.Default() + } else { + manifest, err = llvmpayload.ForMajor(*major) + } + if err != nil { + fmt.Fprintln(stderr, err) + return 1 + } + + fmt.Fprintf(stdout, "LLGO_LLVM_MAJOR=%s\n", fmt.Sprint(manifest.LLVMMajor())) + fmt.Fprintf(stdout, "ESP_CLANG_LLVM_MAJOR=%s\n", fmt.Sprint(manifest.LLVMMajor())) + fmt.Fprintf(stdout, "ESP_CLANG_VERSION=%s\n", manifest.Version()) + fmt.Fprintf(stdout, "ESP_CLANG_BASE_URL=%s\n", manifest.BaseURL()) + for _, host := range []struct { + name, goos, goarch string + }{ + {name: "DARWIN_AMD64", goos: "darwin", goarch: "amd64"}, + {name: "DARWIN_ARM64", goos: "darwin", goarch: "arm64"}, + {name: "LINUX_AMD64", goos: "linux", goarch: "amd64"}, + {name: "LINUX_ARM64", goos: "linux", goarch: "arm64"}, + } { + platform, ok := llvmpayload.PlatformSuffix(host.goos, host.goarch) + if !ok { + panic("missing platform mapping for " + host.goos + "/" + host.goarch) + } + artifact, err := manifest.Artifact(platform) + if err != nil { + fmt.Fprintln(stderr, err) + return 1 + } + fmt.Fprintf(stdout, "ESP_CLANG_SHA256_%s=%s\n", strings.ToUpper(host.name), artifact.SHA256) + } + return 0 +} diff --git a/internal/llvmpayload/cmd/llvmpayload/main_test.go b/internal/llvmpayload/cmd/llvmpayload/main_test.go new file mode 100644 index 0000000000..81a998464f --- /dev/null +++ b/internal/llvmpayload/cmd/llvmpayload/main_test.go @@ -0,0 +1,55 @@ +package main + +import ( + "bytes" + "strings" + "testing" +) + +func TestRun(t *testing.T) { + for _, args := range [][]string{nil, []string{"-major", "21"}} { + var stdout, stderr bytes.Buffer + if code := run(args, &stdout, &stderr); code != 0 { + t.Fatalf("run(%q) exit code = %d, stderr = %q", args, code, stderr.String()) + } + for _, want := range []string{ + "LLGO_LLVM_MAJOR=21\n", + "ESP_CLANG_LLVM_MAJOR=21\n", + "ESP_CLANG_VERSION=21.1.3_20260816\n", + "ESP_CLANG_BASE_URL=https://github.com/goplus/espressif-llvm-project-prebuilt/releases/download/21.1.3_20260816\n", + "ESP_CLANG_SHA256_DARWIN_AMD64=21159a4edb8948d83e1f73dfef394bca6941d0c4035da02f8c90ac59799893fa\n", + "ESP_CLANG_SHA256_DARWIN_ARM64=a8c46104501c38a8a7359ec24bc4e9d646f9fec2bdb2b122cbbee78e060400d1\n", + "ESP_CLANG_SHA256_LINUX_AMD64=582b787057c9e36e7d4db20aaed7bbba74c7ad0481489f034f09476703befbd5\n", + "ESP_CLANG_SHA256_LINUX_ARM64=77f49d832e5f309ecd6baaf169c62e3b064b27f9bee5aedddb6e66c981d56f44\n", + } { + if !strings.Contains(stdout.String(), want) { + t.Errorf("run(%q) output does not contain %q:\n%s", args, want, stdout.String()) + } + } + if stderr.Len() != 0 { + t.Errorf("run(%q) stderr = %q", args, stderr.String()) + } + } +} + +func TestRunErrors(t *testing.T) { + tests := []struct { + args []string + want string + }{ + {args: []string{"-major", "19"}, want: "no LLGo LLVM payload for major version 19"}, + {args: []string{"-major", "invalid"}, want: "invalid value"}, + } + for _, test := range tests { + var stdout, stderr bytes.Buffer + if code := run(test.args, &stdout, &stderr); code == 0 { + t.Fatalf("run(%q) unexpectedly succeeded", test.args) + } + if stdout.Len() != 0 { + t.Errorf("run(%q) stdout = %q", test.args, stdout.String()) + } + if !strings.Contains(stderr.String(), test.want) { + t.Errorf("run(%q) stderr = %q, want substring %q", test.args, stderr.String(), test.want) + } + } +} diff --git a/internal/llvmpayload/payload.go b/internal/llvmpayload/payload.go new file mode 100644 index 0000000000..7b71aac7ba --- /dev/null +++ b/internal/llvmpayload/payload.go @@ -0,0 +1,160 @@ +// Package llvmpayload defines the revision-locked LLVM toolchain distributed +// with and downloaded by LLGo. +package llvmpayload + +import ( + "fmt" + "regexp" + "sort" + "strconv" +) + +const ( + releaseBaseURL = "https://github.com/goplus/espressif-llvm-project-prebuilt/releases/download" + espLLVM21WindowsBaseURL = "https://github.com/espressif/llvm-project/releases/download/esp-21.1.3_20260408" + espLLVM21WindowsVersion = "21.1.3_20260408" + espLLVM21WindowsPlatform = "x86_64-w64-mingw32" +) + +// DefaultMajor is the LLVM payload bundled into LLGo release archives. +const DefaultMajor = 21 + +var llvmMajorPattern = regexp.MustCompile(`(?:^|[^0-9])([0-9]+)\.[0-9]+`) + +type manifest struct { + llvmMajor int + version string + compilerRTVersion string + sha256 map[string]string + artifactOverrides map[string]artifactOverride +} + +type artifactOverride struct { + version string + baseURL string + sha256 string +} + +// Artifact identifies one host-specific LLVM payload archive. +type Artifact struct { + Platform string + Version string + URL string + SHA256 string +} + +var manifests = map[int]manifest{ + 21: { + llvmMajor: 21, + version: "21.1.3_20260816", + compilerRTVersion: "xtensa_release_21.1.3_20260408", + sha256: map[string]string{ + "aarch64-apple-darwin": "a8c46104501c38a8a7359ec24bc4e9d646f9fec2bdb2b122cbbee78e060400d1", + "aarch64-linux-gnu": "77f49d832e5f309ecd6baaf169c62e3b064b27f9bee5aedddb6e66c981d56f44", + "x86_64-apple-darwin": "21159a4edb8948d83e1f73dfef394bca6941d0c4035da02f8c90ac59799893fa", + "x86_64-linux-gnu": "582b787057c9e36e7d4db20aaed7bbba74c7ad0481489f034f09476703befbd5", + }, + artifactOverrides: map[string]artifactOverride{ + espLLVM21WindowsPlatform: { + version: espLLVM21WindowsVersion, + baseURL: espLLVM21WindowsBaseURL, + sha256: "415566ace6f47a9abc302b4ba79776d27668fd3f4e9c0d26861ec4f970323618", + }, + }, + }, +} + +// ForLLVMVersion returns the payload compatible with an in-process LLVM +// version such as "21.1.8". +func ForLLVMVersion(version string) (Manifest, error) { + match := llvmMajorPattern.FindStringSubmatch(version) + if len(match) != 2 { + return Manifest{}, fmt.Errorf("parse LLVM version %q", version) + } + major, err := strconv.Atoi(match[1]) + if err != nil { + return Manifest{}, fmt.Errorf("parse LLVM version %q: %w", version, err) + } + return ForMajor(major) +} + +// ForMajor returns the published payload for one LLVM major version. +func ForMajor(major int) (Manifest, error) { + payload, ok := manifests[major] + if !ok { + return Manifest{}, fmt.Errorf("no LLGo LLVM payload for major version %d", major) + } + return Manifest{payload: payload}, nil +} + +func Default() (Manifest, error) { return ForMajor(DefaultMajor) } + +// Manifest provides read-only access to one payload release. +type Manifest struct { + payload manifest +} + +func (m Manifest) LLVMMajor() int { return m.payload.llvmMajor } + +func (m Manifest) Version() string { return m.payload.version } + +func (m Manifest) CompilerRTVersion() string { return m.payload.compilerRTVersion } + +func (m Manifest) BaseURL() string { + return releaseBaseURL + "/" + m.payload.version +} + +func (m Manifest) Platforms() []string { + platforms := make([]string, 0, len(m.payload.sha256)+len(m.payload.artifactOverrides)) + for platform := range m.payload.sha256 { + platforms = append(platforms, platform) + } + for platform := range m.payload.artifactOverrides { + platforms = append(platforms, platform) + } + sort.Strings(platforms) + return platforms +} + +func (m Manifest) Artifact(platform string) (Artifact, error) { + if override, ok := m.payload.artifactOverrides[platform]; ok { + filename := fmt.Sprintf("clang-esp-%s-%s.tar.xz", override.version, platform) + return Artifact{ + Platform: platform, + Version: override.version, + URL: override.baseURL + "/" + filename, + SHA256: override.sha256, + }, nil + } + checksum, ok := m.payload.sha256[platform] + if !ok { + return Artifact{}, fmt.Errorf("LLVM %d payload %s is unavailable for %s", m.LLVMMajor(), m.Version(), platform) + } + filename := fmt.Sprintf("clang-esp-%s-%s.tar.xz", m.Version(), platform) + return Artifact{ + Platform: platform, + Version: m.Version(), + URL: m.BaseURL() + "/" + filename, + SHA256: checksum, + }, nil +} + +// PlatformSuffix maps the host platform to the suffix used by payload assets. +func PlatformSuffix(goos, goarch string) (string, bool) { + switch goos + "/" + goarch { + case "darwin/amd64": + return "x86_64-apple-darwin", true + case "darwin/arm64": + return "aarch64-apple-darwin", true + case "linux/amd64": + return "x86_64-linux-gnu", true + case "linux/arm64": + return "aarch64-linux-gnu", true + case "windows/amd64", "windows/arm64": + // Espressif publishes an x86-64 Windows host toolchain. Windows on + // ARM64 runs it through the system's x64 emulation layer. + return espLLVM21WindowsPlatform, true + default: + return "", false + } +} diff --git a/internal/llvmpayload/payload_test.go b/internal/llvmpayload/payload_test.go new file mode 100644 index 0000000000..88a0924259 --- /dev/null +++ b/internal/llvmpayload/payload_test.go @@ -0,0 +1,107 @@ +package llvmpayload + +import ( + "encoding/hex" + "strings" + "testing" +) + +func testManifest(t *testing.T, llvmVersion, payloadVersion, compilerRTVersion string, wantMajor int) { + t.Helper() + manifest, err := ForLLVMVersion(llvmVersion) + if err != nil { + t.Fatal(err) + } + if manifest.LLVMMajor() != wantMajor || manifest.Version() != payloadVersion { + t.Fatalf("manifest identity = LLVM %d %s", manifest.LLVMMajor(), manifest.Version()) + } + if manifest.CompilerRTVersion() != compilerRTVersion { + t.Fatalf("compiler-rt version = %q, want %q", manifest.CompilerRTVersion(), compilerRTVersion) + } + platforms := manifest.Platforms() + if len(platforms) != 5 { + t.Fatalf("platform count = %d, want 5: %v", len(platforms), platforms) + } + for _, platform := range platforms { + artifact, err := manifest.Artifact(platform) + if err != nil { + t.Fatal(err) + } + if artifact.Platform != platform { + t.Errorf("artifact platform = %q, want %q", artifact.Platform, platform) + } + if !strings.HasSuffix(artifact.URL, "clang-esp-"+artifact.Version+"-"+platform+".tar.xz") { + t.Errorf("artifact URL = %q", artifact.URL) + } + checksum, err := hex.DecodeString(artifact.SHA256) + if err != nil || len(checksum) != 32 { + t.Errorf("artifact checksum = %q, err %v", artifact.SHA256, err) + } + } +} + +func TestLLVM21Manifest(t *testing.T) { + testManifest(t, "LLVM 21.1.8", "21.1.3_20260816", "xtensa_release_21.1.3_20260408", 21) + + manifest, err := Default() + if err != nil { + t.Fatal(err) + } + if manifest.LLVMMajor() != 21 || manifest.Version() != "21.1.3_20260816" { + t.Fatalf("default manifest identity = LLVM %d %s", manifest.LLVMMajor(), manifest.Version()) + } + windows, err := manifest.Artifact("x86_64-w64-mingw32") + if err != nil { + t.Fatal(err) + } + if windows.Version != "21.1.3_20260408" { + t.Fatalf("Windows artifact version = %q", windows.Version) + } + if windows.URL != "https://github.com/espressif/llvm-project/releases/download/esp-21.1.3_20260408/clang-esp-21.1.3_20260408-x86_64-w64-mingw32.tar.xz" { + t.Fatalf("Windows artifact URL = %q", windows.URL) + } + if windows.SHA256 != "415566ace6f47a9abc302b4ba79776d27668fd3f4e9c0d26861ec4f970323618" { + t.Fatalf("Windows artifact checksum = %q", windows.SHA256) + } +} + +func TestPayloadErrors(t *testing.T) { + if _, err := ForLLVMVersion("development"); err == nil { + t.Fatal("invalid LLVM version accepted") + } + if _, err := ForLLVMVersion(strings.Repeat("9", 100) + ".1.0"); err == nil { + t.Fatal("overflowing LLVM major accepted") + } + if _, err := ForMajor(19); err == nil { + t.Fatal("unpublished LLVM major accepted") + } + manifest, err := ForMajor(21) + if err != nil { + t.Fatal(err) + } + if _, err := manifest.Artifact("arm-linux-gnueabihf"); err == nil { + t.Fatal("unpublished platform accepted") + } +} + +func TestPlatformSuffix(t *testing.T) { + tests := []struct { + goos, goarch string + want string + ok bool + }{ + {goos: "darwin", goarch: "amd64", want: "x86_64-apple-darwin", ok: true}, + {goos: "darwin", goarch: "arm64", want: "aarch64-apple-darwin", ok: true}, + {goos: "linux", goarch: "amd64", want: "x86_64-linux-gnu", ok: true}, + {goos: "linux", goarch: "arm64", want: "aarch64-linux-gnu", ok: true}, + {goos: "linux", goarch: "arm", ok: false}, + {goos: "windows", goarch: "amd64", want: "x86_64-w64-mingw32", ok: true}, + {goos: "windows", goarch: "arm64", want: "x86_64-w64-mingw32", ok: true}, + } + for _, test := range tests { + got, ok := PlatformSuffix(test.goos, test.goarch) + if got != test.want || ok != test.ok { + t.Errorf("PlatformSuffix(%q, %q) = %q, %v; want %q, %v", test.goos, test.goarch, got, ok, test.want, test.ok) + } + } +} diff --git a/internal/lto/lto.go b/internal/lto/lto.go index 06967124d4..47fc3f579e 100644 --- a/internal/lto/lto.go +++ b/internal/lto/lto.go @@ -65,7 +65,7 @@ func (p PassPlugin) LinkerFlags(goos string) ([]string, error) { return nil, nil } if goos == "darwin" { - return nil, fmt.Errorf("LTO pass plugins are not supported on darwin by LLVM 19 ld64.lld or Apple ld64") + return nil, fmt.Errorf("LTO pass plugins are not supported on darwin by the bundled ld64.lld or Apple ld64") } return []string{"-Wl,--load-pass-plugin=" + p.Path}, nil } diff --git a/internal/xtool/llvm/llvm.go b/internal/xtool/llvm/llvm.go index 35f8109fc9..bb2d81fc05 100644 --- a/internal/xtool/llvm/llvm.go +++ b/internal/xtool/llvm/llvm.go @@ -80,6 +80,11 @@ func GetTargetTripleWithGOARM(goos, goarch, goarm string) string { if !armConfig.SoftFloat { triple += "hf" } + } else if llvmos == "linux" { + // Keep the GNU environment explicit. Recent Clang versions use it to + // map an unknown-vendor target to Debian's vendor-less GCC install + // triple (for example, aarch64-linux-gnu). + triple += "-gnu" } return triple } diff --git a/internal/xtool/llvm/llvm_test.go b/internal/xtool/llvm/llvm_test.go index 18e8d19944..9db8ca7566 100644 --- a/internal/xtool/llvm/llvm_test.go +++ b/internal/xtool/llvm/llvm_test.go @@ -139,9 +139,9 @@ func TestGetTargetTriple(t *testing.T) { // Run tests for different OS/arch combinations checkTriple(t, "wasip1/wasm", "wasip1", "wasm", "wasm32-unknown-wasip1") - checkTriple(t, "linux/amd64", "linux", "amd64", "x86_64-unknown-linux") - checkTriple(t, "linux/386", "linux", "386", "i386-unknown-linux") - checkTriple(t, "linux/arm64", "linux", "arm64", "aarch64-unknown-linux") + checkTriple(t, "linux/amd64", "linux", "amd64", "x86_64-unknown-linux-gnu") + checkTriple(t, "linux/386", "linux", "386", "i386-unknown-linux-gnu") + checkTriple(t, "linux/arm64", "linux", "arm64", "aarch64-unknown-linux-gnu") checkTriple(t, "linux/arm", "linux", "arm", "armv7-unknown-linux-gnueabihf") checkTriple(t, "darwin/amd64", "darwin", "amd64", "x86_64-apple-macosx") checkTriple(t, "darwin/arm64", "darwin", "arm64", "arm64-apple-macosx") diff --git a/ltoplugin/CMakeLists.txt b/ltoplugin/CMakeLists.txt index aec66c99b6..89288aa27a 100644 --- a/ltoplugin/CMakeLists.txt +++ b/ltoplugin/CMakeLists.txt @@ -1,11 +1,11 @@ cmake_minimum_required(VERSION 3.20) -project(LLGOLTOPlugin LANGUAGES CXX) +project(LLGOLTOPlugin LANGUAGES C CXX) find_package(LLVM REQUIRED CONFIG) -if(NOT LLVM_VERSION_MAJOR EQUAL 19) - message(FATAL_ERROR "LLGo LTO plugin requires LLVM 19.x, found ${LLVM_PACKAGE_VERSION}") +if(NOT LLVM_VERSION_MAJOR EQUAL 21) + message(FATAL_ERROR "LLGo LTO plugin requires LLVM 21.x, found ${LLVM_PACKAGE_VERSION}") endif() message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") diff --git a/ltoplugin/README.md b/ltoplugin/README.md index 399a7511dc..a1b4eaa4fa 100644 --- a/ltoplugin/README.md +++ b/ltoplugin/README.md @@ -3,11 +3,11 @@ This directory contains the optional LLVM new pass manager plugin used by LLGo full LTO builds. It is not required to build or use LLGo. -Build with the same LLVM 19 toolchain used by LLGo: +Build with the same LLVM 21 toolchain used by LLGo: ```sh cmake -S ltoplugin -B ltoplugin/build \ - -DLLVM_DIR=/path/to/llvm-19/lib/cmake/llvm \ + -DLLVM_DIR=/path/to/llvm-21/lib/cmake/llvm \ -DCMAKE_BUILD_TYPE=Release cmake --build ltoplugin/build ``` @@ -24,7 +24,7 @@ The plugin registers `llgo-lto-pre-globaldce` and also inserts that pass through LLVM's full LTO early extension point, so loading the plugin is enough for the pass to run before the normal full LTO optimization pipeline proceeds. -LLGo forwards the plugin path through lld's `--load-pass-plugin` option. LLVM 19 +LLGo forwards the plugin path through lld's `--load-pass-plugin` option. LLVM 21 `ld64.lld` and Apple `ld64` do not expose an equivalent new pass manager LTO plugin loading option for Mach-O links, so LLGo rejects `-lto-pass-plugin` for Darwin targets until the linker side grows that support. diff --git a/ssa/ssa_test.go b/ssa/ssa_test.go index 90eeaa2d1a..6e10d2d585 100644 --- a/ssa/ssa_test.go +++ b/ssa/ssa_test.go @@ -34,6 +34,7 @@ import ( "unsafe" "github.com/goplus/gogen/packages" + "github.com/xgo-dev/llgo/internal/littest" rtabi "github.com/xgo-dev/llgo/runtime/abi" "github.com/xgo-dev/llvm" ) @@ -2269,8 +2270,8 @@ func TestAny(t *testing.T) { func assertPkg(t *testing.T, p Package, expected string) { t.Helper() - got := StripModuleTarget(p.String()) - want := StripModuleTarget(expected) + got := littest.CanonicalizeLLVMIR(StripModuleTarget(p.String())) + want := littest.CanonicalizeLLVMIR(StripModuleTarget(expected)) if got != want { t.Fatalf("\n==> got:\n%s\n==> expected:\n%s\n", got, want) } @@ -2986,15 +2987,14 @@ attributes #1 = { returns_twice } func TestTargetMachineAndDataLayout(t *testing.T) { tests := []struct { - goos string - goarch string - dataLayout string - triple string + goos string + goarch string + triple string }{ - {"linux", "amd64", "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", "x86_64-unknown-linux"}, - {"linux", "arm64", "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32", "aarch64-unknown-linux"}, - {"darwin", "amd64", "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", "x86_64-apple-macosx"}, - {"darwin", "arm64", "e-m:o-i64:64-i128:128-n32:64-S128-Fn32", "arm64-apple-macosx"}, + {"linux", "amd64", "x86_64-unknown-linux-gnu"}, + {"linux", "arm64", "aarch64-unknown-linux-gnu"}, + {"darwin", "amd64", "x86_64-apple-macosx"}, + {"darwin", "arm64", "arm64-apple-macosx"}, } for _, tt := range tests { prog := NewProgram(&Target{GOOS: tt.goos, GOARCH: tt.goarch}) @@ -3011,14 +3011,15 @@ func TestTargetMachineAndDataLayout(t *testing.T) { t.Fatalf("%s/%s TargetData() returned nil", tt.goos, tt.goarch) } - // Test DataLayout() returns the expected data layout string - if dl := prog.DataLayout(); dl != tt.dataLayout { - t.Fatalf("%s/%s DataLayout mismatch: got %q, want %q", tt.goos, tt.goarch, dl, tt.dataLayout) + // Test DataLayout() returns a valid layout and is propagated to modules. + dl := prog.DataLayout() + if dl == "" || !strings.HasPrefix(dl, "e-") { + t.Fatalf("%s/%s DataLayout is invalid: %q", tt.goos, tt.goarch, dl) } pkg := prog.NewPackage("foo", "foo/bar") - if dl := pkg.Module().DataLayout(); dl != tt.dataLayout { - t.Fatalf("%s/%s module DataLayout mismatch: got %q, want %q", tt.goos, tt.goarch, dl, tt.dataLayout) + if moduleLayout := pkg.Module().DataLayout(); moduleLayout != dl { + t.Fatalf("%s/%s module DataLayout mismatch: got %q, want %q", tt.goos, tt.goarch, moduleLayout, dl) } // Test Target().Spec().Triple returns the expected triple diff --git a/targets/esp32.json b/targets/esp32.json index c88e5c4791..393cc8970b 100644 --- a/targets/esp32.json +++ b/targets/esp32.json @@ -3,7 +3,7 @@ "xtensa" ], "cpu": "esp32", - "features": "+atomctl,+bool,+clamps,+coprocessor,+debug,+density,+dfpaccel,+div32,+exception,+fp,+highpriinterrupts,+interrupt,+loop,+mac16,+memctl,+minmax,+miscsr,+mul32,+mul32high,+nsa,+prid,+regprotect,+rvector,+s32c1i,+sext,+threadptr,+timerint,+windowed", + "features": "+bool,+clamps,+coprocessor,+debug,+density,+dfpaccel,+div32,+exception,+fp,+highpriinterrupts,+interrupt,+loop,+mac16,+minmax,+miscsr,+mul32,+mul32high,+nsa,+prid,+regprotect,+rvector,+s32c1i,+sext,+threadptr,+windowed", "build-tags": [ "esp32", "esp" diff --git a/targets/esp32c3-basic.json b/targets/esp32c3-basic.json index 1478b326c7..671974940d 100644 --- a/targets/esp32c3-basic.json +++ b/targets/esp32c3-basic.json @@ -2,7 +2,7 @@ "inherits": [ "riscv32-esp" ], - "features": "+32bit,+c,+m,+zmmul,-a,-b,-d,-e,-experimental-smmpm,-experimental-smnpm,-experimental-ssnpm,-experimental-sspm,-experimental-ssqosid,-experimental-supm,-experimental-zacas,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-f,-h,-relax,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smepmp,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xesppie,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zaamo,-zabha,-zalrsc,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", + "features": "+32bit,+c,+m,+zmmul,-a,-b,-d,-e,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-f,-h,-relax,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smepmp,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zaamo,-zabha,-zalrsc,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", "build-tags": [ "esp32c3", "esp" diff --git a/targets/esp8266.json b/targets/esp8266.json index 390ce61c9c..59b18a120c 100644 --- a/targets/esp8266.json +++ b/targets/esp8266.json @@ -1,7 +1,7 @@ { "inherits": ["xtensa"], "cpu": "esp8266", - "features": "+debug,+density,+exception,+extendedl32r,+highpriinterrupts,+interrupt,+mul32,+nsa,+prid,+regprotect,+rvector,+timerint", + "features": "+debug,+density,+exception,+extendedl32r,+highpriinterrupts,+interrupt,+mul32,+nsa,+prid,+regprotect,+rvector", "build-tags": ["esp8266", "esp"], "scheduler": "tasks", "linker": "ld.lld", diff --git a/targets/fe310.json b/targets/fe310.json index cd92c4fb1b..370d547a20 100644 --- a/targets/fe310.json +++ b/targets/fe310.json @@ -1,6 +1,6 @@ { "inherits": ["riscv32"], "cpu": "sifive-e31", - "features": "+32bit,+a,+c,+m,+zmmul,-b,-d,-e,-experimental-smmpm,-experimental-smnpm,-experimental-ssnpm,-experimental-sspm,-experimental-ssqosid,-experimental-supm,-experimental-zacas,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-f,-h,-relax,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smepmp,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xesppie,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zaamo,-zabha,-zalrsc,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", + "features": "+32bit,+a,+c,+m,+zmmul,-b,-d,-e,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-f,-h,-relax,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smepmp,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zaamo,-zabha,-zalrsc,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", "build-tags": ["fe310", "sifive"] } diff --git a/targets/k210.json b/targets/k210.json index 2140f459e4..b2c64e6489 100644 --- a/targets/k210.json +++ b/targets/k210.json @@ -1,6 +1,6 @@ { "inherits": ["riscv64"], - "features": "+64bit,+a,+c,+d,+f,+m,+zicsr,+zifencei,+zmmul,-b,-e,-experimental-smmpm,-experimental-smnpm,-experimental-ssnpm,-experimental-sspm,-experimental-ssqosid,-experimental-supm,-experimental-zacas,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-h,-relax,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smepmp,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xesppie,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zaamo,-zabha,-zalrsc,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", + "features": "+64bit,+a,+c,+d,+f,+m,+zicsr,+zifencei,+zmmul,-b,-e,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-h,-relax,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smepmp,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zaamo,-zabha,-zalrsc,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", "build-tags": ["k210", "kendryte"], "code-model": "medium" } diff --git a/targets/riscv-qemu.json b/targets/riscv-qemu.json index 318089332b..1f56796836 100644 --- a/targets/riscv-qemu.json +++ b/targets/riscv-qemu.json @@ -2,7 +2,7 @@ "inherits": [ "riscv32" ], - "features": "+32bit,+a,+c,+m,+zihintpause,+zmmul,-b,-d,-e,-experimental-smmpm,-experimental-smnpm,-experimental-ssnpm,-experimental-sspm,-experimental-ssqosid,-experimental-supm,-experimental-zacas,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-f,-h,-relax,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smepmp,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xesppie,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zaamo,-zabha,-zalrsc,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", + "features": "+32bit,+a,+c,+m,+zihintpause,+zmmul,-b,-d,-e,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-f,-h,-relax,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smepmp,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zaamo,-zabha,-zalrsc,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", "build-tags": [ "virt", "qemu" diff --git a/xtool/env/llvm/llvm_config_darwin_amd64_llvm19.go b/xtool/env/llvm/llvm_config_darwin_amd64_llvm19.go index c1ef8cd433..cf2b8c48b3 100644 --- a/xtool/env/llvm/llvm_config_darwin_amd64_llvm19.go +++ b/xtool/env/llvm/llvm_config_darwin_amd64_llvm19.go @@ -1,4 +1,4 @@ -//go:build !byollvm && darwin && amd64 && !llvm14 && !llvm15 && !llvm16 && !llvm17 && !llvm18 +//go:build !byollvm && darwin && amd64 && llvm19 /* * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. diff --git a/xtool/env/llvm/llvm_config_darwin_amd64_llvm21.go b/xtool/env/llvm/llvm_config_darwin_amd64_llvm21.go new file mode 100644 index 0000000000..3bd51bd7db --- /dev/null +++ b/xtool/env/llvm/llvm_config_darwin_amd64_llvm21.go @@ -0,0 +1,21 @@ +//go:build !byollvm && darwin && amd64 && !llvm14 && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19 && !llvm20 && !llvm22 + +/* + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package llvm + +const ldLLVMConfigBin = "/usr/local/opt/llvm@21/bin/llvm-config" diff --git a/xtool/env/llvm/llvm_config_darwin_llvm19.go b/xtool/env/llvm/llvm_config_darwin_llvm19.go index 08baa210c9..fe157b2153 100644 --- a/xtool/env/llvm/llvm_config_darwin_llvm19.go +++ b/xtool/env/llvm/llvm_config_darwin_llvm19.go @@ -1,4 +1,4 @@ -//go:build !byollvm && darwin && !amd64 && !llvm14 && !llvm15 && !llvm16 && !llvm17 && !llvm18 +//go:build !byollvm && darwin && !amd64 && llvm19 /* * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. diff --git a/xtool/env/llvm/llvm_config_darwin_llvm21.go b/xtool/env/llvm/llvm_config_darwin_llvm21.go new file mode 100644 index 0000000000..ac3c5e5ef7 --- /dev/null +++ b/xtool/env/llvm/llvm_config_darwin_llvm21.go @@ -0,0 +1,21 @@ +//go:build !byollvm && darwin && !amd64 && !llvm14 && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19 && !llvm20 && !llvm22 + +/* + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package llvm + +const ldLLVMConfigBin = "/opt/homebrew/opt/llvm@21/bin/llvm-config" diff --git a/xtool/env/llvm/llvm_config_linux_llvm19.go b/xtool/env/llvm/llvm_config_linux_llvm19.go index 6fca306d4d..5d1fe44989 100644 --- a/xtool/env/llvm/llvm_config_linux_llvm19.go +++ b/xtool/env/llvm/llvm_config_linux_llvm19.go @@ -1,4 +1,4 @@ -//go:build !byollvm && linux && !llvm14 && !llvm15 && !llvm16 && !llvm17 && !llvm18 +//go:build !byollvm && linux && llvm19 /* * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. diff --git a/xtool/env/llvm/llvm_config_linux_llvm21.go b/xtool/env/llvm/llvm_config_linux_llvm21.go new file mode 100644 index 0000000000..15a9cdfb2b --- /dev/null +++ b/xtool/env/llvm/llvm_config_linux_llvm21.go @@ -0,0 +1,21 @@ +//go:build !byollvm && linux && !llvm14 && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19 && !llvm20 && !llvm22 + +/* + * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package llvm + +const ldLLVMConfigBin = "/usr/lib/llvm-21/bin/llvm-config" diff --git a/xtool/env/llvm/llvm_test.go b/xtool/env/llvm/llvm_test.go index 16a45005fe..73effcb2e0 100644 --- a/xtool/env/llvm/llvm_test.go +++ b/xtool/env/llvm/llvm_test.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "runtime" + "strings" "testing" ) @@ -54,3 +55,50 @@ func TestSetupPathIgnoresMissingBinDir(t *testing.T) { t.Fatalf("PATH changed from %q to %q without an LLVM bin directory", before, got) } } + +func TestParseMajorVersion(t *testing.T) { + tests := []struct { + version string + want int + }{ + {version: "21.1.8", want: 21}, + {version: "Homebrew clang version 21.1.8", want: 21}, + {version: "Homebrew LLD 21.1.8 (compatible with GNU linkers)", want: 21}, + } + for _, test := range tests { + got, err := parseMajorVersion(test.version) + if err != nil { + t.Fatalf("parseMajorVersion(%q): %v", test.version, err) + } + if got != test.want { + t.Fatalf("parseMajorVersion(%q) = %d, want %d", test.version, got, test.want) + } + } +} + +func TestValidateToolchainMajor(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("test helper uses a shell script") + } + + matching := writeVersionTool(t, "clang 21.1.3") + if err := ValidateToolchainMajor("21.1.8", matching); err != nil { + t.Fatalf("matching major rejected: %v", err) + } + + mismatched := writeVersionTool(t, "clang 19.1.7") + err := ValidateToolchainMajor("21.1.8", mismatched) + if err == nil || !strings.Contains(err.Error(), "LLVM major version mismatch") { + t.Fatalf("mismatched major error = %v", err) + } +} + +func writeVersionTool(t *testing.T, version string) string { + t.Helper() + tool := filepath.Join(t.TempDir(), "llvm-tool") + contents := "#!/bin/sh\nprintf '%s\\n' '" + version + "'\n" + if err := os.WriteFile(tool, []byte(contents), 0o755); err != nil { + t.Fatal(err) + } + return tool +} diff --git a/xtool/env/llvm/version.go b/xtool/env/llvm/version.go new file mode 100644 index 0000000000..60c0c4bfee --- /dev/null +++ b/xtool/env/llvm/version.go @@ -0,0 +1,54 @@ +package llvm + +import ( + "fmt" + "os/exec" + "regexp" + "strconv" + "strings" +) + +var dottedVersion = regexp.MustCompile(`(?:^|[^0-9])([0-9]+)\.[0-9]+(?:\.[0-9]+)?`) + +// ValidateToolchainMajor checks that every named LLVM tool has the same major +// version as the LLVM library linked into the current LLGo process. +func ValidateToolchainMajor(linkedVersion string, tools ...string) error { + linkedMajor, err := parseMajorVersion(linkedVersion) + if err != nil { + return fmt.Errorf("parse linked LLVM version %q: %w", linkedVersion, err) + } + for _, tool := range tools { + output, err := exec.Command(tool, "--version").CombinedOutput() + if err != nil { + return fmt.Errorf("query LLVM tool %q: %w", tool, err) + } + toolVersion := strings.TrimSpace(string(output)) + toolMajor, err := parseMajorVersion(toolVersion) + if err != nil { + return fmt.Errorf("parse LLVM tool %q version %q: %w", tool, toolVersion, err) + } + if toolMajor != linkedMajor { + return fmt.Errorf("LLVM major version mismatch: linked LLVM %s, %s reports %s", linkedVersion, tool, firstLine(toolVersion)) + } + } + return nil +} + +func parseMajorVersion(version string) (int, error) { + match := dottedVersion.FindStringSubmatch(version) + if len(match) != 2 { + return 0, fmt.Errorf("no dotted version found") + } + major, err := strconv.Atoi(match[1]) + if err != nil { + return 0, err + } + return major, nil +} + +func firstLine(value string) string { + if i := strings.IndexByte(value, '\n'); i >= 0 { + return value[:i] + } + return value +}