diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 51c145f337..2acf285a51 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -27,7 +27,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21.0-rc.4' cache: true - name: Restore LLVM source cache uses: actions/cache/restore@v3 @@ -126,7 +126,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21.0-rc.4' cache: true - name: Build TinyGo run: go install diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 05199e21ec..1e809bf84c 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.20-alpine + image: golang:1.21rc4-alpine steps: - name: Install apk dependencies # tar: needed for actions/cache@v3 @@ -135,7 +135,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21.0-rc.4' cache: true - name: Install wasmtime run: | @@ -177,7 +177,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21.0-rc.4' cache: true - name: Install Node.js uses: actions/setup-node@v3 @@ -290,7 +290,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21.0-rc.4' cache: true - name: Restore LLVM source cache uses: actions/cache/restore@v3 @@ -407,7 +407,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21.0-rc.4' cache: true - name: Restore LLVM source cache uses: actions/cache/restore@v3 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 55e752d707..606394fc9f 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -35,7 +35,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21.0-rc.4' cache: true - name: Restore cached LLVM source uses: actions/cache/restore@v3 @@ -143,7 +143,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21.0-rc.4' cache: true - name: Download TinyGo build uses: actions/download-artifact@v2 @@ -173,7 +173,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21.0-rc.4' cache: true - name: Download TinyGo build uses: actions/download-artifact@v2 @@ -209,7 +209,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21.0-rc.4' cache: true - name: Download TinyGo build uses: actions/download-artifact@v2 diff --git a/builder/build.go b/builder/build.go index 2e8577e377..85114d9d72 100644 --- a/builder/build.go +++ b/builder/build.go @@ -217,19 +217,27 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe var packageJobs []*compileJob packageActionIDJobs := make(map[string]*compileJob) + if config.Options.GlobalValues == nil { + config.Options.GlobalValues = make(map[string]map[string]string) + } if config.Options.GlobalValues["runtime"]["buildVersion"] == "" { version := goenv.Version if strings.HasSuffix(goenv.Version, "-dev") && goenv.GitSha1 != "" { version += "-" + goenv.GitSha1 } - if config.Options.GlobalValues == nil { - config.Options.GlobalValues = make(map[string]map[string]string) - } if config.Options.GlobalValues["runtime"] == nil { config.Options.GlobalValues["runtime"] = make(map[string]string) } config.Options.GlobalValues["runtime"]["buildVersion"] = version } + if config.TestConfig.CompileTestBinary { + // The testing.testBinary is set to "1" when in a test. + // This is needed for testing.Testing() to work correctly. + if config.Options.GlobalValues["testing"] == nil { + config.Options.GlobalValues["testing"] = make(map[string]string) + } + config.Options.GlobalValues["testing"]["testBinary"] = "1" + } var embedFileObjects []*compileJob for _, pkg := range lprogram.Sorted() { diff --git a/builder/config.go b/builder/config.go index 1ca3aa6414..82e8cb94e6 100644 --- a/builder/config.go +++ b/builder/config.go @@ -27,7 +27,7 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) { if err != nil { return nil, err } - if major != 1 || minor < 18 || minor > 20 { + if major != 1 || minor < 18 || minor > 21 { // Note: when this gets updated, also update the Go compatibility matrix: // https://github.com/tinygo-org/tinygo-site/blob/dev/content/docs/reference/go-compat-matrix.md return nil, fmt.Errorf("requires go version 1.18 through 1.20, got go%d.%d", major, minor) diff --git a/compiler/calls.go b/compiler/calls.go index 65a69fea3c..a110addcf6 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -36,7 +36,11 @@ const ( // createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or // createRuntimeInvoke instead. func (b *builder) createRuntimeCallCommon(fnName string, args []llvm.Value, name string, isInvoke bool) llvm.Value { - fn := b.program.ImportedPackage("runtime").Members[fnName].(*ssa.Function) + member := b.program.ImportedPackage("runtime").Members[fnName] + if member == nil { + panic("unknown runtime call: " + fnName) + } + fn := member.(*ssa.Function) fnType, llvmFn := b.getFunction(fn) if llvmFn.IsNil() { panic("trying to call non-existent function: " + fn.RelString(nil)) diff --git a/compiler/compiler.go b/compiler/compiler.go index 09f11e614f..6dd43935a6 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1600,6 +1600,45 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c cplx = b.CreateInsertValue(cplx, r, 0, "") cplx = b.CreateInsertValue(cplx, i, 1, "") return cplx, nil + case "clear": + value := argValues[0] + switch typ := argTypes[0].Underlying().(type) { + case *types.Slice: + elementType := b.getLLVMType(typ.Elem()) + elementSize := b.targetData.TypeAllocSize(elementType) + elementAlign := b.targetData.ABITypeAlignment(elementType) + + // The pointer to the data to be cleared. + llvmBuf := b.CreateExtractValue(value, 0, "buf") + if llvmBuf.Type() != b.i8ptrType { // compatibility with LLVM 14 + llvmBuf = b.CreateBitCast(llvmBuf, b.i8ptrType, "") + } + + // The length (in bytes) to be cleared. + llvmLen := b.CreateExtractValue(value, 1, "len") + llvmLen = b.CreateMul(llvmLen, llvm.ConstInt(llvmLen.Type(), elementSize, false), "") + + // Do the clear operation using the LLVM memset builtin. + // This is also correct for nil slices: in those cases, len will be + // 0 which means the memset call is a no-op (according to the LLVM + // LangRef). + memset := b.getMemsetFunc() + call := b.createCall(memset.GlobalValueType(), memset, []llvm.Value{ + llvmBuf, // dest + llvm.ConstInt(b.ctx.Int8Type(), 0, false), // val + llvmLen, // len + llvm.ConstInt(b.ctx.Int1Type(), 0, false), // isVolatile + }, "") + call.AddCallSiteAttribute(1, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(elementAlign))) + + return llvm.Value{}, nil + case *types.Map: + m := argValues[0] + b.createMapClear(m) + return llvm.Value{}, nil + default: + return llvm.Value{}, b.makeError(pos, "unsupported type in clear builtin: "+typ.String()) + } case "copy": dst := argValues[0] src := argValues[1] @@ -1637,6 +1676,24 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c llvmLen = b.CreateZExt(llvmLen, b.intType, "len.int") } return llvmLen, nil + case "min", "max": + // min and max builtins, added in Go 1.21. + // We can simply reuse the existing binop comparison code, which has all + // the edge cases figured out already. + tok := token.LSS + if callName == "max" { + tok = token.GTR + } + result := argValues[0] + typ := argTypes[0] + for _, arg := range argValues[1:] { + cmp, err := b.createBinOp(tok, typ, typ, result, arg, pos) + if err != nil { + return result, err + } + result = b.CreateSelect(cmp, result, arg, "") + } + return result, nil case "print", "println": for i, value := range argValues { if i >= 1 && callName == "println" { diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 9675c40280..92ce31b012 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -54,6 +54,9 @@ func TestCompiler(t *testing.T) { if goMinor >= 20 { tests = append(tests, testCase{"go1.20.go", "", ""}) } + if goMinor >= 21 { + tests = append(tests, testCase{"go1.21.go", "", ""}) + } for _, tc := range tests { name := tc.file diff --git a/compiler/intrinsics.go b/compiler/intrinsics.go index 5761a438f1..af3a57de1b 100644 --- a/compiler/intrinsics.go +++ b/compiler/intrinsics.go @@ -70,15 +70,7 @@ func (b *builder) createMemoryCopyImpl() { // regular libc memset calls if they aren't optimized out in a different way. func (b *builder) createMemoryZeroImpl() { b.createFunctionStart(true) - fnName := "llvm.memset.p0.i" + strconv.Itoa(b.uintptrType.IntTypeWidth()) - if llvmutil.Major() < 15 { // compatibility with LLVM 14 - fnName = "llvm.memset.p0i8.i" + strconv.Itoa(b.uintptrType.IntTypeWidth()) - } - llvmFn := b.mod.NamedFunction(fnName) - if llvmFn.IsNil() { - fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType, b.ctx.Int8Type(), b.uintptrType, b.ctx.Int1Type()}, false) - llvmFn = llvm.AddFunction(b.mod, fnName, fnType) - } + llvmFn := b.getMemsetFunc() params := []llvm.Value{ b.getValue(b.fn.Params[0], getPos(b.fn)), llvm.ConstInt(b.ctx.Int8Type(), 0, false), @@ -89,6 +81,20 @@ func (b *builder) createMemoryZeroImpl() { b.CreateRetVoid() } +// Return the llvm.memset.p0.i8 function declaration. +func (c *compilerContext) getMemsetFunc() llvm.Value { + fnName := "llvm.memset.p0.i" + strconv.Itoa(c.uintptrType.IntTypeWidth()) + if llvmutil.Major() < 15 { // compatibility with LLVM 14 + fnName = "llvm.memset.p0i8.i" + strconv.Itoa(c.uintptrType.IntTypeWidth()) + } + llvmFn := c.mod.NamedFunction(fnName) + if llvmFn.IsNil() { + fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType, c.ctx.Int8Type(), c.uintptrType, c.ctx.Int1Type()}, false) + llvmFn = llvm.AddFunction(c.mod, fnName, fnType) + } + return llvmFn +} + // createKeepAlive creates the runtime.KeepAlive function. It is implemented // using inline assembly. func (b *builder) createKeepAliveImpl() { diff --git a/compiler/map.go b/compiler/map.go index c3d4289023..21f0ee4a67 100644 --- a/compiler/map.go +++ b/compiler/map.go @@ -185,6 +185,11 @@ func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos tok } } +// Clear the given map. +func (b *builder) createMapClear(m llvm.Value) { + b.createRuntimeCall("hashmapClear", []llvm.Value{m}, "") +} + // createMapIteratorNext lowers the *ssa.Next instruction for iterating over a // map. It returns a tuple of {bool, key, value} with the result of the // iteration. diff --git a/compiler/symbol.go b/compiler/symbol.go index 6426604ec1..cf9d43f7cb 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -367,7 +367,7 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { // The list of allowed types is based on this proposal: // https://github.com/golang/go/issues/59149 func (c *compilerContext) checkWasmImport(f *ssa.Function, pragma string) { - if c.pkg.Path() == "runtime" { + if c.pkg.Path() == "runtime" || c.pkg.Path() == "syscall/js" { // The runtime is a special case. Allow all kinds of parameters // (importantly, including pointers). return diff --git a/compiler/testdata/go1.21.go b/compiler/testdata/go1.21.go new file mode 100644 index 0000000000..589486d024 --- /dev/null +++ b/compiler/testdata/go1.21.go @@ -0,0 +1,65 @@ +package main + +func min1(a int) int { + return min(a) +} + +func min2(a, b int) int { + return min(a, b) +} + +func min3(a, b, c int) int { + return min(a, b, c) +} + +func min4(a, b, c, d int) int { + return min(a, b, c, d) +} + +func minUint8(a, b uint8) uint8 { + return min(a, b) +} + +func minUnsigned(a, b uint) uint { + return min(a, b) +} + +func minFloat32(a, b float32) float32 { + return min(a, b) +} + +func minFloat64(a, b float64) float64 { + return min(a, b) +} + +func minString(a, b string) string { + return min(a, b) +} + +func maxInt(a, b int) int { + return max(a, b) +} + +func maxUint(a, b uint) uint { + return max(a, b) +} + +func maxFloat32(a, b float32) float32 { + return max(a, b) +} + +func maxString(a, b string) string { + return max(a, b) +} + +func clearSlice(s []int) { + clear(s) +} + +func clearZeroSizedSlice(s []struct{}) { + clear(s) +} + +func clearMap(m map[string]int) { + clear(m) +} diff --git a/compiler/testdata/go1.21.ll b/compiler/testdata/go1.21.ll new file mode 100644 index 0000000000..d65c75f4f0 --- /dev/null +++ b/compiler/testdata/go1.21.ll @@ -0,0 +1,179 @@ +; ModuleID = 'go1.21.go' +source_filename = "go1.21.go" +target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20" +target triple = "wasm32-unknown-wasi" + +%runtime._string = type { ptr, i32 } + +; Function Attrs: allockind("alloc,zeroed") allocsize(0) +declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 + +declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1 + +; Function Attrs: nounwind +define hidden void @main.init(ptr %context) unnamed_addr #2 { +entry: + ret void +} + +; Function Attrs: nounwind +define hidden i32 @main.min1(i32 %a, ptr %context) unnamed_addr #2 { +entry: + ret i32 %a +} + +; Function Attrs: nounwind +define hidden i32 @main.min2(i32 %a, i32 %b, ptr %context) unnamed_addr #2 { +entry: + %0 = call i32 @llvm.smin.i32(i32 %a, i32 %b) + ret i32 %0 +} + +; Function Attrs: nounwind +define hidden i32 @main.min3(i32 %a, i32 %b, i32 %c, ptr %context) unnamed_addr #2 { +entry: + %0 = call i32 @llvm.smin.i32(i32 %a, i32 %b) + %1 = call i32 @llvm.smin.i32(i32 %0, i32 %c) + ret i32 %1 +} + +; Function Attrs: nounwind +define hidden i32 @main.min4(i32 %a, i32 %b, i32 %c, i32 %d, ptr %context) unnamed_addr #2 { +entry: + %0 = call i32 @llvm.smin.i32(i32 %a, i32 %b) + %1 = call i32 @llvm.smin.i32(i32 %0, i32 %c) + %2 = call i32 @llvm.smin.i32(i32 %1, i32 %d) + ret i32 %2 +} + +; Function Attrs: nounwind +define hidden i8 @main.minUint8(i8 %a, i8 %b, ptr %context) unnamed_addr #2 { +entry: + %0 = call i8 @llvm.umin.i8(i8 %a, i8 %b) + ret i8 %0 +} + +; Function Attrs: nounwind +define hidden i32 @main.minUnsigned(i32 %a, i32 %b, ptr %context) unnamed_addr #2 { +entry: + %0 = call i32 @llvm.umin.i32(i32 %a, i32 %b) + ret i32 %0 +} + +; Function Attrs: nounwind +define hidden float @main.minFloat32(float %a, float %b, ptr %context) unnamed_addr #2 { +entry: + %0 = fcmp olt float %a, %b + %1 = select i1 %0, float %a, float %b + ret float %1 +} + +; Function Attrs: nounwind +define hidden double @main.minFloat64(double %a, double %b, ptr %context) unnamed_addr #2 { +entry: + %0 = fcmp olt double %a, %b + %1 = select i1 %0, double %a, double %b + ret double %1 +} + +; Function Attrs: nounwind +define hidden %runtime._string @main.minString(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr %context) unnamed_addr #2 { +entry: + %0 = insertvalue %runtime._string zeroinitializer, ptr %a.data, 0 + %1 = insertvalue %runtime._string %0, i32 %a.len, 1 + %2 = insertvalue %runtime._string zeroinitializer, ptr %b.data, 0 + %3 = insertvalue %runtime._string %2, i32 %b.len, 1 + %stackalloc = alloca i8, align 1 + %4 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr undef) #5 + %5 = select i1 %4, %runtime._string %1, %runtime._string %3 + %6 = extractvalue %runtime._string %5, 0 + call void @runtime.trackPointer(ptr %6, ptr nonnull %stackalloc, ptr undef) #5 + ret %runtime._string %5 +} + +declare i1 @runtime.stringLess(ptr, i32, ptr, i32, ptr) #1 + +; Function Attrs: nounwind +define hidden i32 @main.maxInt(i32 %a, i32 %b, ptr %context) unnamed_addr #2 { +entry: + %0 = call i32 @llvm.smax.i32(i32 %a, i32 %b) + ret i32 %0 +} + +; Function Attrs: nounwind +define hidden i32 @main.maxUint(i32 %a, i32 %b, ptr %context) unnamed_addr #2 { +entry: + %0 = call i32 @llvm.umax.i32(i32 %a, i32 %b) + ret i32 %0 +} + +; Function Attrs: nounwind +define hidden float @main.maxFloat32(float %a, float %b, ptr %context) unnamed_addr #2 { +entry: + %0 = fcmp ogt float %a, %b + %1 = select i1 %0, float %a, float %b + ret float %1 +} + +; Function Attrs: nounwind +define hidden %runtime._string @main.maxString(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr %context) unnamed_addr #2 { +entry: + %0 = insertvalue %runtime._string zeroinitializer, ptr %a.data, 0 + %1 = insertvalue %runtime._string %0, i32 %a.len, 1 + %2 = insertvalue %runtime._string zeroinitializer, ptr %b.data, 0 + %3 = insertvalue %runtime._string %2, i32 %b.len, 1 + %stackalloc = alloca i8, align 1 + %4 = call i1 @runtime.stringLess(ptr %b.data, i32 %b.len, ptr %a.data, i32 %a.len, ptr undef) #5 + %5 = select i1 %4, %runtime._string %1, %runtime._string %3 + %6 = extractvalue %runtime._string %5, 0 + call void @runtime.trackPointer(ptr %6, ptr nonnull %stackalloc, ptr undef) #5 + ret %runtime._string %5 +} + +; Function Attrs: nounwind +define hidden void @main.clearSlice(ptr %s.data, i32 %s.len, i32 %s.cap, ptr %context) unnamed_addr #2 { +entry: + %0 = shl i32 %s.len, 2 + call void @llvm.memset.p0.i32(ptr align 4 %s.data, i8 0, i32 %0, i1 false) + ret void +} + +; Function Attrs: argmemonly nocallback nofree nounwind willreturn writeonly +declare void @llvm.memset.p0.i32(ptr nocapture writeonly, i8, i32, i1 immarg) #3 + +; Function Attrs: nounwind +define hidden void @main.clearZeroSizedSlice(ptr %s.data, i32 %s.len, i32 %s.cap, ptr %context) unnamed_addr #2 { +entry: + ret void +} + +; Function Attrs: nounwind +define hidden void @main.clearMap(ptr dereferenceable_or_null(40) %m, ptr %context) unnamed_addr #2 { +entry: + call void @runtime.hashmapClear(ptr %m, ptr undef) #5 + ret void +} + +declare void @runtime.hashmapClear(ptr dereferenceable_or_null(40), ptr) #1 + +; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn +declare i32 @llvm.smin.i32(i32, i32) #4 + +; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn +declare i8 @llvm.umin.i8(i8, i8) #4 + +; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn +declare i32 @llvm.umin.i32(i32, i32) #4 + +; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn +declare i32 @llvm.smax.i32(i32, i32) #4 + +; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn +declare i32 @llvm.umax.i32(i32, i32) #4 + +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } +attributes #1 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } +attributes #3 = { argmemonly nocallback nofree nounwind willreturn writeonly } +attributes #4 = { nocallback nofree nosync nounwind readnone speculatable willreturn } +attributes #5 = { nounwind } diff --git a/go.mod b/go.mod index 3aabc46aed..238f474e6a 100644 --- a/go.mod +++ b/go.mod @@ -31,3 +31,5 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.12 // indirect ) + +replace go.bug.st/serial => github.com/deadprogram/go-serial v0.0.0-20230717164825-4529b3232919 diff --git a/go.sum b/go.sum index 78a7ea867d..6c682c1cb4 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moA github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/deadprogram/go-serial v0.0.0-20230717164825-4529b3232919 h1:Hi7G1bCG70NwlyqGswJKEHoIi4hJVN1SsmfwZ+DQHtw= +github.com/deadprogram/go-serial v0.0.0-20230717164825-4529b3232919/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= @@ -46,8 +48,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs= github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -go.bug.st/serial v1.3.5 h1:k50SqGZCnHZ2MiBQgzccXWG+kd/XpOs1jUljpDDKzaE= -go.bug.st/serial v1.3.5/go.mod h1:z8CesKorE90Qr/oRSJiEuvzYRKol9r/anJZEb5kt304= golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/main_test.go b/main_test.go index 59254656ee..32d13f9103 100644 --- a/main_test.go +++ b/main_test.go @@ -71,6 +71,16 @@ func TestBuild(t *testing.T) { "zeroalloc.go", } + // Go 1.21 made some changes to the language, which we can only test when + // we're actually on Go 1.21. + _, minor, err := goenv.GetGorootVersion() + if err != nil { + t.Fatal("could not get version:", minor) + } + if minor >= 21 { + tests = append(tests, "go1.21.go") + } + if *testTarget != "" { // This makes it possible to run one specific test (instead of all), // which is especially useful to quickly check whether some changes diff --git a/src/internal/bytealg/bytealg.go b/src/internal/bytealg/bytealg.go index a6744c4144..72be9ac587 100644 --- a/src/internal/bytealg/bytealg.go +++ b/src/internal/bytealg/bytealg.go @@ -251,3 +251,13 @@ func IndexRabinKarp(s, substr string) int { } return -1 } + +// MakeNoZero makes a slice of length and capacity n without zeroing the bytes. +// It is the caller's responsibility to ensure uninitialized bytes +// do not leak to the end user. +func MakeNoZero(n int) []byte { + // Note: this does zero the buffer even though that's not necessary. + // For performance reasons we might want to change this (similar to the + // malloc function implemented in the runtime). + return make([]byte, n) +} diff --git a/src/runtime/baremetal.go b/src/runtime/baremetal.go index a51191bb95..173d0db25e 100644 --- a/src/runtime/baremetal.go +++ b/src/runtime/baremetal.go @@ -38,6 +38,8 @@ func growHeap() bool { //export malloc func libc_malloc(size uintptr) unsafe.Pointer { + // Note: this zeroes the returned buffer which is not necessary. + // The same goes for bytealg.MakeNoZero. return alloc(size, nil) } diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index 8a902a55d2..dfbec300ed 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -91,6 +91,35 @@ func hashmapMakeUnsafePointer(keySize, valueSize uintptr, sizeHint uintptr, alg return (unsafe.Pointer)(hashmapMake(keySize, valueSize, sizeHint, alg)) } +// Remove all entries from the map, without actually deallocating the space for +// it. This is used for the clear builtin, and can be used to reuse a map (to +// avoid extra heap allocations). +func hashmapClear(m *hashmap) { + if m == nil { + // Nothing to do. According to the spec: + // > If the map or slice is nil, clear is a no-op. + return + } + + m.count = 0 + numBuckets := uintptr(1) << m.bucketBits + bucketSize := hashmapBucketSize(m) + for i := uintptr(0); i < numBuckets; i++ { + bucket := hashmapBucketAddr(m, m.buckets, i) + for bucket != nil { + // Clear the tophash, to mark these keys/values as removed. + bucket.tophash = [8]uint8{} + + // Clear the keys and values in the bucket so that the GC won't pin + // these allocations. + memzero(unsafe.Add(unsafe.Pointer(bucket), unsafe.Sizeof(hashmapBucket{})), bucketSize-unsafe.Sizeof(hashmapBucket{})) + + // Move on to the next bucket in the chain. + bucket = bucket.next + } + } +} + func hashmapKeyEqualAlg(alg hashmapAlgorithm) func(x, y unsafe.Pointer, n uintptr) bool { switch alg { case hashmapAlgorithmBinary: diff --git a/src/runtime/metrics.go b/src/runtime/metrics.go new file mode 100644 index 0000000000..bd07777db6 --- /dev/null +++ b/src/runtime/metrics.go @@ -0,0 +1,10 @@ +package runtime + +// Implementation of functions needed by runtime/metrics. +// Mostly just dummy implementations: we don't currently use any of these +// metrics. + +//go:linkname godebug_registerMetric internal/godebug.registerMetric +func godebug_registerMetric(name string, read func() uint64) { + // Dummy function for compatibility with Go 1.21. +} diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go index ceb6a2240c..ac7cd25c93 100644 --- a/src/runtime/runtime.go +++ b/src/runtime/runtime.go @@ -100,3 +100,27 @@ func godebug_setUpdate(update func(string, string)) { // variable changes (for example, via os.Setenv). godebugUpdate = update } + +//go:linkname godebug_setNewIncNonDefault internal/godebug.setNewIncNonDefault +func godebug_setNewIncNonDefault(newIncNonDefault func(string) func()) { + // Dummy function necessary in Go 1.21. +} + +// Write to the given file descriptor. +// This is called from internal/godebug starting with Go 1.21, and only seems to +// be called with the stderr file descriptor. +func write(fd uintptr, p unsafe.Pointer, n int32) int32 { + if fd == 2 { // stderr + // Convert to a string, because we know that p won't change during the + // call to printstring. + // TODO: use unsafe.String instead once we require Go 1.20. + s := _string{ + ptr: (*byte)(p), + length: uintptr(n), + } + str := *(*string)(unsafe.Pointer(&s)) + printstring(str) + return n + } + return 0 +} diff --git a/src/runtime/runtime_nintendoswitch.go b/src/runtime/runtime_nintendoswitch.go index f2606023ff..bcfa5151d0 100644 --- a/src/runtime/runtime_nintendoswitch.go +++ b/src/runtime/runtime_nintendoswitch.go @@ -105,7 +105,7 @@ func abort() { } //export write -func write(fd int32, buf *byte, count int) int { +func libc_write(fd int32, buf *byte, count int) int { // TODO: Proper handling write for i := 0; i < count; i++ { putchar(*buf) diff --git a/src/runtime/runtime_wasm_js.go b/src/runtime/runtime_wasm_js.go index c735c76fc1..443ed9e2ea 100644 --- a/src/runtime/runtime_wasm_js.go +++ b/src/runtime/runtime_wasm_js.go @@ -44,8 +44,8 @@ func nanosecondsToTicks(ns int64) timeUnit { // This function is called by the scheduler. // Schedule a call to runtime.scheduler, do not actually sleep. // -//export runtime.sleepTicks +//go:wasmimport gojs runtime.sleepTicks func sleepTicks(d timeUnit) -//export runtime.ticks +//go:wasmimport gojs runtime.ticks func ticks() timeUnit diff --git a/src/runtime/symtab.go b/src/runtime/symtab.go index cfc9885d7f..37c21ee895 100644 --- a/src/runtime/symtab.go +++ b/src/runtime/symtab.go @@ -5,11 +5,14 @@ type Frames struct { } type Frame struct { + PC uintptr + + Func *Func + Function string File string Line int - PC uintptr } func CallersFrames(callers []uintptr) *Frames { diff --git a/src/syscall/syscall_libc_darwin.go b/src/syscall/syscall_libc_darwin.go index 704ba29cae..d64f1061f3 100644 --- a/src/syscall/syscall_libc_darwin.go +++ b/src/syscall/syscall_libc_darwin.go @@ -68,6 +68,7 @@ const ( EISDIR Errno = 21 EINVAL Errno = 22 EMFILE Errno = 24 + EROFS Errno = 30 EPIPE Errno = 32 EAGAIN Errno = 35 ENOTCONN Errno = 57 diff --git a/src/testing/testing.go b/src/testing/testing.go index fee9d40b19..8429e92212 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -120,6 +120,15 @@ func Verbose() bool { return flagVerbose } +// String constant that is being set when running a test. +var testBinary string + +// Testing returns whether the program was compiled as a test, using "tinygo +// test". It returns false when built using "tinygo build", "tinygo flash", etc. +func Testing() bool { + return testBinary == "1" +} + // flushToParent writes c.output to the parent after first writing the header // with the given format and arguments. func (c *common) flushToParent(testName, format string, args ...interface{}) { diff --git a/src/testing/testing_test.go b/src/testing/testing_test.go index 8a2586535e..631a313414 100644 --- a/src/testing/testing_test.go +++ b/src/testing/testing_test.go @@ -195,3 +195,9 @@ func TestSetenv(t *testing.T) { } } } + +func TestTesting(t *testing.T) { + if !testing.Testing() { + t.Error("Expected testing.Testing() to return true while in a test") + } +} diff --git a/targets/wasm_exec.js b/targets/wasm_exec.js index 8e29d6266c..5dfc67c357 100644 --- a/targets/wasm_exec.js +++ b/targets/wasm_exec.js @@ -284,7 +284,7 @@ return 0; }, }, - env: { + gojs: { // func ticks() float64 "runtime.ticks": () => { return timeOrigin + performance.now(); @@ -444,6 +444,10 @@ }, } }; + + // Go 1.20 uses 'env'. Go 1.21 uses 'gojs'. + // For compatibility, we use both as long as Go 1.20 is supported. + this.importObject.env = this.importObject.gojs; } async run(instance) { diff --git a/testdata/go1.21.go b/testdata/go1.21.go new file mode 100644 index 0000000000..603bd06e27 --- /dev/null +++ b/testdata/go1.21.go @@ -0,0 +1,29 @@ +package main + +func main() { + // The new min/max builtins. + ia := 1 + ib := 5 + ic := -3 + fa := 1.0 + fb := 5.0 + fc := -3.0 + println("min/max:", min(ia, ib, ic), max(ia, ib, ic)) + println("min/max:", min(fa, fb, fc), max(fa, fb, fc)) + + // The clear builtin, for slices. + s := []int{1, 2, 3, 4, 5} + clear(s[:3]) + println("cleared s[:3]:", s[0], s[1], s[2], s[3], s[4]) + + // The clear builtin, for maps. + m := map[int]string{ + 1: "one", + 2: "two", + 3: "three", + } + clear(m) + println("cleared map:", m[1], m[2], m[3], len(m)) + m[4] = "four" + println("added to cleared map:", m[1], m[2], m[3], m[4], len(m)) +} diff --git a/testdata/go1.21.txt b/testdata/go1.21.txt new file mode 100644 index 0000000000..3edfdb4568 --- /dev/null +++ b/testdata/go1.21.txt @@ -0,0 +1,5 @@ +min/max: -3 5 +min/max: -3.000000e+000 +5.000000e+000 +cleared s[:3]: 0 0 0 4 5 +cleared map: 0 +added to cleared map: four 1 diff --git a/testdata/testing.go b/testdata/testing.go index 4c1cf44efe..ff378fea95 100644 --- a/testdata/testing.go +++ b/testdata/testing.go @@ -73,6 +73,9 @@ func fakeMatchString(pat, str string) (bool, error) { } func main() { + if testing.Testing() { + println("not running a test at the moment, testing.Testing() should return false") + } testing.Init() flag.Set("test.run", ".*/B") m := testing.MainStart(matchStringOnly(fakeMatchString /*regexp.MatchString*/), tests, benchmarks, fuzzes, examples)