Skip to content

Add Go 1.25 support #4954

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Aug 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,20 @@ commands:
- /go/pkg/mod

jobs:
test-llvm15-go122:
test-oldest:
# This tests our lowest supported versions of Go and LLVM, to make sure at
# least the smoke tests still pass.
docker:
- image: golang:1.22-bullseye
steps:
- test-linux:
llvm: "15"
resource_class: large
test-llvm20-go124:
test-newest:
# This tests the latest supported LLVM version when linking against system
# libraries.
docker:
- image: golang:1.24-bullseye
- image: golang:1.25rc2-bullseye
steps:
- test-linux:
llvm: "20"
Expand All @@ -110,8 +114,5 @@ jobs:
workflows:
test-all:
jobs:
# This tests our lowest supported versions of Go and LLVM, to make sure at
# least the smoke tests still pass.
- test-llvm15-go122
# This tests LLVM 20 support when linking against system libraries.
- test-llvm20-go124
- test-oldest
- test-newest
4 changes: 2 additions & 2 deletions .github/workflows/build-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version: '1.25.0-rc.2'
cache: true
- name: Restore LLVM source cache
uses: actions/cache/restore@v4
Expand Down Expand Up @@ -134,7 +134,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version: '1.25.0-rc.2'
cache: true
- name: Build TinyGo (LLVM ${{ matrix.version }})
run: go install -tags=llvm${{ matrix.version }}
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
# statically linked binary.
runs-on: ubuntu-latest
container:
image: golang:1.24-alpine
image: golang:1.25rc2-alpine
outputs:
version: ${{ steps.version.outputs.version }}
steps:
Expand Down Expand Up @@ -137,7 +137,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version: '1.25.0-rc.2'
cache: true
- name: Install wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1
Expand Down Expand Up @@ -181,7 +181,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version: '1.25.0-rc.2'
cache: true
- name: Install Node.js
uses: actions/setup-node@v4
Expand Down Expand Up @@ -298,7 +298,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version: '1.25.0-rc.2'
cache: true
- name: Restore LLVM source cache
uses: actions/cache/restore@v4
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version: '1.25.0-rc.2'
cache: true
- name: Restore cached LLVM source
uses: actions/cache/restore@v4
Expand Down Expand Up @@ -147,7 +147,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version: '1.25.0-rc.2'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v4
Expand Down Expand Up @@ -177,7 +177,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version: '1.25.0-rc.2'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v4
Expand Down Expand Up @@ -213,7 +213,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version: '1.25.0-rc.2'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v4
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# tinygo-llvm stage obtains the llvm source for TinyGo
FROM golang:1.24 AS tinygo-llvm
FROM golang:1.25rc2 AS tinygo-llvm

RUN apt-get update && \
apt-get install -y apt-utils make cmake clang-15 ninja-build && \
Expand Down Expand Up @@ -33,7 +33,7 @@ RUN cd /tinygo/ && \

# tinygo-compiler copies the compiler build over to a base Go container (without
# all the build tools etc).
FROM golang:1.24 AS tinygo-compiler
FROM golang:1.25rc2 AS tinygo-compiler

# Copy tinygo build.
COPY --from=tinygo-compiler-build /tinygo/build/release/tinygo /tinygo
Expand Down
22 changes: 13 additions & 9 deletions GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -460,38 +460,42 @@ TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_WINDOWS)
TEST_IOFS := false
endif

TEST_SKIP_FLAG := -skip='TestExtraMethods|TestParseAndBytesRoundTrip/P256/Generic'

# Test known-working standard library packages.
# TODO: parallelize, and only show failing tests (no implied -v flag).
.PHONY: tinygo-test
tinygo-test:
$(TINYGO) test $(TEST_PACKAGES_HOST) $(TEST_PACKAGES_SLOW)
@# TestExtraMethods: used by many crypto packages and uses reflect.Type.Method which is not implemented.
@# TestParseAndBytesRoundTrip/P256/Generic: relies on t.Skip() which is not implemented
$(TINYGO) test $(TEST_SKIP_FLAG) $(TEST_PACKAGES_HOST) $(TEST_PACKAGES_SLOW)
@# io/fs requires os.ReadDir, not yet supported on windows or wasi. It also
@# requires a large stack-size. Hence, io/fs is only run conditionally.
@# For more details, see the comments on issue #3143.
ifeq ($(TEST_IOFS),true)
$(TINYGO) test -stack-size=6MB io/fs
endif
tinygo-test-fast:
$(TINYGO) test $(TEST_PACKAGES_HOST)
$(TINYGO) test $(TEST_SKIP_FLAG) $(TEST_PACKAGES_HOST)
tinygo-bench:
$(TINYGO) test -bench . $(TEST_PACKAGES_HOST) $(TEST_PACKAGES_SLOW)
tinygo-bench-fast:
$(TINYGO) test -bench . $(TEST_PACKAGES_HOST)

