Skip to content

build: upgrade LLGo to LLVM 21 - #2334

Open
zhouguangyuan0718 wants to merge 18 commits into
xgo-dev:mainfrom
zhouguangyuan0718:codex/llvm21-upgrade
Open

build: upgrade LLGo to LLVM 21#2334
zhouguangyuan0718 wants to merge 18 commits into
xgo-dev:mainfrom
zhouguangyuan0718:codex/llvm21-upgrade

Conversation

@zhouguangyuan0718

@zhouguangyuan0718 zhouguangyuan0718 commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • switch LLGo's default host LLVM, Clang, LLD, CI, development image, install documentation, and LTO plugin requirement from LLVM 19 to LLVM 21
  • use the revision-locked Espressif LLVM 21 payload 21.1.3_20260816 for release archives and automatic cross-toolchain downloads, with checked-in SHA-256 verification
  • use goplus/compiler-rt tag xtensa_release_21.1.3_20260408 for cross-runtime builds
  • fail fast when the linked LLVM library and command-line tools have different major versions
  • include LLVM/payload identity, target CPU/features, optimization level, and LTO mode in compiled cross-library cache keys
  • keep named-target CPU/features on the external clang/lld path and remove feature names no longer accepted by LLVM 21
  • update LLVM 21 IR spelling differences and require LLVM 21 for LLGOLTOPlugin
  • emit complete GNU Linux target triples so LLVM 21 Clang can discover Debian's vendor-less GCC and libstdc++ installation
  • remove the LLVM 19 compatibility lanes; this PR switches the supported/default LLGo toolchain in one step

Temporary Go LLVM dependency

Until xgo-dev/llvm#48 is merged and tagged, this branch temporarily replaces github.com/xgo-dev/llvm with the exact fork pseudo-version:

github.com/zhouguangyuan0718/go-llvm v0.0.0-20260827144345-45af6df6739a

That version resolves to PR head 45af6df6739a8be0168070ac35ab63170f1378e3, including the LLVMIsOpaqueStruct binding needed to preserve defined empty structs across LLVM contexts. After the binding release is available, the temporary replace will be removed and the normal github.com/xgo-dev/llvm requirement will be updated before merge.

Validation

  • rebased on current main (ff9bad850)
  • no LLVM build tag: go test passed for internal/dcepass, internal/llvmpayload, all internal/crosscompile/... packages, internal/build, internal/littest, ssa, and xtool/env/llvm, using the remotely downloadable fork pseudo-version
  • configured and built LLGOLTOPlugin.dylib against LLVM 21.1.8 with ccache
  • checked the generated payload contract: LLVM 21, payload 21.1.3_20260816, and all four published artifact digests
  • workflow YAML parsing and git diff --check
  • GitHub Actions passed the LLVM 21 main/compatibility, cross-compile, wasm, build-cache, LTO GlobalDCE, and Go Method Drop lanes on the previous head; on head 2e9d23ff6, the release build and Linux amd64/arm64 artifact smoke tests pass with complete GNU triples and no GCC-specific workaround, while the remaining matrix finishes

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: LLVM 21 upgrade

Solid, well-tested change. The new internal/llvmpayload package cleanly centralizes the previously-scattered ESP Clang constants, SHA256 verification (Go and bash, verified before extraction, with cleanup on mismatch and a dedicated negative test) is a real integrity improvement, and the bash hardening (set -euo pipefail, trap cleanup, download-then-verify-then-extract) is correct and consistent with the Go path.

A few items are worth addressing before merge — most importantly whether the LLVM 21 payload/toolchain path is actually functional, and the platform-specific linker/compiler names in the new toolchain validator. See inline comments.

