diff --git a/.circleci/config.yml b/.circleci/config.yml index 5a73dceae2..68a013a253 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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" @@ -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 diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index bda9474327..564978c821 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -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 @@ -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 }} diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 803759892f..52e49eaff2 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -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: @@ -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 @@ -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 @@ -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 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 6c428c98c4..ee7fbf1adb 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/Dockerfile b/Dockerfile index 520ad7c9b5..c45dd5d777 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 && \ @@ -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 diff --git a/GNUmakefile b/GNUmakefile index 2b14dd6ac0..61174600eb 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -460,11 +460,15 @@ 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. @@ -472,7 +476,7 @@ 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: @@ -480,18 +484,18 @@ tinygo-bench-fast: # 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) \ @@ -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: diff --git a/builder/config.go b/builder/config.go index b36b9333f3..5fb74ee192 100644 --- a/builder/config.go +++ b/builder/config.go @@ -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() diff --git a/compiler/intrinsics.go b/compiler/intrinsics.go index 3c7edd7c95..5f1ba6f7d1 100644 --- a/compiler/intrinsics.go +++ b/compiler/intrinsics.go @@ -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", diff --git a/compiler/symbol.go b/compiler/symbol.go index c56e0792f2..2507b3eedb 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -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 diff --git a/src/crypto/x509/internal/macos/macos.go b/src/crypto/x509/internal/macos/macos.go index e9ec2ef843..c662acb577 100644 --- a/src/crypto/x509/internal/macos/macos.go +++ b/src/crypto/x509/internal/macos/macos.go @@ -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") } diff --git a/src/internal/abi/escape.go b/src/internal/abi/escape.go index 0ecdf80308..1f4c763312 100644 --- a/src/internal/abi/escape.go +++ b/src/internal/abi/escape.go @@ -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)") +} diff --git a/src/reflect/type.go b/src/reflect/type.go index 828cf12e27..884f89dc84 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -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. diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go index 6bd719f548..3bd98319e8 100644 --- a/src/runtime/runtime.go +++ b/src/runtime/runtime.go @@ -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 diff --git a/src/runtime/time.go b/src/runtime/time.go index 3935b4486e..23a9bf5e2f 100644 --- a/src/runtime/time.go +++ b/src/runtime/time.go @@ -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 +} diff --git a/src/sync/map.go b/src/sync/map.go index cd8a1967d6..8b5c0cff76 100644 --- a/src/sync/map.go +++ b/src/sync/map.go @@ -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 +} diff --git a/src/sync/map_test.go b/src/sync/map_test.go index f493bdfb51..a41faa40fd 100644 --- a/src/sync/map_test.go +++ b/src/sync/map_test.go @@ -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) + } +} diff --git a/src/testing/benchmark.go b/src/testing/benchmark.go index 53c7fc7b10..2dc59b72fd 100644 --- a/src/testing/benchmark.go +++ b/src/testing/benchmark.go @@ -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. //