# Same thing, except for wasi rather than the current platform.
tinygo-test-wasm:
$(TINYGO) test -target wasm $(TEST_PACKAGES_WASM)
$(TINYGO) test -target wasm $(TEST_SKIP_FLAG) $(TEST_PACKAGES_WASM)
tinygo-test-wasi:
$(TINYGO) test -target wasip1 $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi
$(TINYGO) test -target wasip1 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi
tinygo-test-wasip1:
GOOS=wasip1 GOARCH=wasm $(TINYGO) test $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi
GOOS=wasip1 GOARCH=wasm $(TINYGO) test $(TEST_SKIP_FLAG) $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi
tinygo-test-wasip1-fast:
$(TINYGO) test -target=wasip1 $(TEST_PACKAGES_FAST) ./tests/runtime_wasi
$(TINYGO) test -target=wasip1 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_FAST) ./tests/runtime_wasi

tinygo-test-wasip2-slow:
$(TINYGO) test -target=wasip2 $(TEST_PACKAGES_SLOW)
$(TINYGO) test -target=wasip2 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_SLOW)
tinygo-test-wasip2-fast:
$(TINYGO) test -target=wasip2 $(TEST_PACKAGES_FAST) ./tests/runtime_wasi
$(TINYGO) test -target=wasip2 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_FAST) ./tests/runtime_wasi

tinygo-test-wasip2-sum-slow:
TINYGO=$(TINYGO) \
Expand All @@ -517,7 +521,7 @@ tinygo-bench-wasip2-fast:

# Run tests on riscv-qemu since that one provides a large amount of memory.
tinygo-test-baremetal:
$(TINYGO) test -target riscv-qemu $(TEST_PACKAGES_BAREMETAL)
$(TINYGO) test -target riscv-qemu $(TEST_SKIP_FLAG) $(TEST_PACKAGES_BAREMETAL)

# Test external packages in a large corpus.
test-corpus:
Expand Down
2 changes: 1 addition & 1 deletion builder/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {

// Version range supported by TinyGo.
const minorMin = 19
const minorMax = 24
const minorMax = 25

// Check that we support this Go toolchain version.
gorootMajor, gorootMinor, err := goenv.GetGorootVersion()
Expand Down
23 changes: 23 additions & 0 deletions compiler/intrinsics.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,29 @@ func (b *builder) createKeepAliveImpl() {
b.CreateRetVoid()
}

// createAbiEscapeImpl implements the generic internal/abi.Escape function. It
// currently only supports pointer types.
func (b *builder) createAbiEscapeImpl() {
b.createFunctionStart(true)

// The first parameter is assumed to be a pointer. This is checked at the
// call site of createAbiEscapeImpl.
pointerValue := b.getValue(b.fn.Params[0], getPos(b.fn))

// Create an equivalent of the following C code, which is basically just a
// nop but ensures the pointerValue is kept alive:
//
// __asm__ __volatile__("" : : "r"(pointerValue))
//
// It should be portable to basically everything as the "r" register type
// exists basically everywhere.
asmType := llvm.FunctionType(b.dataPtrType, []llvm.Type{b.dataPtrType}, false)
asmFn := llvm.InlineAsm(asmType, "", "=r,0", true, false, 0, false)
result := b.createCall(asmType, asmFn, []llvm.Value{pointerValue}, "")

b.CreateRet(result)
}

var mathToLLVMMapping = map[string]string{
"math.Ceil": "llvm.ceil.f64",
"math.Exp": "llvm.exp.f64",
Expand Down
17 changes: 17 additions & 0 deletions compiler/symbol.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,23 @@ func (c *compilerContext) maybeCreateSyntheticFunction(fn *ssa.Function, llvmFn
// The exception is the package initializer, which does appear in the
// *ssa.Package members and so shouldn't be created here.
if fn.Synthetic != "" && fn.Synthetic != "package initializer" && fn.Synthetic != "generic function" && fn.Synthetic != "range-over-func yield" {
if origin := fn.Origin(); origin != nil && origin.RelString(nil) == "internal/abi.Escape" {
// This is a special implementation or internal/abi.Escape, which
// can only really be implemented in the compiler.
// For simplicity we'll only implement pointer parameters for now.
if _, ok := fn.Params[0].Type().Underlying().(*types.Pointer); ok {
irbuilder := c.ctx.NewBuilder()
defer irbuilder.Dispose()
b := newBuilder(c, irbuilder, fn)
b.createAbiEscapeImpl()
llvmFn.SetLinkage(llvm.LinkOnceODRLinkage)
llvmFn.SetUnnamedAddr(true)
}
// If the parameter is not of a pointer type, it will be left
// unimplemented. This will result in a linker error if the function
// is really called, making it clear it needs to be implemented.
return
}
if len(fn.Blocks) == 0 {
c.addError(fn.Pos(), "missing function body")
return
Expand Down
4 changes: 4 additions & 0 deletions src/crypto/x509/internal/macos/macos.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ func SecCertificateCopyData(cert CFRef) ([]byte, error) {
return nil, errors.New("not implemented")
}

func SecTrustCopyCertificateChain(trustObj CFRef) (CFRef, error) {
return 0, errors.New("not implemented")
}

func SecTrustEvaluateWithError(trustObj CFRef) (int, error) {
return 0, errors.New("not implemented")
}
Expand Down
7 changes: 7 additions & 0 deletions src/internal/abi/escape.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,10 @@ import "unsafe"
func NoEscape(p unsafe.Pointer) unsafe.Pointer {
return p
}

func Escape[T any](x T) T {
// This function is either implemented in the compiler, or left undefined
// for some variation of T. The body of this function should not be compiled
// as-is.
panic("internal/abi.Escape: unreachable (implemented in the compiler)")
}
5 changes: 5 additions & 0 deletions src/reflect/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ type Method struct {
Index int // index for Type.Method
}

// IsExported reports whether the method is exported.
func (m Method) IsExported() bool {
return m.PkgPath == ""
}

// The following Type type has been copied almost entirely from
// https://github.com/golang/go/blob/go1.15/src/reflect/type.go#L27-L212.
// Some methods have been commented out as they haven't yet been implemented.
Expand Down
27 changes: 27 additions & 0 deletions src/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,33 @@ func UnlockOSThread() {
// point of the call.
func KeepAlive(x interface{})

// AddCleanup is a dummy cleanup implementation. It doesn't do any cleaning up.
//
// We base this on the following loophole in the official runtime.AddCleanup
// documentation:
//
// > The cleanup(arg) call is not always guaranteed to run; in particular it is
// > not guaranteed to run before program exit.
//
// So it's technically correct (the best kind of correct) to not run any
// cleanups. But of course, this can lead to resource leaks so cleanups may need
// to be implemented eventually.
func AddCleanup[T, S any](ptr *T, cleanup func(S), arg S) Cleanup {
return Cleanup{}
}

type Cleanup struct{}

func (c Cleanup) Stop() {}

//go:linkname registerWeakPointer weak.runtime_registerWeakPointer
func registerWeakPointer(ptr unsafe.Pointer) unsafe.Pointer {
// TODO: unimplemented.
// I hope not implementing this won't break anything, like packages that
// expect weak pointers to be GC'd before they actually are.
return ptr
}

var godebugUpdate func(string, string)

//go:linkname godebug_setUpdate internal/godebug.setUpdate
Expand Down
6 changes: 6 additions & 0 deletions src/runtime/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,9 @@ func timerCallback(tn *timerNode, delta int64) {
addTimer(tn)
}
}

//go:linkname time_runtimeIsBubbled time.runtimeIsBubbled
func time_runtimeIsBubbled() bool {
// We don't currently support bubbles.
return false
}
12 changes: 12 additions & 0 deletions src/sync/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,15 @@ func (m *Map) Range(f func(key, value interface{}) bool) {
}
}
}

// Swap replaces the value for the given key, and returns the old value if any.
func (m *Map) Swap(key, value any) (previous any, loaded bool) {
m.lock.Lock()
defer m.lock.Unlock()
if m.m == nil {
m.m = make(map[interface{}]interface{})
}
previous, loaded = m.m[key]
m.m[key] = value
return
}
19 changes: 19 additions & 0 deletions src/sync/map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,22 @@ func TestMapLoadAndDelete(t *testing.T) {
t.Errorf("LoadAndDelete returned %v, %v, want nil, false", v, ok)
}
}

func TestMapSwap(t *testing.T) {
var sm sync.Map
sm.Store("present", "value")

if v, ok := sm.Swap("present", "value2"); !ok || v != "value" {
t.Errorf("Swap returned %v, %v, want value, true", v, ok)
}
if v, ok := sm.Load("present"); !ok || v != "value2" {
t.Errorf("Load after Swap returned %v, %v, want value2, true", v, ok)
}

if v, ok := sm.Swap("new", "foo"); ok || v != nil {
t.Errorf("Swap returned %v, %v, want nil, false", v, ok)
}
if v, ok := sm.Load("present"); !ok || v != "value2" {
t.Errorf("Load after Swap returned %v, %v, want foo, true", v, ok)
}
}
4 changes: 4 additions & 0 deletions src/testing/benchmark.go
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,10 @@ func (b *B) RunParallel(body func(*PB)) {
return
}

func (b *B) Loop() bool {
panic("unimplemented: testing.B.Loop")
}

// Benchmark benchmarks a single function. It is useful for creating
// custom benchmarks that do not use the "go test" command.
//
Expand Down
Loading