Findings that have no reliable inline location:

  • Docs still pin LLVM 19. ltoplugin/README.md (build instructions, -DLLVM_DIR=/path/to/llvm-19/..., and the "LLVM 19 ld64.lld" wording) still hard-codes LLVM 19 even though CMakeLists.txt now accepts 19.x or 21.x and internal/lto/lto.go was de-versioned to "the bundled ld64.lld". Similarly, README.md (generated from doc/_readme/scripts/install_macos.sh / install_ubuntu.sh) still installs only llvm@19/llvm-19 and does not mention the new toolchain-major validation requirement introduced by validateLLVMToolchain — a user who builds with -tags llvm21 but installs LLVM 19 tools will now hit "LLVM major version mismatch". Consider updating these docs (fix belongs in the doc/_readme/scripts/*.sh sources for the generated README).
  • WASI SDK download remains unverified (pre-existing, low). Now that the checksum plumbing exists (downloadAndExtractArchiveWithChecksum), the WASI SDK download still passes an empty checksum and skips verification. Not introduced here, but a natural candidate to pin next since this PR is about hardening this path.

Comment on lines +33 to +44
var manifests = map[int]manifest{
19: {
llvmMajor: 19,
version: "19.1.2_20250905-3",
sha256: map[string]string{
"aarch64-apple-darwin": "4f15d18c93eabdace3eab901582e528ac334d328fb8f19f153ee55b2208d101b",
"aarch64-linux-gnu": "b2d8e77bbf3394c6a1f0d66e59385d78d2b49b97ebe782e612cba7f93dcb2337",
"x86_64-apple-darwin": "e4f329a911e813ee825984f039578614dc0fe69001c2afe3e61edf27821be3ad",
"x86_64-linux-gnu": "e2e0c48cd76e45ceba910917a2a97988dc80e3bb6040ea262bfe9293d5d9ac57",
},
},
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] No LLVM 21 payload: ESP cross-compile fails under -tags llvm21

This PR adds llvm21 build-tag config files and widens the LTO plugin to accept LLVM 21, but manifests contains only the 19 entry and DefaultMajor is still 19. When LLGo is built with -tags llvm21, gllvm.Version reports 21.x, so getESPClangRoot -> llvmpayload.ForLLVMVersion("21...") -> ForMajor(21) returns "no LLGo LLVM payload for major version 21" (crosscompile.go:141-144), and any ESP/cross-compile build on an LLVM 21 toolchain fails.

If the LLVM 21 ESP payload simply is not published yet and 19-only downloads are intentional for now, that is fine — but it should be documented, and the package doc comment ("toolchains distributed with and downloaded by LLGo", ForLLVMVersion example 21.1.8) overstates availability. As written, the headline "upgrade to LLVM 21" is not functional for the ESP download path.

Comment thread internal/build/build.go
Comment on lines +415 to +417
if err := validateLLVMToolchain(export); err != nil {
return nil, fmt.Errorf("invalid LLVM toolchain: %w", err)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] validateLLVMToolchain runs 3 uncached --version subprocesses on every Build

Build now unconditionally calls validateLLVMToolchain, which spawns three subprocesses (llvm-config, clang/export.CC, ld.lld) via exec.Command(tool, "--version"). For a one-shot llgo build this is negligible. But in-process, high-fan-out drivers (the cltest harness, go test suites that call Build/Do once per package) will spawn hundreds x 3 --version processes per run, all producing the same answer since gllvm.Version and the toolchain paths are constant for the process lifetime. Consider memoizing the result per (linkedVersion, toolPath) in ValidateToolchainMajor so repeated Build calls pay the cost once.

Comment on lines +108 to +121
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
default:
return "", false
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] PlatformSuffix drops linux/arm and windows/amd64 mappings

The refactored getESPClangPlatform now delegates to PlatformSuffix, which only maps darwin/linux amd64/arm64. The previous inline switch also handled linux/arm (arm-linux-gnueabihf) and windows/amd64 (x86_64-w64-mingw32). If any target config or CI relied on those, the download path now returns "" -> "platform ... is not supported for download". If the removal is intentional (those payloads never shipped), please confirm; otherwise it is a regression.

Comment on lines +32 to +33
fmt.Printf("LLGO_LLVM_MAJOR=%s\n", fmt.Sprint(manifest.LLVMMajor()))
fmt.Printf("ESP_CLANG_LLVM_MAJOR=%s\n", fmt.Sprint(manifest.LLVMMajor()))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] Unnecessary fmt.Sprint on int; use %d

LLVMMajor() returns int; fmt.Printf("...=%s\n", fmt.Sprint(manifest.LLVMMajor())) formats an int to string then re-formats as %s. Use %d directly. Also note the mixed error handling below: a missing platform mapping panics while a missing artifact does os.Exit(1). For a CLI feeding $GITHUB_ENV, prefer the stderr + os.Exit(1) form for both so CI failures are readable without a Go stack trace.

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review (supplemental)

One additional inline finding from the LLVM 21 review; see comment.

Comment thread internal/build/build.go
Comment on lines +805 to +811
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"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Toolchain validator hardcodes ld.lld/clang (breaks darwin/windows)

validateLLVMToolchain always probes ld.lld, but the linker is platform-specific: darwin toolchains ship ld64.lld and windows uses lld-link (crosscompile.go:290,295). ClangRoot is set to the ESP Clang bundle for darwin and linux hosts, so on macOS filepath.Join(binDir, "ld.lld") does not exist and ValidateToolchainMajor fails with query LLVM tool ... ld.lld ... no such file, aborting the build on exactly the darwin release path this PR cares about.

Also, the ClangRoot branch probes filepath.Join(binDir, "clang"), but the bundle and export.CC only guarantee clang++ (crosscompile.go:217). Prefer validating export.CC and deriving the linker name from the target GOOS (mirroring the crosscompile linker logic) instead of hardcoding ld.lld/clang.

@codecov

codecov Bot commented Aug 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.35865% with 11 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/llvmpayload/cmd/llvmpayload/main.go 85.36% 6 Missing ⚠️
internal/crosscompile/crosscompile.go 93.75% 2 Missing ⚠️
internal/build/build.go 92.85% 1 Missing ⚠️
internal/crosscompile/fetch.go 95.45% 1 Missing ⚠️
internal/crosscompile/libc.go 97.91% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown

LLGo baseline benchmarks

c4c910c4fb20 | workflow run | long-term charts

Program measurements

Platform Workload File size vs base Text size vs base Build vs base Run vs base
Linux cprintf 19496 B 0 B / +0.0% 387 B 0 B / +0.0% 357.160 ms +27.51 ms / +8.3% (worse) 1.257 ms -17.52 us / -1.4% (better)
Linux cprintf-lto 19328 B 0 B / +0.0% 368 B 0 B / +0.0% 354.327 ms +17.77 ms / +5.3% (worse) 1.255 ms +8.145 us / +0.7% (worse)
Linux fmtprintf 1651136 B 0 B / +0.0% 500315 B 0 B / +0.0% 2.554 s +38.9 ms / +1.5% (worse) 3.170 ms +61.38 us / +2.0% (worse)
Linux fmtprintf-lto 1535888 B -8 B / -0.0005209% (better) 471152 B 0 B / +0.0% 8.844 s +68.79 ms / +0.8% (worse) 2.986 ms -30.89 us / -1.0% (better)
Linux println 62360 B 0 B / +0.0% 15357 B 0 B / +0.0% 353.621 ms +14.19 ms / +4.2% (worse) 1.637 ms +79.38 us / +5.1% (worse)
Linux println-lto 54256 B 0 B / +0.0% 12900 B 0 B / +0.0% 542.694 ms +21.24 ms / +4.1% (worse) 1.574 ms -65.32 us / -4.0% (better)
macOS cprintf 84480 B 0 B / +0.0% 16733 B 0 B / +0.0% 492.580 ms +95.14 ms / +23.9% (worse) 2.522 ms -37.38 us / -1.5% (better)
macOS cprintf-lto 100704 B 0 B / +0.0% 16713 B 0 B / +0.0% 453.804 ms +30.95 ms / +7.3% (worse) 2.557 ms +72.54 us / +2.9% (worse)
macOS fmtprintf 1512880 B 0 B / +0.0% 884024 B 0 B / +0.0% 2.253 s +133.2 ms / +6.3% (worse) 5.107 ms -205.7 us / -3.9% (better)
macOS fmtprintf-lto 1224880 B 0 B / +0.0% 895712 B 0 B / +0.0% 5.695 s -441.6 ms / -7.2% (better) 5.327 ms -558.4 us / -9.5% (better)
macOS println 114784 B 0 B / +0.0% 35437 B 0 B / +0.0% 441.053 ms +51.36 ms / +13.2% (worse) 3.266 ms -133.5 us / -3.9% (better)
macOS println-lto 118656 B 0 B / +0.0% 33145 B 0 B / +0.0% 641.844 ms +88.17 ms / +15.9% (worse) 3.385 ms +156.9 us / +4.9% (worse)
Windows cprintf 120320 B 0 B / +0.0% 65782 B 0 B / +0.0% 884.294 ms +75.3 ms / +9.3% (worse) 3.420 ms -477.7 us / -12.3% (better)
Windows cprintf-lto 119808 B 0 B / +0.0% 65702 B 0 B / +0.0% 898.695 ms +6.535 ms / +0.7% (worse) 3.452 ms -249.3 us / -6.7% (better)
Windows fmtprintf 1607168 B 0 B / +0.0% 685686 B 0 B / +0.0% 3.749 s +91.3 ms / +2.5% (worse) 8.445 ms -33.3 us / -0.4% (better)
Windows fmtprintf-lto 1671168 B 0 B / +0.0% 694198 B 0 B / +0.0% 10.378 s -468.7 ms / -4.3% (better) 9.377 ms +628.3 us / +7.2% (worse)
Windows println 194048 B 0 B / +0.0% 120598 B 0 B / +0.0% 854.457 ms +44.79 ms / +5.5% (worse) 7.606 ms +83 us / +1.1% (worse)
Windows println-lto 190976 B 0 B / +0.0% 117574 B 0 B / +0.0% 1.099 s +87.46 ms / +8.7% (worse) 7.533 ms +807.1 us / +12.0% (worse)
Core language and compiler benchmarks
Platform Benchmark ns/op vs base
Linux BenchmarkLookupPCRandom 13.320 ns/op -0.08 ns/op / -0.6% (better)
Linux BenchmarkMergeCompilerFlags 151.300 ns/op -0.6 ns/op / -0.4% (better)
Linux BenchmarkMergeLinkerFlags 103.100 ns/op -1.1 ns/op / -1.1% (better)
Linux BenchmarkChannelBuffered 40.510 ns/op -0.02 ns/op / -0.04935% (better)
Linux BenchmarkChannelHandoff 28870 ns/op +1118 ns/op / +4.0% (worse)
Linux BenchmarkDefer 45.400 ns/op +0.56 ns/op / +1.2% (worse)
Linux BenchmarkDirectCall 1.872 ns/op +0.004 ns/op / +0.2% (worse)
Linux BenchmarkGlobalRead 1.556 ns/op -0.001 ns/op / -0.1% (better)
Linux BenchmarkGlobalWrite 2.486 ns/op -0.001 ns/op / -0.04021% (better)
Linux BenchmarkGoroutine 32810 ns/op -10762 ns/op / -24.7% (better)
Linux BenchmarkInterfaceCall 7.780 ns/op -0.007 ns/op / -0.1% (better)
Linux BenchmarkRuntimeGetG 1.867 ns/op 0 ns/op / +0.0%
macOS BenchmarkLookupPCRandom 11.310 ns/op -1.08 ns/op / -8.7% (better)
macOS BenchmarkMergeCompilerFlags 107.400 ns/op -25.2 ns/op / -19.0% (better)
macOS BenchmarkMergeLinkerFlags 75.560 ns/op +0.69 ns/op / +0.9% (worse)
macOS BenchmarkChannelBuffered 23.570 ns/op -1.97 ns/op / -7.7% (better)
macOS BenchmarkChannelHandoff 7028 ns/op -2388 ns/op / -25.4% (better)
macOS BenchmarkDefer 30.730 ns/op -5.96 ns/op / -16.2% (better)
macOS BenchmarkDirectCall 1.012 ns/op -0.05 ns/op / -4.7% (better)
macOS BenchmarkGlobalRead 1.124 ns/op -0.018 ns/op / -1.6% (better)
macOS BenchmarkGlobalWrite 1.040 ns/op -0.042 ns/op / -3.9% (better)
macOS BenchmarkGoroutine 32612 ns/op +2721 ns/op / +9.1% (worse)
macOS BenchmarkInterfaceCall 4.392 ns/op -0.342 ns/op / -7.2% (better)
macOS BenchmarkRuntimeGetG 2.023 ns/op -0.179 ns/op / -8.1% (better)
Windows BenchmarkLookupPCRandom 15.930 ns/op +0.02 ns/op / +0.1% (worse)
Windows BenchmarkMergeCompilerFlags 587.200 ns/op -18.5 ns/op / -3.1% (better)
Windows BenchmarkMergeLinkerFlags 533.900 ns/op +9.8 ns/op / +1.9% (worse)
Windows BenchmarkChannelBuffered 35.390 ns/op +0.06 ns/op / +0.2% (worse)
Windows BenchmarkChannelHandoff 1027 ns/op +76.2 ns/op / +8.0% (worse)
Windows BenchmarkDefer 56.850 ns/op +0.17 ns/op / +0.3% (worse)
Windows BenchmarkDirectCall 1.549 ns/op +0.001 ns/op / +0.1% (worse)
Windows BenchmarkGlobalRead 1.555 ns/op +0.006 ns/op / +0.4% (worse)
Windows BenchmarkGlobalWrite 2.440 ns/op -0.002 ns/op / -0.1% (better)
Windows BenchmarkGoroutine 93593 ns/op +2755 ns/op / +3.0% (worse)
Windows BenchmarkInterfaceCall 9.303 ns/op -0.004 ns/op / -0.04298% (better)
Windows BenchmarkRuntimeGetG 2.787 ns/op -0.007 ns/op / -0.3% (better)

Compared with c6857d23fec4 measured in the same runner job.

@zhouguangyuan0718
zhouguangyuan0718 force-pushed the codex/llvm21-upgrade branch 2 times, most recently from 4b6e55f to bf6d6ef Compare August 15, 2026 08:42
@zhouguangyuan0718 zhouguangyuan0718 changed the title build: prepare the LLVM 21 upgrade build: upgrade LLGo to LLVM 21 Aug 27, 2026
@zhouguangyuan0718
zhouguangyuan0718 force-pushed the codex/llvm21-upgrade branch 4 times, most recently from 0e3c4ae to 3ec7475 Compare August 28, 2026 09:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant