From 7ffd181b4f19e082603a5276d3323a95fb468f31 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Sat, 15 Aug 2026 14:43:02 +0800 Subject: [PATCH 01/28] build: prepare LLVM 21 compatibility --- internal/build/build.go | 20 +++++++ internal/littest/littest.go | 10 ++++ internal/littest/littest_test.go | 9 ++++ internal/lto/lto.go | 2 +- ltoplugin/CMakeLists.txt | 6 +-- ssa/ssa_test.go | 31 +++++------ .../llvm/llvm_config_darwin_amd64_llvm19.go | 2 +- .../llvm/llvm_config_darwin_amd64_llvm21.go | 21 ++++++++ xtool/env/llvm/llvm_config_darwin_llvm19.go | 2 +- xtool/env/llvm/llvm_config_darwin_llvm21.go | 21 ++++++++ xtool/env/llvm/llvm_config_linux_llvm19.go | 2 +- xtool/env/llvm/llvm_config_linux_llvm21.go | 21 ++++++++ xtool/env/llvm/llvm_test.go | 48 +++++++++++++++++ xtool/env/llvm/version.go | 54 +++++++++++++++++++ 14 files changed, 227 insertions(+), 22 deletions(-) create mode 100644 xtool/env/llvm/llvm_config_darwin_amd64_llvm21.go create mode 100644 xtool/env/llvm/llvm_config_darwin_llvm21.go create mode 100644 xtool/env/llvm/llvm_config_linux_llvm21.go create mode 100644 xtool/env/llvm/version.go diff --git a/internal/build/build.go b/internal/build/build.go index be4dbda5c2..4e6382fdc1 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. 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/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/ltoplugin/CMakeLists.txt b/ltoplugin/CMakeLists.txt index aec66c99b6..2237892f4f 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 19 AND NOT LLVM_VERSION_MAJOR EQUAL 21) + message(FATAL_ERROR "LLGo LTO plugin requires LLVM 19.x or 21.x, found ${LLVM_PACKAGE_VERSION}") endif() message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") diff --git a/ssa/ssa_test.go b/ssa/ssa_test.go index 90eeaa2d1a..9a00cec411 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"}, + {"linux", "arm64", "aarch64-unknown-linux"}, + {"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/xtool/env/llvm/llvm_config_darwin_amd64_llvm19.go b/xtool/env/llvm/llvm_config_darwin_amd64_llvm19.go index c1ef8cd433..e330bbd2c0 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 && !llvm14 && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm20 && !llvm21 && !llvm22 /* * 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..c815343500 --- /dev/null +++ b/xtool/env/llvm/llvm_config_darwin_amd64_llvm21.go @@ -0,0 +1,21 @@ +//go:build !byollvm && darwin && amd64 && llvm21 + +/* + * 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..829203508b 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 && !llvm14 && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm20 && !llvm21 && !llvm22 /* * 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..757611e6b2 --- /dev/null +++ b/xtool/env/llvm/llvm_config_darwin_llvm21.go @@ -0,0 +1,21 @@ +//go:build !byollvm && darwin && !amd64 && llvm21 + +/* + * 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..e9eabea377 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 && !llvm14 && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm20 && !llvm21 && !llvm22 /* * 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..d10082059f --- /dev/null +++ b/xtool/env/llvm/llvm_config_linux_llvm21.go @@ -0,0 +1,21 @@ +//go:build !byollvm && linux && llvm21 + +/* + * 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 +} From f103223c596e4b24e05272f5fd6febd014f0c2e7 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Sat, 15 Aug 2026 14:44:05 +0800 Subject: [PATCH 02/28] build: centralize the LLVM payload contract --- .github/actions/setup-goreleaser/action.yml | 7 +- .github/workflows/download_esp_clang.sh | 63 ++++++++-- .github/workflows/release-build.yml | 2 + .goreleaser.yaml | 8 +- internal/crosscompile/crosscompile.go | 73 +++++------ internal/crosscompile/crosscompile_test.go | 34 ++++-- internal/crosscompile/fetch.go | 36 +++++- internal/crosscompile/fetch_test.go | 71 ++++++++--- internal/llvmpayload/cmd/llvmpayload/main.go | 55 +++++++++ internal/llvmpayload/payload.go | 121 +++++++++++++++++++ internal/llvmpayload/payload_test.go | 71 +++++++++++ 11 files changed, 458 insertions(+), 83 deletions(-) create mode 100644 internal/llvmpayload/cmd/llvmpayload/main.go create mode 100644 internal/llvmpayload/payload.go create mode 100644 internal/llvmpayload/payload_test.go 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/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/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/.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/internal/crosscompile/crosscompile.go b/internal/crosscompile/crosscompile.go index 50c75656d5..6c0e9cfc47 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 { @@ -184,8 +186,6 @@ var ( ) 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" ) @@ -195,6 +195,20 @@ const espClangWindowsPlatform = "x86_64-w64-mingw32" // cacheRoot can be overridden for testing var cacheRoot = env.LLGoCacheDir +var resolveESPClangArtifact = espClangArtifact + +func espClangArtifact(payload llvmpayload.Manifest, platform string) (llvmpayload.Artifact, error) { + if platform == espClangWindowsPlatform { + filename := fmt.Sprintf("clang-esp-%s-%s.tar.xz", espClangWindowsVersion, platform) + return llvmpayload.Artifact{ + Platform: platform, + Version: espClangWindowsVersion, + URL: espClangWindowsBaseUrl + "/" + filename, + }, nil + } + return payload.Artifact(platform) +} + func cacheDir() string { return filepath.Join(cacheRoot(), "crosscompile") } @@ -258,17 +272,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 +304,13 @@ 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 + if goos == "windows" && (goarch == "amd64" || goarch == "arm64") { + // Espressif publishes an x86-64 Windows host toolchain. Windows on + // ARM64 runs it through the system's x64 emulation layer. + return espClangWindowsPlatform } - return espClangBaseUrl, espClangVersion + platform, _ := llvmpayload.PlatformSuffix(goos, goarch) + return platform } // ldFlagsFromFileName extracts the library name from a filename for use in linker flags diff --git a/internal/crosscompile/crosscompile_test.go b/internal/crosscompile/crosscompile_test.go index 7055e35f63..e132dd1281 100644 --- a/internal/crosscompile/crosscompile_test.go +++ b/internal/crosscompile/crosscompile_test.go @@ -13,6 +13,7 @@ import ( "strings" "testing" + "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" @@ -26,13 +27,17 @@ 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}, + {"darwin", "arm64", "aarch64-apple-darwin", payload.Version()}, + {"linux", "amd64", "x86_64-linux-gnu", payload.Version()}, {"windows", "amd64", espClangWindowsPlatform, espClangWindowsVersion}, {"windows", "arm64", espClangWindowsPlatform, espClangWindowsVersion}, } @@ -42,9 +47,13 @@ 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 := espClangArtifact(payload, platform) + if err != nil { + t.Errorf("espClangArtifact(%q) error = %v", platform, err) + continue + } + if artifact.Version != test.wantVersion { + t.Errorf("espClangArtifact(%q) version = %q, want %q", platform, artifact.Version, test.wantVersion) } } } @@ -402,16 +411,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) diff --git a/internal/crosscompile/fetch.go b/internal/crosscompile/fetch.go index 4277527a3c..7a1d23f8ea 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,19 @@ 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() + hash := sha256.New() + if _, err := io.Copy(hash, file); 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..949c9b2800 100644 --- a/internal/crosscompile/fetch_test.go +++ b/internal/crosscompile/fetch_test.go @@ -22,6 +22,8 @@ import ( "sync/atomic" "testing" "time" + + "github.com/xgo-dev/llgo/internal/llvmpayload" ) // Helper function to create a test tar.gz archive @@ -828,7 +830,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 +920,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 +930,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) } @@ -982,15 +988,18 @@ 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 +1014,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 +1118,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/llvmpayload/cmd/llvmpayload/main.go b/internal/llvmpayload/cmd/llvmpayload/main.go new file mode 100644 index 0000000000..23445d11b6 --- /dev/null +++ b/internal/llvmpayload/cmd/llvmpayload/main.go @@ -0,0 +1,55 @@ +// Command llvmpayload prints the checked-in LLVM payload contract for release +// scripts that cannot import Go constants directly. +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + "github.com/xgo-dev/llgo/internal/llvmpayload" +) + +func main() { + major := flag.Int("major", 0, "LLVM major (defaults to the release payload)") + flag.Parse() + + var ( + manifest llvmpayload.Manifest + err error + ) + if *major == 0 { + manifest, err = llvmpayload.Default() + } else { + manifest, err = llvmpayload.ForMajor(*major) + } + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + fmt.Printf("LLGO_LLVM_MAJOR=%s\n", fmt.Sprint(manifest.LLVMMajor())) + fmt.Printf("ESP_CLANG_LLVM_MAJOR=%s\n", fmt.Sprint(manifest.LLVMMajor())) + fmt.Printf("ESP_CLANG_VERSION=%s\n", manifest.Version()) + fmt.Printf("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(os.Stderr, err) + os.Exit(1) + } + fmt.Printf("ESP_CLANG_SHA256_%s=%s\n", strings.ToUpper(host.name), artifact.SHA256) + } +} diff --git a/internal/llvmpayload/payload.go b/internal/llvmpayload/payload.go new file mode 100644 index 0000000000..750314b46d --- /dev/null +++ b/internal/llvmpayload/payload.go @@ -0,0 +1,121 @@ +// Package llvmpayload defines the revision-locked LLVM toolchains 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" + +// DefaultMajor is the LLVM payload bundled into LLGo release archives. +const DefaultMajor = 19 + +var llvmMajorPattern = regexp.MustCompile(`(?:^|[^0-9])([0-9]+)\.[0-9]+`) + +type manifest struct { + llvmMajor int + version string + sha256 map[string]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{ + 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", + }, + }, +} + +// 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) BaseURL() string { + return releaseBaseURL + "/" + m.payload.version +} + +func (m Manifest) Platforms() []string { + platforms := make([]string, 0, len(m.payload.sha256)) + for platform := range m.payload.sha256 { + platforms = append(platforms, platform) + } + sort.Strings(platforms) + return platforms +} + +func (m Manifest) Artifact(platform string) (Artifact, error) { + 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 + default: + return "", false + } +} diff --git a/internal/llvmpayload/payload_test.go b/internal/llvmpayload/payload_test.go new file mode 100644 index 0000000000..9eb588545a --- /dev/null +++ b/internal/llvmpayload/payload_test.go @@ -0,0 +1,71 @@ +package llvmpayload + +import ( + "encoding/hex" + "strings" + "testing" +) + +func TestLLVM19Manifest(t *testing.T) { + manifest, err := ForLLVMVersion("LLVM 19.1.7") + if err != nil { + t.Fatal(err) + } + if manifest.LLVMMajor() != 19 || manifest.Version() != "19.1.2_20250905-3" { + t.Fatalf("manifest identity = LLVM %d %s", manifest.LLVMMajor(), manifest.Version()) + } + platforms := manifest.Platforms() + if len(platforms) != 4 { + t.Fatalf("platform count = %d, want 4: %v", len(platforms), platforms) + } + for _, platform := range platforms { + artifact, err := manifest.Artifact(platform) + if err != nil { + t.Fatal(err) + } + if !strings.HasSuffix(artifact.URL, "clang-esp-"+manifest.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 TestPayloadErrors(t *testing.T) { + if _, err := ForLLVMVersion("development"); err == nil { + t.Fatal("invalid LLVM version accepted") + } + if _, err := ForMajor(20); err == nil { + t.Fatal("unpublished LLVM major accepted") + } + manifest, err := ForMajor(19) + 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", ok: false}, + } + 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) + } + } +} From 360a5dc00f82f49dcaf4c348d917db48bd24fc74 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Sat, 15 Aug 2026 16:23:20 +0800 Subject: [PATCH 03/28] build: isolate cross libraries by LLVM toolchain --- internal/crosscompile/crosscompile.go | 36 ++++++++-- internal/crosscompile/crosscompile_test.go | 26 +++++++ internal/crosscompile/libc.go | 82 +++++++++++++++++----- internal/crosscompile/libc_test.go | 61 +++++++++++++--- targets/esp32.json | 2 +- targets/esp32c3-basic.json | 2 +- targets/esp8266.json | 2 +- targets/fe310.json | 2 +- targets/k210.json | 2 +- targets/riscv-qemu.json | 2 +- 10 files changed, 183 insertions(+), 34 deletions(-) diff --git a/internal/crosscompile/crosscompile.go b/internal/crosscompile/crosscompile.go index 6c0e9cfc47..08db91e015 100644 --- a/internal/crosscompile/crosscompile.go +++ b/internal/crosscompile/crosscompile.go @@ -53,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 @@ -319,12 +321,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 { @@ -735,6 +753,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() @@ -894,9 +914,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 @@ -904,7 +932,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 } @@ -930,7 +958,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 e132dd1281..07155cd962 100644 --- a/internal/crosscompile/crosscompile_test.go +++ b/internal/crosscompile/crosscompile_test.go @@ -297,6 +297,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 != "" { @@ -854,6 +857,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/libc.go b/internal/crosscompile/libc.go index 994ad3ad63..632fbb416c 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,111 @@ 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) + 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..d4cab934e6 100644 --- a/internal/crosscompile/libc_test.go +++ b/internal/crosscompile/libc_test.go @@ -13,6 +13,39 @@ 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) + } +} + func TestGetLibcCompileConfigByName(t *testing.T) { baseDir := filepath.FromSlash("/test/base") target := "armv7" @@ -20,21 +53,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 +77,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 +103,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 +125,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,6 +149,10 @@ func TestGetRTCompileConfigByName(t *testing.T) { } group := cfg.Groups[0] + expectedDir := compiledLibraryDir(baseDir, rtlib.GetCompilerRTConfig(), testCompilerKey) + if outputDir != expectedDir { + t.Fatalf("output dir = %q, want %q", outputDir, expectedDir) + } expectedFile := filepath.Join(baseDir, rtlib.GetCompilerRTConfig().String(), "lib", "builtins", "absvdi2.c") if !slices.Contains(group.Files, expectedFile) { t.Errorf("Expected files [%s], got: %v", expectedFile, group.Files) 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" From 1c89eb764c54e0300368949eda18df4df7c49bc3 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Sun, 16 Aug 2026 23:38:03 +0800 Subject: [PATCH 04/28] build: register the LLVM 21 payload release --- internal/llvmpayload/payload.go | 10 ++++++++++ internal/llvmpayload/payload_test.go | 15 ++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/internal/llvmpayload/payload.go b/internal/llvmpayload/payload.go index 750314b46d..ec30446e8a 100644 --- a/internal/llvmpayload/payload.go +++ b/internal/llvmpayload/payload.go @@ -41,6 +41,16 @@ var manifests = map[int]manifest{ "x86_64-linux-gnu": "e2e0c48cd76e45ceba910917a2a97988dc80e3bb6040ea262bfe9293d5d9ac57", }, }, + 21: { + llvmMajor: 21, + version: "21.1.3_20260816", + sha256: map[string]string{ + "aarch64-apple-darwin": "a8c46104501c38a8a7359ec24bc4e9d646f9fec2bdb2b122cbbee78e060400d1", + "aarch64-linux-gnu": "77f49d832e5f309ecd6baaf169c62e3b064b27f9bee5aedddb6e66c981d56f44", + "x86_64-apple-darwin": "21159a4edb8948d83e1f73dfef394bca6941d0c4035da02f8c90ac59799893fa", + "x86_64-linux-gnu": "582b787057c9e36e7d4db20aaed7bbba74c7ad0481489f034f09476703befbd5", + }, + }, } // ForLLVMVersion returns the payload compatible with an in-process LLVM diff --git a/internal/llvmpayload/payload_test.go b/internal/llvmpayload/payload_test.go index 9eb588545a..ba7aef8333 100644 --- a/internal/llvmpayload/payload_test.go +++ b/internal/llvmpayload/payload_test.go @@ -6,12 +6,13 @@ import ( "testing" ) -func TestLLVM19Manifest(t *testing.T) { - manifest, err := ForLLVMVersion("LLVM 19.1.7") +func testManifest(t *testing.T, llvmVersion, payloadVersion string, wantMajor int) { + t.Helper() + manifest, err := ForLLVMVersion(llvmVersion) if err != nil { t.Fatal(err) } - if manifest.LLVMMajor() != 19 || manifest.Version() != "19.1.2_20250905-3" { + if manifest.LLVMMajor() != wantMajor || manifest.Version() != payloadVersion { t.Fatalf("manifest identity = LLVM %d %s", manifest.LLVMMajor(), manifest.Version()) } platforms := manifest.Platforms() @@ -33,6 +34,14 @@ func TestLLVM19Manifest(t *testing.T) { } } +func TestLLVM19Manifest(t *testing.T) { + testManifest(t, "LLVM 19.1.7", "19.1.2_20250905-3", 19) +} + +func TestLLVM21Manifest(t *testing.T) { + testManifest(t, "LLVM 21.1.8", "21.1.3_20260816", 21) +} + func TestPayloadErrors(t *testing.T) { if _, err := ForLLVMVersion("development"); err == nil { t.Fatal("invalid LLVM version accepted") From bebdab3d96f559472f4af930af22f27001dd15d0 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Thu, 27 Aug 2026 21:29:06 +0800 Subject: [PATCH 05/28] build: align LLVM 21 runtime dependencies --- .github/actions/setup-deps/action.yml | 66 +++++++++++++++---- README.md | 6 +- THIRD_PARTY_NOTICES.md | 13 ++-- .../crosscompile/compile/rtlib/compiler_rt.go | 21 ++++-- .../crosscompile/compile/rtlib/rt_test.go | 53 +++++++-------- internal/crosscompile/libc.go | 5 +- internal/crosscompile/libc_test.go | 8 ++- internal/llvmpayload/payload.go | 19 ++++-- internal/llvmpayload/payload_test.go | 9 ++- ltoplugin/README.md | 11 ++-- 10 files changed, 141 insertions(+), 70 deletions(-) diff --git a/.github/actions/setup-deps/action.yml b/.github/actions/setup-deps/action.yml index 815ee267c9..add2d099cc 100644 --- a/.github/actions/setup-deps/action.yml +++ b/.github/actions/setup-deps/action.yml @@ -512,6 +512,16 @@ runs: 19) llvm_version=19.1.7 package_version=19.1.7-1 + runtime_package_version=19.1.7-1 + libxml2_package=libxml2-2.12.9-2 + llvm_tools_package= + ;; + 21) + llvm_version=21.1.8 + package_version=21.1.8-4 + runtime_package_version=21.1.8-1 + libxml2_package= + llvm_tools_package=llvm-tools-21.1.8-4 ;; *) echo "unsupported Windows LLVM version: $llvm_major" >&2 @@ -540,20 +550,31 @@ 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" + "libc++-$runtime_package_version" + "libunwind-$runtime_package_version" ) + # LLVM 19 imports libxml2-2.dll. Current MSYS2 libxml2 releases + # provide libxml2-16.dll after an ABI version bump. LLVM 21 uses the + # current ABI and lets pacman resolve it normally. + if [[ -n "$libxml2_package" ]]; then + packages+=("$libxml2_package") + fi + if [[ -n "$llvm_tools_package" ]]; then + packages+=("$llvm_tools_package") + fi 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. + # 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. + # Archived LLVM packages retain the compiler-runtime package identity + # from their publication date. libc++ already supplies that runtime, + # so satisfy only the renamed metadata while pacman resolves current + # unversioned dependencies. assumed_cc_runtime="$prefix-cc-libs=$llvm_version" pacman --noconfirm -U \ --assume-installed "$assumed_cc_runtime" \ @@ -572,18 +593,37 @@ 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; 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 + 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 + if [[ -n "$llvm_tools_package" ]]; then + installed="$(pacman -Q "$prefix-llvm-tools" | awk '{print $2}')" + if [[ "$installed" != "$package_version" ]]; then + echo "expected llvm-tools $package_version, got $installed" >&2 + exit 1 + fi fi + if [[ -n "$libxml2_package" ]]; then + 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 + fi + 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. diff --git a/README.md b/README.md index feb7f83d95..2313aebb30 100644 --- a/README.md +++ b/README.md @@ -306,9 +306,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 19](https://llvm.org) (default) or LLVM 21 (`GOFLAGS=-tags=llvm21`) +- [Clang 19](https://clang.llvm.org) or Clang 21, matching LLVM +- [LLD 19](https://lld.llvm.org) or LLD 21, matching LLVM - [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/) diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 95198b491b..677fd6386d 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -74,10 +74,13 @@ 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 an Espressif-maintained ESP LLVM/Clang toolchain matching +the LLVM major linked into LLGo: + +- LLVM 19: [`19.1.2_20250905-3`](https://github.com/goplus/espressif-llvm-project-prebuilt/releases/tag/19.1.2_20250905-3) +- LLVM 21: [`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 19 payload and include it under `crosscompile/clang`, because the shipped `llgo` executable dynamically links its LLVM library. @@ -109,7 +112,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 19](https://github.com/goplus/compiler-rt/tree/xtensa_release_19.1.2) or [LLVM 21](https://github.com/goplus/compiler-rt/tree/xtensa_release_21.1.3_20260408), selected with the linked LLVM major | 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/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..bc132a4fe3 100644 --- a/internal/crosscompile/compile/rtlib/rt_test.go +++ b/internal/crosscompile/compile/rtlib/rt_test.go @@ -7,33 +7,34 @@ import ( ) 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) - } - - expectedVersion := "xtensa_release_19.1.2" - if config.Version != expectedVersion { - t.Errorf("Expected Version '%s', got '%s'", expectedVersion, config.Version) - } - - 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) - } - - expectedArchiveSrcDir := "compiler-rt-xtensa_release_19.1.2" - if config.ResourceSubDir != expectedArchiveSrcDir { - t.Errorf("Expected ResourceSubDir '%s', got '%s'", expectedArchiveSrcDir, config.ResourceSubDir) + for _, test := range []struct { + llvmVersion string + want string + }{ + {llvmVersion: "19.1.7", want: "xtensa_release_19.1.2"}, + {llvmVersion: "21.1.8", want: "xtensa_release_21.1.3_20260408"}, + } { + config, err := compilerRTConfigForLLVMVersion(test.llvmVersion) + if err != nil { + t.Fatal(err) + } + if config.Name != "compiler-rt" { + t.Errorf("LLVM %s name = %q", test.llvmVersion, config.Name) + } + if config.Version != test.want { + t.Errorf("LLVM %s version = %q, want %q", test.llvmVersion, config.Version, test.want) + } + wantURL := "https://github.com/goplus/compiler-rt/archive/refs/tags/" + test.want + ".tar.gz" + if config.Url != wantURL { + t.Errorf("LLVM %s URL = %q, want %q", test.llvmVersion, config.Url, wantURL) + } + wantString := "compiler-rt-" + test.want + if config.ResourceSubDir != wantString || config.String() != wantString { + t.Errorf("LLVM %s archive identity = %q / %q, want %q", test.llvmVersion, config.ResourceSubDir, config.String(), wantString) + } } - - // 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()) + if _, err := compilerRTConfigForLLVMVersion("development"); err == nil { + t.Fatal("invalid LLVM version accepted") } } diff --git a/internal/crosscompile/libc.go b/internal/crosscompile/libc.go index 632fbb416c..aa26ed52b2 100644 --- a/internal/crosscompile/libc.go +++ b/internal/crosscompile/libc.go @@ -108,7 +108,10 @@ func getRTCompileConfigByName(baseDir, rtName, target, compilerKey string) (outp switch rtName { case "compiler-rt": - config = rtlib.GetCompilerRTConfig() + config, err = rtlib.GetCompilerRTConfig() + if err != nil { + return + } sourceDir = filepath.Join(baseDir, config.String()) compileConfig = rtlib.GetCompilerRTCompileConfig(sourceDir, target) default: diff --git a/internal/crosscompile/libc_test.go b/internal/crosscompile/libc_test.go index d4cab934e6..73ff983653 100644 --- a/internal/crosscompile/libc_test.go +++ b/internal/crosscompile/libc_test.go @@ -149,11 +149,15 @@ func TestGetRTCompileConfigByName(t *testing.T) { } group := cfg.Groups[0] - expectedDir := compiledLibraryDir(baseDir, rtlib.GetCompilerRTConfig(), testCompilerKey) + 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, rtlib.GetCompilerRTConfig().String(), "lib", "builtins", "absvdi2.c") + 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/llvmpayload/payload.go b/internal/llvmpayload/payload.go index ec30446e8a..3e451dd6a3 100644 --- a/internal/llvmpayload/payload.go +++ b/internal/llvmpayload/payload.go @@ -17,9 +17,10 @@ const DefaultMajor = 19 var llvmMajorPattern = regexp.MustCompile(`(?:^|[^0-9])([0-9]+)\.[0-9]+`) type manifest struct { - llvmMajor int - version string - sha256 map[string]string + llvmMajor int + version string + compilerRTVersion string + sha256 map[string]string } // Artifact identifies one host-specific LLVM payload archive. @@ -32,8 +33,9 @@ type Artifact struct { var manifests = map[int]manifest{ 19: { - llvmMajor: 19, - version: "19.1.2_20250905-3", + llvmMajor: 19, + version: "19.1.2_20250905-3", + compilerRTVersion: "xtensa_release_19.1.2", sha256: map[string]string{ "aarch64-apple-darwin": "4f15d18c93eabdace3eab901582e528ac334d328fb8f19f153ee55b2208d101b", "aarch64-linux-gnu": "b2d8e77bbf3394c6a1f0d66e59385d78d2b49b97ebe782e612cba7f93dcb2337", @@ -42,8 +44,9 @@ var manifests = map[int]manifest{ }, }, 21: { - llvmMajor: 21, - version: "21.1.3_20260816", + 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", @@ -87,6 +90,8 @@ 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 } diff --git a/internal/llvmpayload/payload_test.go b/internal/llvmpayload/payload_test.go index ba7aef8333..acdc308b63 100644 --- a/internal/llvmpayload/payload_test.go +++ b/internal/llvmpayload/payload_test.go @@ -6,7 +6,7 @@ import ( "testing" ) -func testManifest(t *testing.T, llvmVersion, payloadVersion string, wantMajor int) { +func testManifest(t *testing.T, llvmVersion, payloadVersion, compilerRTVersion string, wantMajor int) { t.Helper() manifest, err := ForLLVMVersion(llvmVersion) if err != nil { @@ -15,6 +15,9 @@ func testManifest(t *testing.T, llvmVersion, payloadVersion string, wantMajor in 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) != 4 { t.Fatalf("platform count = %d, want 4: %v", len(platforms), platforms) @@ -35,11 +38,11 @@ func testManifest(t *testing.T, llvmVersion, payloadVersion string, wantMajor in } func TestLLVM19Manifest(t *testing.T) { - testManifest(t, "LLVM 19.1.7", "19.1.2_20250905-3", 19) + testManifest(t, "LLVM 19.1.7", "19.1.2_20250905-3", "xtensa_release_19.1.2", 19) } func TestLLVM21Manifest(t *testing.T) { - testManifest(t, "LLVM 21.1.8", "21.1.3_20260816", 21) + testManifest(t, "LLVM 21.1.8", "21.1.3_20260816", "xtensa_release_21.1.3_20260408", 21) } func TestPayloadErrors(t *testing.T) { diff --git a/ltoplugin/README.md b/ltoplugin/README.md index 399a7511dc..e7171ef969 100644 --- a/ltoplugin/README.md +++ b/ltoplugin/README.md @@ -3,11 +3,12 @@ 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 19 or LLVM 21 toolchain selected for LLGo. For an +LLVM 21 build: ```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 +25,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 -`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 +LLGo forwards the plugin path through lld's `--load-pass-plugin` option. The +supported LLVM 19 and LLVM 21 Mach-O linkers do not expose an equivalent new +pass manager LTO plugin loading option, so LLGo rejects `-lto-pass-plugin` for Darwin targets until the linker side grows that support. From 6680294d75272c992407e084e952744c19763f87 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Sat, 15 Aug 2026 16:26:26 +0800 Subject: [PATCH 06/28] build: make LLVM 21 the primary toolchain --- .github/actions/setup-deps/action.yml | 2 +- .github/workflows/benchmark.yml | 5 ++- .github/workflows/build-cache.yml | 8 ++-- .github/workflows/doc.yml | 7 +++- .github/workflows/go.yml | 14 +++---- .github/workflows/goroot.yml | 2 +- .github/workflows/llgo.yml | 42 +++++++++---------- .github/workflows/model-demo.yml | 2 +- .github/workflows/targets.yml | 2 +- README.md | 18 ++++---- doc/_readme/scripts/install_macos.sh | 4 +- doc/_readme/scripts/install_ubuntu.sh | 4 +- ltoplugin/README.md | 9 ++-- .../llvm/llvm_config_darwin_amd64_llvm19.go | 2 +- .../llvm/llvm_config_darwin_amd64_llvm21.go | 2 +- xtool/env/llvm/llvm_config_darwin_llvm19.go | 2 +- xtool/env/llvm/llvm_config_darwin_llvm21.go | 2 +- xtool/env/llvm/llvm_config_linux_llvm19.go | 2 +- xtool/env/llvm/llvm_config_linux_llvm21.go | 2 +- 19 files changed, 69 insertions(+), 62 deletions(-) diff --git a/.github/actions/setup-deps/action.yml b/.github/actions/setup-deps/action.yml index add2d099cc..ba8c0c8469 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 diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 3b6f26bc5f..218b3b4989 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -73,6 +73,9 @@ jobs: env: GOMAXPROCS: "2" LLGO_ROOT: ${{ github.workspace }} + # The PR base may still default to LLVM 19 while this upgrade is under + # review; select LLVM 21 explicitly for an apples-to-apples comparison. + GOFLAGS: -tags=llvm21 steps: - uses: actions/checkout@v7 with: @@ -111,7 +114,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' }} 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..adbb00b84f 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -11,6 +11,11 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true +# Remote-install checks clone the current main branch, which may still default +# to LLVM 19 while this upgrade PR is under review. +env: + GOFLAGS: -tags=llvm21 + jobs: remote_install: timeout-minutes: 30 @@ -153,7 +158,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/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..39e26417b3 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 @@ -374,14 +374,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 +389,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 +397,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 +413,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 +422,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 +431,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 +440,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 +449,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 +552,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 +602,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 +633,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/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/README.md b/README.md index 2313aebb30..900a964b6a 100644 --- a/README.md +++ b/README.md @@ -306,9 +306,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) (default) or LLVM 21 (`GOFLAGS=-tags=llvm21`) -- [Clang 19](https://clang.llvm.org) or Clang 21, matching LLVM -- [LLD 19](https://lld.llvm.org) or LLD 21, matching LLVM +- [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 +326,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 +340,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 +352,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/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/ltoplugin/README.md b/ltoplugin/README.md index e7171ef969..a1b4eaa4fa 100644 --- a/ltoplugin/README.md +++ b/ltoplugin/README.md @@ -3,8 +3,7 @@ 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 or LLVM 21 toolchain selected for LLGo. For an -LLVM 21 build: +Build with the same LLVM 21 toolchain used by LLGo: ```sh cmake -S ltoplugin -B ltoplugin/build \ @@ -25,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. The -supported LLVM 19 and LLVM 21 Mach-O linkers do not expose an equivalent new -pass manager LTO plugin loading option, so LLGo rejects `-lto-pass-plugin` for +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/xtool/env/llvm/llvm_config_darwin_amd64_llvm19.go b/xtool/env/llvm/llvm_config_darwin_amd64_llvm19.go index e330bbd2c0..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 && !llvm20 && !llvm21 && !llvm22 +//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 index c815343500..3bd51bd7db 100644 --- a/xtool/env/llvm/llvm_config_darwin_amd64_llvm21.go +++ b/xtool/env/llvm/llvm_config_darwin_amd64_llvm21.go @@ -1,4 +1,4 @@ -//go:build !byollvm && darwin && amd64 && llvm21 +//go:build !byollvm && darwin && amd64 && !llvm14 && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19 && !llvm20 && !llvm22 /* * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. diff --git a/xtool/env/llvm/llvm_config_darwin_llvm19.go b/xtool/env/llvm/llvm_config_darwin_llvm19.go index 829203508b..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 && !llvm20 && !llvm21 && !llvm22 +//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 index 757611e6b2..ac3c5e5ef7 100644 --- a/xtool/env/llvm/llvm_config_darwin_llvm21.go +++ b/xtool/env/llvm/llvm_config_darwin_llvm21.go @@ -1,4 +1,4 @@ -//go:build !byollvm && darwin && !amd64 && llvm21 +//go:build !byollvm && darwin && !amd64 && !llvm14 && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19 && !llvm20 && !llvm22 /* * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. diff --git a/xtool/env/llvm/llvm_config_linux_llvm19.go b/xtool/env/llvm/llvm_config_linux_llvm19.go index e9eabea377..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 && !llvm20 && !llvm21 && !llvm22 +//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 index d10082059f..15a9cdfb2b 100644 --- a/xtool/env/llvm/llvm_config_linux_llvm21.go +++ b/xtool/env/llvm/llvm_config_linux_llvm21.go @@ -1,4 +1,4 @@ -//go:build !byollvm && linux && llvm21 +//go:build !byollvm && linux && !llvm14 && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19 && !llvm20 && !llvm22 /* * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. From c955de53f9c5c6a1824ba9a198d402e19c0656fc Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Sun, 16 Aug 2026 23:38:59 +0800 Subject: [PATCH 07/28] build: make the LLVM 21 payload the default --- internal/llvmpayload/payload.go | 2 +- internal/llvmpayload/payload_test.go | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/internal/llvmpayload/payload.go b/internal/llvmpayload/payload.go index 3e451dd6a3..d9348cdedc 100644 --- a/internal/llvmpayload/payload.go +++ b/internal/llvmpayload/payload.go @@ -12,7 +12,7 @@ import ( const releaseBaseURL = "https://github.com/goplus/espressif-llvm-project-prebuilt/releases/download" // DefaultMajor is the LLVM payload bundled into LLGo release archives. -const DefaultMajor = 19 +const DefaultMajor = 21 var llvmMajorPattern = regexp.MustCompile(`(?:^|[^0-9])([0-9]+)\.[0-9]+`) diff --git a/internal/llvmpayload/payload_test.go b/internal/llvmpayload/payload_test.go index acdc308b63..7a92bbb0ae 100644 --- a/internal/llvmpayload/payload_test.go +++ b/internal/llvmpayload/payload_test.go @@ -43,6 +43,14 @@ func TestLLVM19Manifest(t *testing.T) { 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()) + } } func TestPayloadErrors(t *testing.T) { From c5f53f1067700b9f45229bd0991bb3f0dc436ba2 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Thu, 27 Aug 2026 21:46:33 +0800 Subject: [PATCH 08/28] build: complete the LLVM 21 default switch --- .github/actions/setup-deps/action.yml | 45 ++---------------- .github/workflows/benchmark.yml | 3 -- .github/workflows/doc.yml | 5 -- THIRD_PARTY_NOTICES.md | 11 ++--- dev/Dockerfile.dev | 2 +- .../crosscompile/compile/rtlib/rt_test.go | 47 +++++++++---------- internal/llvmpayload/payload.go | 13 +---- internal/llvmpayload/payload_test.go | 8 +--- ltoplugin/CMakeLists.txt | 4 +- 9 files changed, 35 insertions(+), 103 deletions(-) diff --git a/.github/actions/setup-deps/action.yml b/.github/actions/setup-deps/action.yml index ba8c0c8469..2290d26cca 100644 --- a/.github/actions/setup-deps/action.yml +++ b/.github/actions/setup-deps/action.yml @@ -509,19 +509,10 @@ runs: llvm_major="${{ inputs.llvm-version }}" case "$llvm_major" in - 19) - llvm_version=19.1.7 - package_version=19.1.7-1 - runtime_package_version=19.1.7-1 - libxml2_package=libxml2-2.12.9-2 - llvm_tools_package= - ;; 21) llvm_version=21.1.8 package_version=21.1.8-4 runtime_package_version=21.1.8-1 - libxml2_package= - llvm_tools_package=llvm-tools-21.1.8-4 ;; *) echo "unsupported Windows LLVM version: $llvm_major" >&2 @@ -550,18 +541,10 @@ runs: "llvm-$package_version" "llvm-libs-$package_version" "lld-$package_version" + "llvm-tools-$package_version" "libc++-$runtime_package_version" "libunwind-$runtime_package_version" ) - # LLVM 19 imports libxml2-2.dll. Current MSYS2 libxml2 releases - # provide libxml2-16.dll after an ABI version bump. LLVM 21 uses the - # current ABI and lets pacman resolve it normally. - if [[ -n "$libxml2_package" ]]; then - packages+=("$libxml2_package") - fi - if [[ -n "$llvm_tools_package" ]]; then - packages+=("$llvm_tools_package") - fi urls=() for package in "${packages[@]}"; do urls+=("$repo/$prefix-$package-any.pkg.tar.zst") @@ -571,16 +554,8 @@ runs: # 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. - # Archived LLVM packages retain the compiler-runtime package identity - # from their publication date. libc++ already supplies that runtime, - # so satisfy only the renamed metadata while pacman resolves current - # unversioned dependencies. - assumed_cc_runtime="$prefix-cc-libs=$llvm_version" - pacman --noconfirm -U \ - --assume-installed "$assumed_cc_runtime" \ - "${urls[@]}" + pacman --noconfirm -U "${urls[@]}" pacman --noconfirm -S --needed \ - --assume-installed "$assumed_cc_runtime" \ "$prefix-pkgconf" \ "$prefix-sqlite3" \ "$prefix-libuv" \ @@ -593,7 +568,7 @@ runs: "$prefix-make" \ make - for package in clang clang-libs compiler-rt llvm llvm-libs lld; 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 @@ -607,20 +582,6 @@ runs: exit 1 fi done - if [[ -n "$llvm_tools_package" ]]; then - installed="$(pacman -Q "$prefix-llvm-tools" | awk '{print $2}')" - if [[ "$installed" != "$package_version" ]]; then - echo "expected llvm-tools $package_version, got $installed" >&2 - exit 1 - fi - fi - if [[ -n "$libxml2_package" ]]; then - 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 - fi for package in libxml2 libuv gc libatomic_ops cjson; do pacman -Q "$prefix-$package" done diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 218b3b4989..037e7b6b97 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -73,9 +73,6 @@ jobs: env: GOMAXPROCS: "2" LLGO_ROOT: ${{ github.workspace }} - # The PR base may still default to LLVM 19 while this upgrade is under - # review; select LLVM 21 explicitly for an apples-to-apples comparison. - GOFLAGS: -tags=llvm21 steps: - uses: actions/checkout@v7 with: diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index adbb00b84f..a4cd487b62 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -11,11 +11,6 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true -# Remote-install checks clone the current main branch, which may still default -# to LLVM 19 while this upgrade PR is under review. -env: - GOFLAGS: -tags=llvm21 - jobs: remote_install: timeout-minutes: 30 diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 677fd6386d..b5bdba2857 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -74,13 +74,10 @@ development- and test-only modules are not included. ## LLVM/Clang -LLGo can download an Espressif-maintained ESP LLVM/Clang toolchain matching -the LLVM major linked into LLGo: +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). -- LLVM 19: [`19.1.2_20250905-3`](https://github.com/goplus/espressif-llvm-project-prebuilt/releases/tag/19.1.2_20250905-3) -- LLVM 21: [`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 19 payload and include it under +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. @@ -112,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` LLVM 19](https://github.com/goplus/compiler-rt/tree/xtensa_release_19.1.2) or [LLVM 21](https://github.com/goplus/compiler-rt/tree/xtensa_release_21.1.3_20260408), selected with the linked LLVM major | 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/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/internal/crosscompile/compile/rtlib/rt_test.go b/internal/crosscompile/compile/rtlib/rt_test.go index bc132a4fe3..246da6ab7c 100644 --- a/internal/crosscompile/compile/rtlib/rt_test.go +++ b/internal/crosscompile/compile/rtlib/rt_test.go @@ -7,35 +7,32 @@ import ( ) func TestGetCompilerRTConfig_LibConfig(t *testing.T) { - for _, test := range []struct { - llvmVersion string - want string - }{ - {llvmVersion: "19.1.7", want: "xtensa_release_19.1.2"}, - {llvmVersion: "21.1.8", want: "xtensa_release_21.1.3_20260408"}, - } { - config, err := compilerRTConfigForLLVMVersion(test.llvmVersion) - if err != nil { - t.Fatal(err) - } - if config.Name != "compiler-rt" { - t.Errorf("LLVM %s name = %q", test.llvmVersion, config.Name) - } - if config.Version != test.want { - t.Errorf("LLVM %s version = %q, want %q", test.llvmVersion, config.Version, test.want) - } - wantURL := "https://github.com/goplus/compiler-rt/archive/refs/tags/" + test.want + ".tar.gz" - if config.Url != wantURL { - t.Errorf("LLVM %s URL = %q, want %q", test.llvmVersion, config.Url, wantURL) - } - wantString := "compiler-rt-" + test.want - if config.ResourceSubDir != wantString || config.String() != wantString { - t.Errorf("LLVM %s archive identity = %q / %q, want %q", test.llvmVersion, config.ResourceSubDir, config.String(), wantString) - } + const llvmVersion = "21.1.8" + const want = "xtensa_release_21.1.3_20260408" + config, err := compilerRTConfigForLLVMVersion(llvmVersion) + if err != nil { + t.Fatal(err) + } + if config.Name != "compiler-rt" { + t.Errorf("LLVM %s name = %q", llvmVersion, config.Name) + } + if config.Version != want { + t.Errorf("LLVM %s version = %q, want %q", llvmVersion, config.Version, want) + } + 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") + } } func TestPlatformSpecifiedFiles(t *testing.T) { diff --git a/internal/llvmpayload/payload.go b/internal/llvmpayload/payload.go index d9348cdedc..592b524a8a 100644 --- a/internal/llvmpayload/payload.go +++ b/internal/llvmpayload/payload.go @@ -1,4 +1,4 @@ -// Package llvmpayload defines the revision-locked LLVM toolchains distributed +// Package llvmpayload defines the revision-locked LLVM toolchain distributed // with and downloaded by LLGo. package llvmpayload @@ -32,17 +32,6 @@ type Artifact struct { } var manifests = map[int]manifest{ - 19: { - llvmMajor: 19, - version: "19.1.2_20250905-3", - compilerRTVersion: "xtensa_release_19.1.2", - sha256: map[string]string{ - "aarch64-apple-darwin": "4f15d18c93eabdace3eab901582e528ac334d328fb8f19f153ee55b2208d101b", - "aarch64-linux-gnu": "b2d8e77bbf3394c6a1f0d66e59385d78d2b49b97ebe782e612cba7f93dcb2337", - "x86_64-apple-darwin": "e4f329a911e813ee825984f039578614dc0fe69001c2afe3e61edf27821be3ad", - "x86_64-linux-gnu": "e2e0c48cd76e45ceba910917a2a97988dc80e3bb6040ea262bfe9293d5d9ac57", - }, - }, 21: { llvmMajor: 21, version: "21.1.3_20260816", diff --git a/internal/llvmpayload/payload_test.go b/internal/llvmpayload/payload_test.go index 7a92bbb0ae..ab010304aa 100644 --- a/internal/llvmpayload/payload_test.go +++ b/internal/llvmpayload/payload_test.go @@ -37,10 +37,6 @@ func testManifest(t *testing.T, llvmVersion, payloadVersion, compilerRTVersion s } } -func TestLLVM19Manifest(t *testing.T) { - testManifest(t, "LLVM 19.1.7", "19.1.2_20250905-3", "xtensa_release_19.1.2", 19) -} - func TestLLVM21Manifest(t *testing.T) { testManifest(t, "LLVM 21.1.8", "21.1.3_20260816", "xtensa_release_21.1.3_20260408", 21) @@ -57,10 +53,10 @@ func TestPayloadErrors(t *testing.T) { if _, err := ForLLVMVersion("development"); err == nil { t.Fatal("invalid LLVM version accepted") } - if _, err := ForMajor(20); err == nil { + if _, err := ForMajor(19); err == nil { t.Fatal("unpublished LLVM major accepted") } - manifest, err := ForMajor(19) + manifest, err := ForMajor(21) if err != nil { t.Fatal(err) } diff --git a/ltoplugin/CMakeLists.txt b/ltoplugin/CMakeLists.txt index 2237892f4f..89288aa27a 100644 --- a/ltoplugin/CMakeLists.txt +++ b/ltoplugin/CMakeLists.txt @@ -4,8 +4,8 @@ project(LLGOLTOPlugin LANGUAGES C CXX) find_package(LLVM REQUIRED CONFIG) -if(NOT LLVM_VERSION_MAJOR EQUAL 19 AND NOT LLVM_VERSION_MAJOR EQUAL 21) - message(FATAL_ERROR "LLGo LTO plugin requires LLVM 19.x or 21.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}") From fe5ab182434b5c6a99f6c16fc902b59db287b471 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Thu, 27 Aug 2026 22:09:59 +0800 Subject: [PATCH 09/28] build: use released LLVM 21 binding --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 926ad44e14..c2ee8b9302 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 diff --git a/go.sum b/go.sum index 228514c6e8..dd5e29ad3a 100644 --- a/go.sum +++ b/go.sum @@ -24,6 +24,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU 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= go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A= From ae65d6698c33d6c478353fb56b684b6b58f5826e Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Thu, 27 Aug 2026 22:13:12 +0800 Subject: [PATCH 10/28] ci: benchmark the base with LLVM 21 --- .github/workflows/benchmark.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 037e7b6b97..061bda930a 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -201,7 +201,10 @@ jobs: # base contains Windows support. continue-on-error: ${{ startsWith(matrix.id, 'windows-') }} shell: bash + # The PR base still defaults to LLVM 19 until this upgrade lands, but + # the benchmark intentionally compares both revisions with LLVM 21. env: + GOFLAGS: -tags=llvm21 LLGO_BENCHMARK_PREBUILT_LLGO: ${{ matrix.windows_arch && matrix.windows_arch != 'amd64' && '.benchmark/base-llgo.exe' || '' }} run: | benchmark/baseline/run.sh \ From 3ccc26a9ae700f8fa8474b5cb84b40b72d952d8c Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Thu, 27 Aug 2026 22:48:08 +0800 Subject: [PATCH 11/28] internal/dcepass: preserve empty structs on LLVM 21 --- internal/dcepass/dcepass.go | 2 +- internal/dcepass/dcepass_test.go | 14 +++++++++++++- internal/dcepass/testdata/method_slots/expect.ll | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) 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 From 46d70490fbe3ae93b1ddcbe2904c61312bae89fd Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Fri, 28 Aug 2026 00:25:02 +0800 Subject: [PATCH 12/28] build: preserve the GNU Linux target environment --- internal/xtool/llvm/llvm.go | 5 +++++ internal/xtool/llvm/llvm_test.go | 6 +++--- ssa/ssa_test.go | 4 ++-- 3 files changed, 10 insertions(+), 5 deletions(-) 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/ssa/ssa_test.go b/ssa/ssa_test.go index 9a00cec411..6e10d2d585 100644 --- a/ssa/ssa_test.go +++ b/ssa/ssa_test.go @@ -2991,8 +2991,8 @@ func TestTargetMachineAndDataLayout(t *testing.T) { goarch string triple string }{ - {"linux", "amd64", "x86_64-unknown-linux"}, - {"linux", "arm64", "aarch64-unknown-linux"}, + {"linux", "amd64", "x86_64-unknown-linux-gnu"}, + {"linux", "arm64", "aarch64-unknown-linux-gnu"}, {"darwin", "amd64", "x86_64-apple-macosx"}, {"darwin", "arm64", "arm64-apple-macosx"}, } From f5d09cf94061d4f84e984c2da6ee3aaec489a45e Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Fri, 28 Aug 2026 07:46:54 +0800 Subject: [PATCH 13/28] ci: rerun LLVM 21 validation From d381c0f02002aa89db109ebad62279ff1c872728 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Fri, 28 Aug 2026 10:11:21 +0800 Subject: [PATCH 14/28] ci: align Windows LLDB with LLVM 21 --- .github/workflows/llgo.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/llgo.yml b/.github/workflows/llgo.yml index 39e26417b3..ea4f4ddd3e 100644 --- a/.github/workflows/llgo.yml +++ b/.github/workflows/llgo.yml @@ -241,8 +241,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 From 19b9e1df48c6ad848db9d7ad72e2947b11648c5d Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Fri, 28 Aug 2026 17:08:17 +0800 Subject: [PATCH 15/28] fix: use LLVM 21 ESP toolchain on Windows --- internal/build/build_test.go | 25 +++++++++ .../crosscompile/compile/rtlib/rt_test.go | 14 +++++ internal/crosscompile/crosscompile.go | 26 +-------- internal/crosscompile/crosscompile_test.go | 46 ++++++++++++++-- internal/crosscompile/fetch_test.go | 6 ++ internal/crosscompile/libc_test.go | 30 ++++++++++ internal/llvmpayload/cmd/llvmpayload/main.go | 34 ++++++++---- .../llvmpayload/cmd/llvmpayload/main_test.go | 55 +++++++++++++++++++ internal/llvmpayload/payload.go | 39 ++++++++++++- internal/llvmpayload/payload_test.go | 25 +++++++-- 10 files changed, 253 insertions(+), 47 deletions(-) create mode 100644 internal/llvmpayload/cmd/llvmpayload/main_test.go 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/crosscompile/compile/rtlib/rt_test.go b/internal/crosscompile/compile/rtlib/rt_test.go index 246da6ab7c..aea08f9f7c 100644 --- a/internal/crosscompile/compile/rtlib/rt_test.go +++ b/internal/crosscompile/compile/rtlib/rt_test.go @@ -4,6 +4,8 @@ import ( "path/filepath" "strings" "testing" + + gllvm "github.com/xgo-dev/llvm" ) func TestGetCompilerRTConfig_LibConfig(t *testing.T) { @@ -33,6 +35,18 @@ func TestGetCompilerRTConfig_LibConfig(t *testing.T) { if _, err := compilerRTConfigForLLVMVersion("19.1.7"); err == nil { t.Fatal("unsupported LLVM version accepted") } + + 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) + } } func TestPlatformSpecifiedFiles(t *testing.T) { diff --git a/internal/crosscompile/crosscompile.go b/internal/crosscompile/crosscompile.go index 08db91e015..ca35ecb994 100644 --- a/internal/crosscompile/crosscompile.go +++ b/internal/crosscompile/crosscompile.go @@ -187,29 +187,10 @@ var ( wasiMacosSubdir = "wasi-sdk-25.0-x86_64-macos" ) -var ( - 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 = espClangArtifact - -func espClangArtifact(payload llvmpayload.Manifest, platform string) (llvmpayload.Artifact, error) { - if platform == espClangWindowsPlatform { - filename := fmt.Sprintf("clang-esp-%s-%s.tar.xz", espClangWindowsVersion, platform) - return llvmpayload.Artifact{ - Platform: platform, - Version: espClangWindowsVersion, - URL: espClangWindowsBaseUrl + "/" + filename, - }, nil - } - return payload.Artifact(platform) -} +var resolveESPClangArtifact = llvmpayload.Manifest.Artifact func cacheDir() string { return filepath.Join(cacheRoot(), "crosscompile") @@ -306,11 +287,6 @@ func getESPClangRoot(forceEspClang bool) (clangRoot string, err error) { // getESPClangPlatform returns the platform suffix for ESP Clang downloads func getESPClangPlatform(goos, goarch string) string { - if goos == "windows" && (goarch == "amd64" || goarch == "arm64") { - // Espressif publishes an x86-64 Windows host toolchain. Windows on - // ARM64 runs it through the system's x64 emulation layer. - return espClangWindowsPlatform - } platform, _ := llvmpayload.PlatformSuffix(goos, goarch) return platform } diff --git a/internal/crosscompile/crosscompile_test.go b/internal/crosscompile/crosscompile_test.go index 07155cd962..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,10 +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 ( @@ -38,8 +41,8 @@ func TestESPClangHostDownload(t *testing.T) { }{ {"darwin", "arm64", "aarch64-apple-darwin", payload.Version()}, {"linux", "amd64", "x86_64-linux-gnu", payload.Version()}, - {"windows", "amd64", espClangWindowsPlatform, espClangWindowsVersion}, - {"windows", "arm64", espClangWindowsPlatform, espClangWindowsVersion}, + {"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) @@ -47,17 +50,50 @@ func TestESPClangHostDownload(t *testing.T) { t.Errorf("getESPClangPlatform(%q, %q) = %q, want %q", test.goos, test.goarch, platform, test.wantPlatform) continue } - artifact, err := espClangArtifact(payload, platform) + artifact, err := payload.Artifact(platform) if err != nil { - t.Errorf("espClangArtifact(%q) error = %v", platform, err) + t.Errorf("Artifact(%q) error = %v", platform, err) continue } if artifact.Version != test.wantVersion { - t.Errorf("espClangArtifact(%q) version = %q, want %q", platform, 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() { diff --git a/internal/crosscompile/fetch_test.go b/internal/crosscompile/fetch_test.go index 949c9b2800..7c4ef03e50 100644 --- a/internal/crosscompile/fetch_test.go +++ b/internal/crosscompile/fetch_test.go @@ -984,6 +984,12 @@ 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") + } +} + func TestESPClangDownloadLicenseFailure(t *testing.T) { archivePath := createTestTarXz(t, map[string]string{ "esp-clang/bin/clang": "fake esp clang binary", diff --git a/internal/crosscompile/libc_test.go b/internal/crosscompile/libc_test.go index 73ff983653..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" @@ -44,6 +46,34 @@ func TestCompilerVersionCacheKey(t *testing.T) { 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) { diff --git a/internal/llvmpayload/cmd/llvmpayload/main.go b/internal/llvmpayload/cmd/llvmpayload/main.go index 23445d11b6..fe5347c7d9 100644 --- a/internal/llvmpayload/cmd/llvmpayload/main.go +++ b/internal/llvmpayload/cmd/llvmpayload/main.go @@ -5,6 +5,7 @@ package main import ( "flag" "fmt" + "io" "os" "strings" @@ -12,8 +13,18 @@ import ( ) func main() { - major := flag.Int("major", 0, "LLVM major (defaults to the release payload)") - flag.Parse() + 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 @@ -25,14 +36,14 @@ func main() { manifest, err = llvmpayload.ForMajor(*major) } if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) + fmt.Fprintln(stderr, err) + return 1 } - fmt.Printf("LLGO_LLVM_MAJOR=%s\n", fmt.Sprint(manifest.LLVMMajor())) - fmt.Printf("ESP_CLANG_LLVM_MAJOR=%s\n", fmt.Sprint(manifest.LLVMMajor())) - fmt.Printf("ESP_CLANG_VERSION=%s\n", manifest.Version()) - fmt.Printf("ESP_CLANG_BASE_URL=%s\n", manifest.BaseURL()) + 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 }{ @@ -47,9 +58,10 @@ func main() { } artifact, err := manifest.Artifact(platform) if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) + fmt.Fprintln(stderr, err) + return 1 } - fmt.Printf("ESP_CLANG_SHA256_%s=%s\n", strings.ToUpper(host.name), artifact.SHA256) + 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 index 592b524a8a..7b71aac7ba 100644 --- a/internal/llvmpayload/payload.go +++ b/internal/llvmpayload/payload.go @@ -9,7 +9,12 @@ import ( "strconv" ) -const releaseBaseURL = "https://github.com/goplus/espressif-llvm-project-prebuilt/releases/download" +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 @@ -21,6 +26,13 @@ type manifest struct { 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. @@ -42,6 +54,13 @@ var manifests = map[int]manifest{ "x86_64-apple-darwin": "21159a4edb8948d83e1f73dfef394bca6941d0c4035da02f8c90ac59799893fa", "x86_64-linux-gnu": "582b787057c9e36e7d4db20aaed7bbba74c7ad0481489f034f09476703befbd5", }, + artifactOverrides: map[string]artifactOverride{ + espLLVM21WindowsPlatform: { + version: espLLVM21WindowsVersion, + baseURL: espLLVM21WindowsBaseURL, + sha256: "415566ace6f47a9abc302b4ba79776d27668fd3f4e9c0d26861ec4f970323618", + }, + }, }, } @@ -86,15 +105,27 @@ func (m Manifest) BaseURL() string { } func (m Manifest) Platforms() []string { - platforms := make([]string, 0, len(m.payload.sha256)) + 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) @@ -119,6 +150,10 @@ func PlatformSuffix(goos, goarch string) (string, bool) { 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 index ab010304aa..00e9cabf31 100644 --- a/internal/llvmpayload/payload_test.go +++ b/internal/llvmpayload/payload_test.go @@ -19,15 +19,18 @@ func testManifest(t *testing.T, llvmVersion, payloadVersion, compilerRTVersion s t.Fatalf("compiler-rt version = %q, want %q", manifest.CompilerRTVersion(), compilerRTVersion) } platforms := manifest.Platforms() - if len(platforms) != 4 { - t.Fatalf("platform count = %d, want 4: %v", len(platforms), 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 !strings.HasSuffix(artifact.URL, "clang-esp-"+manifest.Version()+"-"+platform+".tar.xz") { + 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) @@ -47,6 +50,19 @@ func TestLLVM21Manifest(t *testing.T) { 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) { @@ -76,7 +92,8 @@ func TestPlatformSuffix(t *testing.T) { {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", 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) From 018650a6050e40bf908ed122458a457ba8085c3d Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Fri, 28 Aug 2026 17:46:15 +0800 Subject: [PATCH 16/28] build: align Windows C++ bindings with LLVM 21 --- go.mod | 3 +++ go.sum | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index c2ee8b9302..5d03c8583b 100644 --- a/go.mod +++ b/go.mod @@ -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-20260829113337-5762a8b8a78f diff --git a/go.sum b/go.sum index dd5e29ad3a..f3d501d702 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= @@ -28,6 +26,8 @@ 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-20260829113337-5762a8b8a78f h1:fY+jtABSQbFzNSu8lreQHkEqUgV3u3MGA/e6AAqKiYQ= +github.com/zhouguangyuan0718/lib v0.0.0-20260829113337-5762a8b8a78f/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= From 951fff39a7eed6b03e853ab40f196818943d2653 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Fri, 28 Aug 2026 18:28:15 +0800 Subject: [PATCH 17/28] test: cover LLVM payload error paths --- internal/crosscompile/fetch.go | 6 +++++- internal/crosscompile/fetch_test.go | 4 ++++ internal/llvmpayload/payload_test.go | 3 +++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/internal/crosscompile/fetch.go b/internal/crosscompile/fetch.go index 7a1d23f8ea..08eb897571 100644 --- a/internal/crosscompile/fetch.go +++ b/internal/crosscompile/fetch.go @@ -259,8 +259,12 @@ func fileSHA256(filename string) (string, error) { return "", err } defer file.Close() + return readerSHA256(file) +} + +func readerSHA256(reader io.Reader) (string, error) { hash := sha256.New() - if _, err := io.Copy(hash, file); err != nil { + if _, err := io.Copy(hash, reader); err != nil { return "", err } return hex.EncodeToString(hash.Sum(nil)), nil diff --git a/internal/crosscompile/fetch_test.go b/internal/crosscompile/fetch_test.go index 7c4ef03e50..6d41b5f306 100644 --- a/internal/crosscompile/fetch_test.go +++ b/internal/crosscompile/fetch_test.go @@ -21,6 +21,7 @@ import ( "sync" "sync/atomic" "testing" + "testing/iotest" "time" "github.com/xgo-dev/llgo/internal/llvmpayload" @@ -988,6 +989,9 @@ 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) { diff --git a/internal/llvmpayload/payload_test.go b/internal/llvmpayload/payload_test.go index 00e9cabf31..88a0924259 100644 --- a/internal/llvmpayload/payload_test.go +++ b/internal/llvmpayload/payload_test.go @@ -69,6 +69,9 @@ 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") } From 7f7b5ad5ef1fbc1543d2c4845c1c5ce0df8f5c00 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 1 Sep 2026 10:29:50 +0800 Subject: [PATCH 18/28] build: align rebased Windows toolchains with LLVM 21 --- .github/actions/setup-demo-deps/action.yml | 6 +-- .github/actions/setup-deps/action.yml | 52 +++++++++--------- .github/actions/setup-llvm-mingw/action.yml | 54 +++++++++---------- .github/actions/test-windows-cross/action.yml | 2 +- go.sum | 2 - 5 files changed, 57 insertions(+), 59 deletions(-) diff --git a/.github/actions/setup-demo-deps/action.yml b/.github/actions/setup-demo-deps/action.yml index 1e040e9120..34ad1f2ac7 100644 --- a/.github/actions/setup-demo-deps/action.yml +++ b/.github/actions/setup-demo-deps/action.yml @@ -18,7 +18,7 @@ runs: 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. + # The x64 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 @@ -48,7 +48,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 +59,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 diff --git a/.github/actions/setup-deps/action.yml b/.github/actions/setup-deps/action.yml index 2290d26cca..ce272ae045 100644 --- a/.github/actions/setup-deps/action.yml +++ b/.github/actions/setup-deps/action.yml @@ -39,27 +39,28 @@ 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 + path: ${{ runner.tool_cache }}\llgo-llvm\clang+llvm-21.1.8-x86_64-pc-windows-msvc + key: llgo-llvm-21.1.8-full-target-windows-x64-749d22f565fcd5718dbed06512572d0e5353b502c03fe1f7f17ee8b8aca21a47 - 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' 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" + $archiveName = "clang+llvm-$env:LLVM_VERSION-x86_64-pc-windows-msvc.tar.xz" $archive = Join-Path $env:RUNNER_TEMP $archiveName - $url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-$env:LLVM_VERSION/$archiveName" + $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" @@ -71,7 +72,7 @@ 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" @@ -102,24 +103,24 @@ runs: 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\clang+llvm-21.1.8-x86_64-pc-windows-msvc + key: llgo-llvm-21.1.8-full-target-windows-x64-749d22f565fcd5718dbed06512572d0e5353b502c03fe1f7f17ee8b8aca21a47 - 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 +147,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 +185,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 +247,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 +272,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 @@ -665,7 +665,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-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/go.sum b/go.sum index f3d501d702..026c81ad97 100644 --- a/go.sum +++ b/go.sum @@ -20,8 +20,6 @@ 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= From c64d124bb5f8819d34f40b376c3b69f416022b78 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 1 Sep 2026 11:03:03 +0800 Subject: [PATCH 19/28] ci: preserve LLVM 21 Windows development payload --- .github/actions/setup-deps/action.yml | 52 +++++++++++++++++---------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/.github/actions/setup-deps/action.yml b/.github/actions/setup-deps/action.yml index ce272ae045..5ae8a80153 100644 --- a/.github/actions/setup-deps/action.yml +++ b/.github/actions/setup-deps/action.yml @@ -39,11 +39,14 @@ runs: id: windows_llvm_cache uses: actions/cache/restore@v4 with: - path: ${{ runner.tool_cache }}\llgo-llvm\clang+llvm-21.1.8-x86_64-pc-windows-msvc - key: llgo-llvm-21.1.8-full-target-windows-x64-749d22f565fcd5718dbed06512572d0e5353b502c03fe1f7f17ee8b8aca21a47 + # 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: # Use the official development archive rather than the smaller Windows @@ -58,12 +61,19 @@ runs: } $archiveName = "clang+llvm-$env:LLVM_VERSION-x86_64-pc-windows-msvc.tar.xz" - $archive = Join-Path $env:RUNNER_TEMP $archiveName - $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" + $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)) { @@ -77,6 +87,12 @@ runs: 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) { @@ -97,14 +113,14 @@ 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\clang+llvm-21.1.8-x86_64-pc-windows-msvc - key: llgo-llvm-21.1.8-full-target-windows-x64-749d22f565fcd5718dbed06512572d0e5353b502c03fe1f7f17ee8b8aca21a47 + 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' @@ -364,7 +380,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 ` @@ -422,11 +438,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)" From 3f62189b1f3527eb396eef1b3b5122e0c2cd03e5 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 1 Sep 2026 11:03:03 +0800 Subject: [PATCH 20/28] docs: update consolidated demo links --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 900a964b6a..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)): From 19c75c02413ee3b6757ebdb663c2e48b2c051a2a Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 1 Sep 2026 11:16:26 +0800 Subject: [PATCH 21/28] ci: build Windows baselines with LLVM 21 --- .github/workflows/benchmark.yml | 8 ++++++-- .github/workflows/test_windows_toolchain_profile.ps1 | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 061bda930a..7c0c2ca660 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -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,8 +206,7 @@ jobs: # base contains Windows support. continue-on-error: ${{ startsWith(matrix.id, 'windows-') }} shell: bash - # The PR base still defaults to LLVM 19 until this upgrade lands, but - # the benchmark intentionally compares both revisions with LLVM 21. + # 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' || '' }} 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 = @() From f32c3b2ea1289932f636c1466b59af362bfb8591 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 1 Sep 2026 11:36:07 +0800 Subject: [PATCH 22/28] ci: keep cross-arch Python setup native --- .github/actions/setup-demo-deps/action.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/actions/setup-demo-deps/action.yml b/.github/actions/setup-demo-deps/action.yml index 34ad1f2ac7..471a58a402 100644 --- a/.github/actions/setup-demo-deps/action.yml +++ b/.github/actions/setup-demo-deps/action.yml @@ -14,17 +14,18 @@ runs: using: "composite" steps: - name: Set up Python 3.12 on Windows - if: runner.os == 'Windows' + if: runner.os == 'Windows' && inputs.windows-arch == 'amd64' id: windows_python uses: actions/setup-python@v6 with: # The x64 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. + # the MinGW demo environment on the normal latest 3.12 patch. Only the + # amd64 debugger needs this host interpreter. 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. python-version: ${{ inputs.windows-abi == 'msvc' && '3.12.7' || '3.12' }} architecture: x64 # setup-python otherwise replaces PKG_CONFIG_PATH, hiding the native @@ -256,7 +257,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") { From b80b88f1656167e507717f98a01f45ede2ca2d4b Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 1 Sep 2026 11:59:05 +0800 Subject: [PATCH 23/28] build: map portable C++ runtime to MSVC --- internal/build/build.go | 12 +++++++++++- internal/build/package_build_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/internal/build/build.go b/internal/build/build.go index 4e6382fdc1..ed25bdc3a7 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -1483,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) @@ -1497,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/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", From 9723928192ed6e624bddd6c0fa675e79095a5e4c Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 1 Sep 2026 12:37:30 +0800 Subject: [PATCH 24/28] ci: keep ESP inspection on host Python --- .github/actions/setup-demo-deps/action.yml | 16 ++++++++-------- .github/workflows/llgo.yml | 17 ++++++++++------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/.github/actions/setup-demo-deps/action.yml b/.github/actions/setup-demo-deps/action.yml index 471a58a402..d9e70a83c5 100644 --- a/.github/actions/setup-demo-deps/action.yml +++ b/.github/actions/setup-demo-deps/action.yml @@ -13,20 +13,20 @@ inputs: runs: using: "composite" steps: - - name: Set up Python 3.12 on Windows - if: runner.os == 'Windows' && inputs.windows-arch == 'amd64' + - 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: # The x64 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. Only the - # amd64 debugger needs this host interpreter. 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. - python-version: ${{ inputs.windows-abi == 'msvc' && '3.12.7' || '3.12' }} + # the MinGW demo environment and 386 host tools on the normal latest + # 3.12 patch. 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: ${{ inputs.windows-abi == 'msvc' && inputs.windows-arch == 'amd64' && '3.12.7' || '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. diff --git a/.github/workflows/llgo.yml b/.github/workflows/llgo.yml index ea4f4ddd3e..71c9a7af34 100644 --- a/.github/workflows/llgo.yml +++ b/.github/workflows/llgo.yml @@ -202,14 +202,17 @@ 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. Windows + # ARM64 only builds the image, so it neither inspects it nor needs + # esptool's native cryptography dependency. The 386 lane uses the + # x64 host Python instead of cross-building that dependency. + if [[ "$RUNNER_OS" != "Windows" || "${{ matrix.windows_arch }}" != "arm64" ]]; then + 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=() From e386431c1fc21c309c4ce2f9e60d7369d2013c1d Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 1 Sep 2026 13:13:23 +0800 Subject: [PATCH 25/28] build: expose selected Windows LLVM metadata alias --- .github/actions/setup-deps/action.yml | 30 ++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/.github/actions/setup-deps/action.yml b/.github/actions/setup-deps/action.yml index 5ae8a80153..b54e20f54b 100644 --- a/.github/actions/setup-deps/action.yml +++ b/.github/actions/setup-deps/action.yml @@ -340,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. @@ -422,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" @@ -602,8 +611,10 @@ runs: 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'/}" @@ -618,7 +629,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' @@ -634,7 +645,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" } @@ -667,7 +683,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" From 2889aba4a18d3e534a7d1b3e2e26aa3d37937801 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 1 Sep 2026 13:15:18 +0800 Subject: [PATCH 26/28] build: refresh staged lib dependency --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5d03c8583b..862cab0696 100644 --- a/go.mod +++ b/go.mod @@ -28,4 +28,4 @@ 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-20260829113337-5762a8b8a78f +replace github.com/goplus/lib => github.com/zhouguangyuan0718/lib v0.0.0-20260901051643-a8f1f8266c99 diff --git a/go.sum b/go.sum index 026c81ad97..cc16505e1d 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,8 @@ 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-20260829113337-5762a8b8a78f h1:fY+jtABSQbFzNSu8lreQHkEqUgV3u3MGA/e6AAqKiYQ= -github.com/zhouguangyuan0718/lib v0.0.0-20260829113337-5762a8b8a78f/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0= +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= From fc2a09f913d33d4680fc761a27be5285466990f1 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 1 Sep 2026 14:40:59 +0800 Subject: [PATCH 27/28] ci: restore Windows ESP and LLDB inspection --- .github/workflows/llgo.yml | 21 ++++++++++++++++----- cmd/llgo/lldbtest/common.sh | 2 +- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/.github/workflows/llgo.yml b/.github/workflows/llgo.yml index 71c9a7af34..a4f1a32bd4 100644 --- a/.github/workflows/llgo.yml +++ b/.github/workflows/llgo.yml @@ -202,11 +202,21 @@ jobs: fi done fi - # esptool inspects target firmware but runs on the CI host. Windows - # ARM64 only builds the image, so it neither inspects it nor needs - # esptool's native cryptography dependency. The 386 lane uses the - # x64 host Python instead of cross-building that dependency. - if [[ "$RUNNER_OS" != "Windows" || "${{ matrix.windows_arch }}" != "arm64" ]]; then + # 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" @@ -285,6 +295,7 @@ jobs: # 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 + $env:PATH = "$env:LLGO_WINDOWS_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 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 From eaf8037ef6bbbd012ea014bb095f84af2b545dd3 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 1 Sep 2026 15:21:53 +0800 Subject: [PATCH 28/28] ci: match Windows LLDB Python ABI --- .github/actions/setup-demo-deps/action.yml | 38 +++++++++++++++++----- .github/actions/setup-deps/action.yml | 2 ++ .github/workflows/llgo.yml | 9 ++--- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/.github/actions/setup-demo-deps/action.yml b/.github/actions/setup-demo-deps/action.yml index d9e70a83c5..1c1b3fded7 100644 --- a/.github/actions/setup-demo-deps/action.yml +++ b/.github/actions/setup-demo-deps/action.yml @@ -18,20 +18,28 @@ runs: id: windows_python uses: actions/setup-python@v6 with: - # The x64 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 and 386 host tools on the normal latest - # 3.12 patch. 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: ${{ inputs.windows-abi == 'msvc' && inputs.windows-arch == 'amd64' && '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 @@ -90,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 diff --git a/.github/actions/setup-deps/action.yml b/.github/actions/setup-deps/action.yml index b54e20f54b..215bbbd5cf 100644 --- a/.github/actions/setup-deps/action.yml +++ b/.github/actions/setup-deps/action.yml @@ -470,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)" diff --git a/.github/workflows/llgo.yml b/.github/workflows/llgo.yml index a4f1a32bd4..a358183c56 100644 --- a/.github/workflows/llgo.yml +++ b/.github/workflows/llgo.yml @@ -292,10 +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 - $env:PATH = "$env:LLGO_WINDOWS_PYTHON_HOME;$env:PATH" + # 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