From ad4d722f54b1521e163ce5d0687f51817fccf617 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 16 Mar 2024 16:31:36 +0100 Subject: [PATCH 001/444] all: move -panic=trap support to the compiler/runtime Support for `-panic=trap` was previously a pass in the optimization pipeline. This change moves it to the compiler and runtime, which in my opinion is a much better place. As a side effect, it also fixes https://github.com/tinygo-org/tinygo/issues/4161 by trapping inside runtime.runtimePanicAt and not just runtime.runtimePanic. This change also adds a test for the list of imported functions. This is a more generic test where it's easy to add more tests for WebAssembly file properties, such as exported functions. --- builder/build.go | 1 + compiler/compiler.go | 8 +++++ main_test.go | 60 +++++++++++++++++++++++++++++++++ src/runtime/panic.go | 16 +++++++++ transform/optimizer.go | 4 --- transform/panic.go | 33 ------------------ transform/panic_test.go | 12 ------- transform/testdata/panic.ll | 22 ------------ transform/testdata/panic.out.ll | 25 -------------- transform/transform_test.go | 1 + 10 files changed, 86 insertions(+), 96 deletions(-) delete mode 100644 transform/panic.go delete mode 100644 transform/panic_test.go delete mode 100644 transform/testdata/panic.ll delete mode 100644 transform/testdata/panic.out.ll diff --git a/builder/build.go b/builder/build.go index b9965c1e85..7b7cc4728c 100644 --- a/builder/build.go +++ b/builder/build.go @@ -200,6 +200,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe MaxStackAlloc: config.MaxStackAlloc(), NeedsStackObjects: config.NeedsStackObjects(), Debug: !config.Options.SkipDWARF, // emit DWARF except when -internal-nodwarf is passed + PanicStrategy: config.PanicStrategy(), } // Load the target machine, which is the LLVM object that contains all diff --git a/compiler/compiler.go b/compiler/compiler.go index 2ebd5a9f22..63fc214c99 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -56,6 +56,7 @@ type Config struct { MaxStackAlloc uint64 NeedsStackObjects bool Debug bool // Whether to emit debug information in the LLVM module. + PanicStrategy string } // compilerContext contains function-independent data that should still be @@ -1855,6 +1856,13 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) supportsRecover = 1 } return llvm.ConstInt(b.ctx.Int1Type(), supportsRecover, false), nil + case name == "runtime.panicStrategy": + // These constants are defined in src/runtime/panic.go. + panicStrategy := map[string]uint64{ + "print": 1, // panicStrategyPrint + "trap": 2, // panicStrategyTrap + }[b.Config.PanicStrategy] + return llvm.ConstInt(b.ctx.Int8Type(), panicStrategy, false), nil case name == "runtime/interrupt.New": return b.createInterruptGlobal(instr) } diff --git a/main_test.go b/main_test.go index b3e86420f4..9c28eb86d6 100644 --- a/main_test.go +++ b/main_test.go @@ -15,11 +15,13 @@ import ( "reflect" "regexp" "runtime" + "slices" "strings" "sync" "testing" "time" + "github.com/aykevl/go-wasm" "github.com/tinygo-org/tinygo/builder" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/goenv" @@ -404,6 +406,64 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c } } +// Test WebAssembly files for certain properties. +func TestWebAssembly(t *testing.T) { + t.Parallel() + type testCase struct { + name string + panicStrategy string + imports []string + } + for _, tc := range []testCase{ + // Test whether there really are no imports when using -panic=trap. This + // tests the bugfix for https://github.com/tinygo-org/tinygo/issues/4161. + {name: "panic-default", imports: []string{"wasi_snapshot_preview1.fd_write"}}, + {name: "panic-trap", panicStrategy: "trap", imports: []string{}}, + } { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + tmpdir := t.TempDir() + options := optionsFromTarget("wasi", sema) + options.PanicStrategy = tc.panicStrategy + config, err := builder.NewConfig(&options) + if err != nil { + t.Fatal(err) + } + + result, err := builder.Build("testdata/trivialpanic.go", ".wasm", tmpdir, config) + if err != nil { + t.Fatal("failed to build binary:", err) + } + f, err := os.Open(result.Binary) + if err != nil { + t.Fatal("could not open output binary:", err) + } + defer f.Close() + module, err := wasm.Parse(f) + if err != nil { + t.Fatal("could not parse output binary:", err) + } + + // Test the list of imports. + if tc.imports != nil { + var imports []string + for _, section := range module.Sections { + switch section := section.(type) { + case *wasm.SectionImport: + for _, symbol := range section.Entries { + imports = append(imports, symbol.Module+"."+symbol.Field) + } + } + } + if !slices.Equal(imports, tc.imports) { + t.Errorf("import list not as expected!\nexpected: %v\nactual: %v", tc.imports, imports) + } + } + }) + } +} + func TestTest(t *testing.T) { t.Parallel() diff --git a/src/runtime/panic.go b/src/runtime/panic.go index e8a67e4b64..062305f15c 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -21,6 +21,16 @@ func tinygo_longjmp(frame *deferFrame) // Returns whether recover is supported on the current architecture. func supportsRecover() bool +const ( + panicStrategyPrint = 1 + panicStrategyTrap = 2 +) + +// Compile intrinsic. +// Returns which strategy is used. This is usually "print" but can be changed +// using the -panic= compiler flag. +func panicStrategy() uint8 + // DeferFrame is a stack allocated object that stores information for the // current "defer frame", which is used in functions that use the `defer` // keyword. @@ -37,6 +47,9 @@ type deferFrame struct { // Builtin function panic(msg), used as a compiler intrinsic. func _panic(message interface{}) { + if panicStrategy() == panicStrategyTrap { + trap() + } if supportsRecover() { frame := (*deferFrame)(task.Current().DeferFrame) if frame != nil { @@ -60,6 +73,9 @@ func runtimePanic(msg string) { } func runtimePanicAt(addr unsafe.Pointer, msg string) { + if panicStrategy() == panicStrategyTrap { + trap() + } if hasReturnAddr { printstring("panic: runtime error at ") printptr(uintptr(addr) - callInstSize) diff --git a/transform/optimizer.go b/transform/optimizer.go index e73e976006..cd4d1ee883 100644 --- a/transform/optimizer.go +++ b/transform/optimizer.go @@ -40,10 +40,6 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error { fn.SetLinkage(llvm.ExternalLinkage) } - if config.PanicStrategy() == "trap" { - ReplacePanicsWithTrap(mod) // -panic=trap - } - // run a check of all of our code if config.VerifyIR() { errs := ircheck.Module(mod) diff --git a/transform/panic.go b/transform/panic.go deleted file mode 100644 index dee3bae06d..0000000000 --- a/transform/panic.go +++ /dev/null @@ -1,33 +0,0 @@ -package transform - -import ( - "tinygo.org/x/go-llvm" -) - -// ReplacePanicsWithTrap replaces each call to panic (or similar functions) with -// calls to llvm.trap, to reduce code size. This is the -panic=trap command-line -// option. -func ReplacePanicsWithTrap(mod llvm.Module) { - ctx := mod.Context() - builder := ctx.NewBuilder() - defer builder.Dispose() - - trap := mod.NamedFunction("llvm.trap") - if trap.IsNil() { - trapType := llvm.FunctionType(ctx.VoidType(), nil, false) - trap = llvm.AddFunction(mod, "llvm.trap", trapType) - } - for _, name := range []string{"runtime._panic", "runtime.runtimePanic"} { - fn := mod.NamedFunction(name) - if fn.IsNil() { - continue - } - for _, use := range getUses(fn) { - if use.IsACallInst().IsNil() || use.CalledValue() != fn { - panic("expected use of a panic function to be a call") - } - builder.SetInsertPointBefore(use) - builder.CreateCall(trap.GlobalValueType(), trap, nil, "") - } - } -} diff --git a/transform/panic_test.go b/transform/panic_test.go deleted file mode 100644 index ea4efd0e7d..0000000000 --- a/transform/panic_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package transform_test - -import ( - "testing" - - "github.com/tinygo-org/tinygo/transform" -) - -func TestReplacePanicsWithTrap(t *testing.T) { - t.Parallel() - testTransform(t, "testdata/panic", transform.ReplacePanicsWithTrap) -} diff --git a/transform/testdata/panic.ll b/transform/testdata/panic.ll deleted file mode 100644 index 660e30f2f5..0000000000 --- a/transform/testdata/panic.ll +++ /dev/null @@ -1,22 +0,0 @@ -target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64" -target triple = "armv7m-none-eabi" - -@"runtime.lookupPanic$string" = constant [18 x i8] c"index out of range" - -declare void @runtime.runtimePanic(ptr, i32) - -declare void @runtime._panic(i32, ptr) - -define void @runtime.lookupPanic() { - call void @runtime.runtimePanic(ptr @"runtime.lookupPanic$string", i32 18) - ret void -} - -; This is equivalent to the following code: -; func someFunc(x interface{}) { -; panic(x) -; } -define void @someFunc(i32 %typecode, ptr %value) { - call void @runtime._panic(i32 %typecode, ptr %value) - unreachable -} diff --git a/transform/testdata/panic.out.ll b/transform/testdata/panic.out.ll deleted file mode 100644 index 458e4c2477..0000000000 --- a/transform/testdata/panic.out.ll +++ /dev/null @@ -1,25 +0,0 @@ -target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64" -target triple = "armv7m-none-eabi" - -@"runtime.lookupPanic$string" = constant [18 x i8] c"index out of range" - -declare void @runtime.runtimePanic(ptr, i32) - -declare void @runtime._panic(i32, ptr) - -define void @runtime.lookupPanic() { - call void @llvm.trap() - call void @runtime.runtimePanic(ptr @"runtime.lookupPanic$string", i32 18) - ret void -} - -define void @someFunc(i32 %typecode, ptr %value) { - call void @llvm.trap() - call void @runtime._panic(i32 %typecode, ptr %value) - unreachable -} - -; Function Attrs: cold noreturn nounwind -declare void @llvm.trap() #0 - -attributes #0 = { cold noreturn nounwind } diff --git a/transform/transform_test.go b/transform/transform_test.go index f23a480a90..40769dbe86 100644 --- a/transform/transform_test.go +++ b/transform/transform_test.go @@ -137,6 +137,7 @@ func compileGoFileForTesting(t *testing.T, filename string) llvm.Module { Scheduler: config.Scheduler(), AutomaticStackSize: config.AutomaticStackSize(), Debug: true, + PanicStrategy: config.PanicStrategy(), } machine, err := compiler.NewTargetMachine(compilerConfig) if err != nil { From 78775007fa475a08dd64d5bd4b0093e8ece6fc52 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 12 Mar 2024 21:52:52 +0100 Subject: [PATCH 002/444] wasm: fix symbol table index for archives The symbol table was generated incorrectly. The correct way is to use the custom linking WebAssembly section, which I implemented in go-wasm for this purpose. This fixes https://github.com/tinygo-org/tinygo/issues/4114 and is a prerequisite for https://github.com/tinygo-org/tinygo/pull/4176. --- builder/ar.go | 22 ++++++++++++++++------ go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/builder/ar.go b/builder/ar.go index 3f1c8c213f..578b88ba57 100644 --- a/builder/ar.go +++ b/builder/ar.go @@ -78,17 +78,27 @@ func makeArchive(arfile *os.File, objs []string) error { } else if dbg, err := wasm.Parse(objfile); err == nil { for _, s := range dbg.Sections { switch section := s.(type) { - case *wasm.SectionImport: - for _, ln := range section.Entries { - - if ln.Kind != wasm.ExtKindFunction { - // Not a function + case *wasm.SectionLinking: + for _, symbol := range section.Symbols { + if symbol.Flags&wasm.LinkingSymbolFlagUndefined != 0 { + // Don't list undefined functions. + continue + } + if symbol.Flags&wasm.LinkingSymbolFlagBindingLocal != 0 { + // Don't include local symbols. + continue + } + if symbol.Kind != wasm.LinkingSymbolKindFunction && symbol.Kind != wasm.LinkingSymbolKindData { + // Link functions and data symbols. + // Some data symbols need to be included, such as + // __log_data. continue } + // Include in the archive. symbolTable = append(symbolTable, struct { name string fileIndex int - }{ln.Field, i}) + }{symbol.Name, i}) } } } diff --git a/go.mod b/go.mod index 42a7e5f1c7..aa10ee7b69 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/tinygo-org/tinygo go 1.18 require ( - github.com/aykevl/go-wasm v0.0.2-0.20220616010729-4a0a888aebdc + github.com/aykevl/go-wasm v0.0.2-0.20240312204833-50275154210c github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 github.com/chromedp/cdproto v0.0.0-20220113222801-0725d94bb6ee github.com/chromedp/chromedp v0.7.6 diff --git a/go.sum b/go.sum index 28bd6e3387..1fe5d75765 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/aykevl/go-wasm v0.0.2-0.20220616010729-4a0a888aebdc h1:Yp49g+qqgQRPk/gcRSmAsXgnT16XPJ6Y5JM1poc6gYM= -github.com/aykevl/go-wasm v0.0.2-0.20220616010729-4a0a888aebdc/go.mod h1:7sXyiaA0WtSogCu67R2252fQpVmJMh9JWJ9ddtGkpWw= +github.com/aykevl/go-wasm v0.0.2-0.20240312204833-50275154210c h1:4T0Vj1UkGgcpkRrmn7SbokebnlfxJcMZPgWtOYACAAA= +github.com/aykevl/go-wasm v0.0.2-0.20240312204833-50275154210c/go.mod h1:7sXyiaA0WtSogCu67R2252fQpVmJMh9JWJ9ddtGkpWw= github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI= github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/chromedp/cdproto v0.0.0-20211126220118-81fa0469ad77/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U= From d67ec3b2668cd8fcbf0bc8a9c415f4e29f3b5081 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 20 Mar 2024 22:50:41 +0100 Subject: [PATCH 003/444] goenv: put back @sago35 update to new v0.32.0 development version Signed-off-by: deadprogram --- goenv/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/goenv/version.go b/goenv/version.go index e9de723b12..f00b4b95d2 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -9,7 +9,7 @@ import ( // Version of TinyGo. // Update this value before release of new version of software. -const version = "0.31.2" +const version = "0.32.0-dev" var ( // This variable is set at build time using -ldflags parameters. From 6714974ababa90cfb611d93ac00dfc7dac832860 Mon Sep 17 00:00:00 2001 From: Anders Savill Date: Mon, 18 Mar 2024 10:12:39 +0800 Subject: [PATCH 004/444] feather-nrf52840-sense doesn't use LFXO --- src/machine/board_feather-nrf52840-sense.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/machine/board_feather-nrf52840-sense.go b/src/machine/board_feather-nrf52840-sense.go index 2a78450818..ab1c3fbe1b 100644 --- a/src/machine/board_feather-nrf52840-sense.go +++ b/src/machine/board_feather-nrf52840-sense.go @@ -2,7 +2,7 @@ package machine -const HasLowFrequencyCrystal = true +const HasLowFrequencyCrystal = false // GPIO Pins const ( From 8a93196f1fa8d193e280948e972b725e66acb666 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 13 Mar 2024 14:37:16 +0100 Subject: [PATCH 005/444] Makefile: allow overriding the packages to test in `make test` This way you can for example run `make test GOTESTPKGS=./builder` to only test the builder package. I've often done this by manually modifying the Makefile, so having a make parameter available would make this much easier. --- GNUmakefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/GNUmakefile b/GNUmakefile index cc3367060f..f3495b49da 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -31,6 +31,7 @@ export GOROOT = $(shell $(GO) env GOROOT) # Flags to pass to go test. GOTESTFLAGS ?= +GOTESTPKGS ?= ./builder ./cgo ./compileopts ./compiler ./interp ./transform . # tinygo binary for tests TINYGO ?= $(call detect,tinygo,tinygo $(CURDIR)/build/tinygo) @@ -283,7 +284,7 @@ tinygo: @if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " $(MAKE) llvm-source"; echo " $(MAKE) $(LLVM_BUILDDIR)"; exit 1; fi CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" -ldflags="-X github.com/tinygo-org/tinygo/goenv.GitSha1=`git rev-parse --short HEAD`" . test: wasi-libc check-nodejs-version - CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=20m -buildmode exe -tags "byollvm osusergo" ./builder ./cgo ./compileopts ./compiler ./interp ./transform . + CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=20m -buildmode exe -tags "byollvm osusergo" $(GOTESTPKGS) # Standard library packages that pass tests on darwin, linux, wasi, and windows, but take over a minute in wasi TEST_PACKAGES_SLOW = \ From a7ddb33bea6e4f02e964c6b3fb4eaab5f96db346 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20B=C3=B6cker?= Date: Thu, 21 Mar 2024 07:53:06 +0100 Subject: [PATCH 006/444] Add pca10059-s140v7 as a target --- targets/pca10059-s140v7.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 targets/pca10059-s140v7.json diff --git a/targets/pca10059-s140v7.json b/targets/pca10059-s140v7.json new file mode 100644 index 0000000000..ca302c3734 --- /dev/null +++ b/targets/pca10059-s140v7.json @@ -0,0 +1,3 @@ +{ + "inherits": ["pca10059", "nrf52840-s140v7"] +} From d97e4dbccf73b42819b1b0c0692490170e753212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20B=C3=B6cker?= Date: Fri, 22 Mar 2024 07:46:52 +0100 Subject: [PATCH 007/444] Add smoke test for pca10059-s140v7 --- GNUmakefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/GNUmakefile b/GNUmakefile index f3495b49da..43d1f9a6ed 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -599,6 +599,8 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10056-s140v7 examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=pca10059-s140v7 examples/blinky1 + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=reelboard-s140v7 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=wioterminal examples/blinky1 From d628e3e2cb6f767dc5367bc43fb14751a509404a Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 26 Feb 2024 14:44:52 +0100 Subject: [PATCH 008/444] ci: don't add --recursive when updating submodules It's not generally needed. It was added in https://github.com/tinygo-org/tinygo/pull/3958 to fix an issue with binaryen that has since been fixed in a different way, so we don't need the googletest dependency anymore. --- .circleci/config.yml | 6 +++--- .github/workflows/linux.yml | 4 ++-- BUILDING.md | 2 +- Dockerfile | 2 +- GNUmakefile | 4 ++-- flake.nix | 8 ++++---- hooks/post_checkout | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 11e58108f1..6827366a7b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ commands: steps: - run: name: "Pull submodules" - command: git submodule update --init --recursive + command: git submodule update --init llvm-source-linux: steps: - restore_cache: @@ -33,13 +33,13 @@ commands: steps: - restore_cache: keys: - - binaryen-linux-v2 + - binaryen-linux-v3 - run: name: "Build Binaryen" command: | make binaryen - save_cache: - key: binaryen-linux-v2 + key: binaryen-linux-v3 paths: - build/wasm-opt test-linux: diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 9b6c8d895f..978e5a1232 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -356,13 +356,13 @@ jobs: uses: actions/cache@v4 id: cache-binaryen with: - key: binaryen-linux-${{ matrix.goarch }}-v3 + key: binaryen-linux-${{ matrix.goarch }}-v4 path: build/wasm-opt - name: Build Binaryen if: steps.cache-binaryen.outputs.cache-hit != 'true' run: | sudo apt-get install --no-install-recommends ninja-build - git submodule update --init --recursive lib/binaryen + git submodule update --init lib/binaryen make CROSS=${{ matrix.toolchain }} binaryen - name: Install fpm run: | diff --git a/BUILDING.md b/BUILDING.md index efb49c7259..52e411ec53 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -85,7 +85,7 @@ Now that we have a working static build, it's time to make a release tarball: If you did not clone the repository with the `--recursive` option, you will get errors until you initialize the project submodules: - git submodule update --init --recursive + git submodule update --init The release tarball is stored in build/release.tar.gz, and can be extracted with the following command (for example in ~/lib): diff --git a/Dockerfile b/Dockerfile index d54719e250..eb99e0e952 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ COPY . /tinygo # build the compiler and tools RUN cd /tinygo/ && \ - git submodule update --init --recursive && \ + git submodule update --init && \ make gen-device -j4 && \ make build/release diff --git a/GNUmakefile b/GNUmakefile index 43d1f9a6ed..3e0549ec1c 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -191,7 +191,7 @@ gen-device: gen-device-stm32 endif gen-device-avr: - @if [ ! -e lib/avr/README.md ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init --recursive"; exit 1; fi + @if [ ! -e lib/avr/README.md ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi $(GO) build -o ./build/gen-device-avr ./tools/gen-device-avr/ ./build/gen-device-avr lib/avr/packs/atmega src/device/avr/ ./build/gen-device-avr lib/avr/packs/tiny src/device/avr/ @@ -265,7 +265,7 @@ endif .PHONY: wasi-libc wasi-libc: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a: - @if [ ! -e lib/wasi-libc/Makefile ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init --recursive"; exit 1; fi + @if [ ! -e lib/wasi-libc/Makefile ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi cd lib/wasi-libc && $(MAKE) -j4 EXTRA_CFLAGS="-O2 -g -DNDEBUG -mnontrapping-fptoint -msign-ext" MALLOC_IMPL=none CC="$(CLANG)" AR=$(LLVM_AR) NM=$(LLVM_NM) # Check for Node.js used during WASM tests. diff --git a/flake.nix b/flake.nix index b98612d328..52bac72d11 100644 --- a/flake.nix +++ b/flake.nix @@ -18,10 +18,10 @@ # # But you'll need a bit more to make TinyGo actually able to compile code: # -# make llvm-source # fetch compiler-rt -# git submodule update --init --recursive # fetch lots of other libraries and SVD files -# make gen-device -j4 # build src/device/*/*.go files -# make wasi-libc # build support for wasi/wasm +# make llvm-source # fetch compiler-rt +# git submodule update --init # fetch lots of other libraries and SVD files +# make gen-device -j4 # build src/device/*/*.go files +# make wasi-libc # build support for wasi/wasm # # With this, you should have an environment that can compile anything - except # for the Xtensa architecture (ESP8266/ESP32) because support for that lives in diff --git a/hooks/post_checkout b/hooks/post_checkout index 71fa8f796d..1c6f495cf0 100755 --- a/hooks/post_checkout +++ b/hooks/post_checkout @@ -1,4 +1,4 @@ #!/bin/bash # Docker hub does a recursive clone, then checks the branch out, # so when a PR adds a submodule (or updates it), it fails. -git submodule update --init --recursive +git submodule update --init From 7c546525ea979025c17973a6e248a2119024acbb Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 4 Mar 2024 20:27:05 +0100 Subject: [PATCH 009/444] wasm-unknown: add math and memory builtins that LLVM needs This new library is needed for wasm targets that aren't WASI and don't need/want a libc, but still need some intrinsics that are generated by LLVM. --- GNUmakefile | 14 ++++++- builder/build.go | 7 ++++ builder/wasmbuiltins.go | 79 +++++++++++++++++++++++++++++++++++++++ compileopts/config.go | 2 + targets/wasm-unknown.json | 1 + 5 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 builder/wasmbuiltins.go diff --git a/GNUmakefile b/GNUmakefile index 3e0549ec1c..96d8e0deb3 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -821,7 +821,9 @@ build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN @mkdir -p build/release/tinygo/lib/nrfx @mkdir -p build/release/tinygo/lib/picolibc/newlib/libc @mkdir -p build/release/tinygo/lib/picolibc/newlib/libm - @mkdir -p build/release/tinygo/lib/wasi-libc + @mkdir -p build/release/tinygo/lib/wasi-libc/libc-bottom-half/headers + @mkdir -p build/release/tinygo/lib/wasi-libc/libc-top-half/musl/arch + @mkdir -p build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @mkdir -p build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0 @mkdir -p build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus @mkdir -p build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4 @@ -872,7 +874,15 @@ endif @cp -rp lib/picolibc/newlib/libm/common build/release/tinygo/lib/picolibc/newlib/libm @cp -rp lib/picolibc/newlib/libm/math build/release/tinygo/lib/picolibc/newlib/libm @cp -rp lib/picolibc-stdio.c build/release/tinygo/lib - @cp -rp lib/wasi-libc/sysroot build/release/tinygo/lib/wasi-libc/sysroot + @cp -rp lib/wasi-libc/libc-bottom-half/headers/public build/release/tinygo/lib/wasi-libc/libc-bottom-half/headers + @cp -rp lib/wasi-libc/libc-top-half/musl/arch/generic build/release/tinygo/lib/wasi-libc/libc-top-half/musl/arch + @cp -rp lib/wasi-libc/libc-top-half/musl/arch/wasm32 build/release/tinygo/lib/wasi-libc/libc-top-half/musl/arch + @cp -rp lib/wasi-libc/libc-top-half/musl/src/include build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src + @cp -rp lib/wasi-libc/libc-top-half/musl/src/internal build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src + @cp -rp lib/wasi-libc/libc-top-half/musl/src/math build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src + @cp -rp lib/wasi-libc/libc-top-half/musl/src/string build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src + @cp -rp lib/wasi-libc/libc-top-half/musl/include build/release/tinygo/lib/wasi-libc/libc-top-half/musl + @cp -rp lib/wasi-libc/sysroot build/release/tinygo/lib/wasi-libc/sysroot @cp -rp llvm-project/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt-builtins @cp -rp llvm-project/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt-builtins @cp -rp src build/release/tinygo/src diff --git a/builder/build.go b/builder/build.go index 7b7cc4728c..5a6683ae6f 100644 --- a/builder/build.go +++ b/builder/build.go @@ -168,6 +168,13 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe return BuildResult{}, errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?") } libcDependencies = append(libcDependencies, dummyCompileJob(path)) + case "wasmbuiltins": + libcJob, unlock, err := WasmBuiltins.load(config, tmpdir) + if err != nil { + return BuildResult{}, err + } + defer unlock() + libcDependencies = append(libcDependencies, libcJob) case "mingw-w64": _, unlock, err := MinGW.load(config, tmpdir) if err != nil { diff --git a/builder/wasmbuiltins.go b/builder/wasmbuiltins.go new file mode 100644 index 0000000000..06366bf953 --- /dev/null +++ b/builder/wasmbuiltins.go @@ -0,0 +1,79 @@ +package builder + +import ( + "os" + "path/filepath" + + "github.com/tinygo-org/tinygo/goenv" +) + +var WasmBuiltins = Library{ + name: "wasmbuiltins", + makeHeaders: func(target, includeDir string) error { + os.Mkdir(includeDir+"/bits", 0o777) + f, err := os.Create(includeDir + "/bits/alltypes.h") + if err != nil { + return err + } + if _, err := f.Write([]byte(wasmAllTypes)); err != nil { + return err + } + return f.Close() + }, + cflags: func(target, headerPath string) []string { + libcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") + return []string{ + "-Werror", + "-Wall", + "-std=gnu11", + "-nostdlibinc", + "-isystem", libcDir + "/libc-top-half/musl/arch/wasm32", + "-isystem", libcDir + "/libc-top-half/musl/arch/generic", + "-isystem", libcDir + "/libc-top-half/musl/src/internal", + "-isystem", libcDir + "/libc-top-half/musl/src/include", + "-isystem", libcDir + "/libc-top-half/musl/include", + "-isystem", libcDir + "/libc-bottom-half/headers/public", + "-I" + headerPath, + } + }, + sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") }, + librarySources: func(target string) ([]string, error) { + return []string{ + // memory builtins needed for llvm.memcpy.*, llvm.memmove.*, and + // llvm.memset.* LLVM intrinsics. + "libc-top-half/musl/src/string/memcpy.c", + "libc-top-half/musl/src/string/memmove.c", + "libc-top-half/musl/src/string/memset.c", + + // exp, exp2, and log are needed for LLVM math builtin functions + // like llvm.exp.*. + "libc-top-half/musl/src/math/__math_divzero.c", + "libc-top-half/musl/src/math/__math_invalid.c", + "libc-top-half/musl/src/math/__math_oflow.c", + "libc-top-half/musl/src/math/__math_uflow.c", + "libc-top-half/musl/src/math/__math_xflow.c", + "libc-top-half/musl/src/math/exp.c", + "libc-top-half/musl/src/math/exp_data.c", + "libc-top-half/musl/src/math/exp2.c", + "libc-top-half/musl/src/math/log.c", + "libc-top-half/musl/src/math/log_data.c", + }, nil + }, +} + +// alltypes.h for wasm-libc, using the types as defined inside Clang. +const wasmAllTypes = ` +typedef __SIZE_TYPE__ size_t; +typedef __INT8_TYPE__ int8_t; +typedef __INT16_TYPE__ int16_t; +typedef __INT32_TYPE__ int32_t; +typedef __INT64_TYPE__ int64_t; +typedef __UINT8_TYPE__ uint8_t; +typedef __UINT16_TYPE__ uint16_t; +typedef __UINT32_TYPE__ uint32_t; +typedef __UINT64_TYPE__ uint64_t; +typedef __UINTPTR_TYPE__ uintptr_t; + +// This type is used internally in wasi-libc. +typedef double double_t; +` diff --git a/compileopts/config.go b/compileopts/config.go index 9349738a4a..24da4e280d 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -311,6 +311,8 @@ func (c *Config) CFlags(libclang bool) []string { case "wasi-libc": root := goenv.Get("TINYGOROOT") cflags = append(cflags, "--sysroot="+root+"/lib/wasi-libc/sysroot") + case "wasmbuiltins": + // nothing to add (library is purely for builtins) case "mingw-w64": root := goenv.Get("TINYGOROOT") path, _ := c.LibcPath("mingw-w64") diff --git a/targets/wasm-unknown.json b/targets/wasm-unknown.json index 903afa49e8..a3f6313c19 100644 --- a/targets/wasm-unknown.json +++ b/targets/wasm-unknown.json @@ -7,6 +7,7 @@ "goarch": "arm", "linker": "wasm-ld", "rtlib": "compiler-rt", + "libc": "wasmbuiltins", "scheduler": "none", "gc": "leaking", "default-stack-size": 4096, From a5ceb793be150f3bef1cb7bb9a3faa63a3a65d90 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Tue, 26 Mar 2024 10:43:46 +0100 Subject: [PATCH 010/444] builder: add check for error on creating needed directory as suggested by @b0ch3nski Signed-off-by: deadprogram --- builder/wasmbuiltins.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/builder/wasmbuiltins.go b/builder/wasmbuiltins.go index 06366bf953..bcd92f3ece 100644 --- a/builder/wasmbuiltins.go +++ b/builder/wasmbuiltins.go @@ -10,7 +10,9 @@ import ( var WasmBuiltins = Library{ name: "wasmbuiltins", makeHeaders: func(target, includeDir string) error { - os.Mkdir(includeDir+"/bits", 0o777) + if err := os.Mkdir(includeDir+"/bits", 0o777); err != nil { + return err + } f, err := os.Create(includeDir + "/bits/alltypes.h") if err != nil { return err From b6fdbee14e79bacc091ab805a169c551f140f33a Mon Sep 17 00:00:00 2001 From: Patrick Lindsay Date: Wed, 13 Mar 2024 21:29:55 -0500 Subject: [PATCH 011/444] Begin implementing support for Adafruit ESP32 Feather V2 --- .../board_adafruit-esp32-feather-v2.go | 41 +++++++++++++++++++ targets/adafruit-esp32-feather-v2.json | 4 ++ 2 files changed, 45 insertions(+) create mode 100644 src/machine/board_adafruit-esp32-feather-v2.go create mode 100644 targets/adafruit-esp32-feather-v2.json diff --git a/src/machine/board_adafruit-esp32-feather-v2.go b/src/machine/board_adafruit-esp32-feather-v2.go new file mode 100644 index 0000000000..2802265c7b --- /dev/null +++ b/src/machine/board_adafruit-esp32-feather-v2.go @@ -0,0 +1,41 @@ +//go:build adafruit_esp32_feather_v2 + +package machine + +const GPIO20 Pin = 20 + +const ( + IO0 = GPIO0 + IO2 = GPIO2 + IO4 = GPIO4 + IO5 = GPIO5 + IO7 = GPIO7 + IO8 = GPIO8 + IO12 = GPIO12 + IO13 = GPIO13 + IO14 = GPIO14 + IO15 = GPIO15 + IO19 = GPIO19 + IO20 = GPIO20 + IO21 = GPIO21 + IO22 = GPIO22 + IO25 = GPIO25 + IO26 = GPIO26 + IO27 = GPIO27 + IO32 = GPIO32 + IO33 = GPIO33 + IO34 = GPIO34 + IO35 = GPIO35 + IO36 = GPIO36 + IO37 = GPIO37 + IO38 = GPIO38 + IO39 = GPIO39 +) + +// Built-in LEDs and Button +const ( + NEOPIXEL = IO0 + NEOPIXEL_I2C_POWER = IO2 + LED = IO13 + BUTTON = IO38 +) diff --git a/targets/adafruit-esp32-feather-v2.json b/targets/adafruit-esp32-feather-v2.json new file mode 100644 index 0000000000..9db914dbde --- /dev/null +++ b/targets/adafruit-esp32-feather-v2.json @@ -0,0 +1,4 @@ +{ + "inherits": ["esp32"], + "build-tags": ["adafruit_esp32_feather_v2"] +} From 62d8cdb218ff4ff37998ad91f9f9e9e146f70271 Mon Sep 17 00:00:00 2001 From: Patrick Lindsay Date: Fri, 15 Mar 2024 21:48:46 -0500 Subject: [PATCH 012/444] Map the rest of the pinout --- .../board_adafruit-esp32-feather-v2.go | 86 ++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/src/machine/board_adafruit-esp32-feather-v2.go b/src/machine/board_adafruit-esp32-feather-v2.go index 2802265c7b..f971dcad85 100644 --- a/src/machine/board_adafruit-esp32-feather-v2.go +++ b/src/machine/board_adafruit-esp32-feather-v2.go @@ -32,10 +32,94 @@ const ( IO39 = GPIO39 ) +// Digital pins +const ( + D12 = IO12 + D13 = IO13 + D14 = IO14 + D15 = IO15 + D27 = IO27 + D32 = IO32 + D33 = IO33 + D37 = IO37 +) + +// Analog pins +const ( + A0 = IO26 + A1 = IO25 + A2 = IO34 + A3 = IO39 + A4 = IO36 + A5 = IO4 +) + // Built-in LEDs and Button const ( - NEOPIXEL = IO0 + WS2812 = IO0 + NEOPIXEL = WS2812 NEOPIXEL_I2C_POWER = IO2 LED = IO13 BUTTON = IO38 ) + +// SPI pins +const ( + SPI_SCK_PIN = IO5 + SPI_MOSI_PIN = IO19 + SPI_MISO_PIN = IO21 + + SPI_SDO_PIN = SPI_MOSI_PIN + SPI_SDI_PIN = SPI_MISO_PIN + + // Silk labels + SCK = SPI_SCK_PIN + MO = SPI_MOSI_PIN + MI = SPI_MISO_PIN +) + +// I2C pins +const ( + I2C_SCL_PIN = IO20 + I2C_SDA_PIN = IO22 + + // Silk labels + SCL = I2C_SCL_PIN + SDA = I2C_SDA_PIN +) + +// ADC pins +const ( + ADC1_0 = IO36 + ADC1_1 = IO37 + ADC1_2 = IO38 + ADC1_3 = IO39 + ADC1_4 = IO32 + ADC1_5 = IO33 + ADC1_6 = IO34 + ADC1_7 = IO35 + + ADC2_0 = IO4 + ADC2_1 = IO0 + ADC2_2 = IO2 + ADC2_3 = IO15 + ADC2_4 = IO13 + ADC2_5 = IO12 + ADC2_6 = IO14 + ADC2_7 = IO27 + ADC2_8 = IO25 + ADC2_9 = IO26 +) + +// UART pins +const ( + UART_TX_PIN = IO19 + UART_RX_PIN = IO22 + + UART2_TX_PIN = IO8 + UART2_RX_PIN = IO7 + + // Silk labels + RX = UART2_RX_PIN + TX = UART2_TX_PIN +) From 8e8ad9004f165716e64676ec534def097594484a Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Mon, 4 Mar 2024 10:26:41 -0800 Subject: [PATCH 013/444] all: replace target=wasi with target=wasip1 This eliminates the 'wasi' build tag in favor of 'GOOS=wasip1', introduced in Go 1.21. For backwards compatablity, -target=wasi is a synonym for -target=wasip1. --- GNUmakefile | 10 +++++----- README.md | 6 +++--- main.go | 7 ++++++- src/crypto/rand/rand_urandom.go | 2 +- src/os/dir_test.go | 2 +- src/os/dir_unix.go | 2 +- src/os/{dir_wasi.go => dir_wasip1.go} | 4 ++-- src/os/dirent_linux.go | 2 +- src/os/file_other.go | 2 +- src/os/is_wasi_no_test.go | 2 +- src/os/is_wasi_test.go | 2 +- src/os/os_chmod_test.go | 2 +- src/os/os_symlink_test.go | 2 +- src/os/read_test.go | 2 +- src/os/removeall_noat.go | 2 +- src/os/stat_other.go | 2 +- src/os/tempfile_test.go | 2 +- src/runtime/memhash_fnv.go | 2 +- src/runtime/memhash_leveldb.go | 2 +- src/runtime/os_linux.go | 2 +- src/runtime/runtime_unix.go | 2 +- .../{runtime_wasm_wasi.go => runtime_wasip1.go} | 2 +- src/runtime/runtime_wasm_js.go | 2 +- src/syscall/errno_other.go | 2 +- .../{syscall_libc_wasi.go => syscall_libc_wasip1.go} | 12 ++++++++++-- src/testing/is_wasi_no_test.go | 2 +- src/testing/is_wasi_test.go | 2 +- targets/{wasi.json => wasip1.json} | 6 +++--- 28 files changed, 51 insertions(+), 38 deletions(-) rename src/os/{dir_wasi.go => dir_wasip1.go} (98%) rename src/runtime/{runtime_wasm_wasi.go => runtime_wasip1.go} (98%) rename src/syscall/{syscall_libc_wasi.go => syscall_libc_wasip1.go} (97%) rename targets/{wasi.json => wasip1.json} (85%) diff --git a/GNUmakefile b/GNUmakefile index 96d8e0deb3..2d7da5c590 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -427,17 +427,17 @@ tinygo-bench-fast: # Same thing, except for wasi rather than the current platform. tinygo-test-wasi: - $(TINYGO) test -target wasi $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi + $(TINYGO) test -target wasip1 $(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 tinygo-test-wasi-fast: - $(TINYGO) test -target wasi $(TEST_PACKAGES_FAST) ./tests/runtime_wasi + $(TINYGO) test -target wasip1 $(TEST_PACKAGES_FAST) ./tests/runtime_wasi tinygo-test-wasip1-fast: GOOS=wasip1 GOARCH=wasm $(TINYGO) test $(TEST_PACKAGES_FAST) ./tests/runtime_wasi tinygo-bench-wasi: - $(TINYGO) test -target wasi -bench . $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) + $(TINYGO) test -target wasip1 -bench . $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) tinygo-bench-wasi-fast: - $(TINYGO) test -target wasi -bench . $(TEST_PACKAGES_FAST) + $(TINYGO) test -target wasip1 -bench . $(TEST_PACKAGES_FAST) # Test external packages in a large corpus. test-corpus: @@ -445,7 +445,7 @@ test-corpus: test-corpus-fast: CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus -short . -corpus=testdata/corpus.yaml test-corpus-wasi: wasi-libc - CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasi + CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasip1 tinygo-baremetal: # Regression tests that run on a baremetal target and don't fit in either main_test.go or smoketest. diff --git a/README.md b/README.md index d470c47394..e03b749bb9 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ tinygo flash -target arduino examples/blinky1 TinyGo is very useful for compiling programs both for use in browsers (WASM) as well as for use on servers and other edge devices (WASI). -TinyGo programs can run in Fastly Compute@Edge (https://developer.fastly.com/learning/compute/go/), Fermyon Spin (https://developer.fermyon.com/spin/go-components), wazero (https://wazero.io/languages/tinygo/) and many other WebAssembly runtimes. +TinyGo programs can run in [Fastly Compute](https://www.fastly.com/documentation/guides/compute/go/), [Fermyon Spin](https://developer.fermyon.com/spin/go-components), [wazero](https://wazero.io/languages/tinygo/) and many other WebAssembly runtimes. Here is a small TinyGo program for use by a WASI host application: @@ -54,14 +54,14 @@ func add(x, y uint32) uint32 { return x + y } -// main is required for the `wasi` target, even if it isn't used. +// main is required for the `wasip1` target, even if it isn't used. func main() {} ``` This compiles the above TinyGo program for use on any WASI runtime: ```shell -tinygo build -o main.wasm -target=wasi main.go +tinygo build -o main.wasm -target=wasip1 main.go ``` ## Installation diff --git a/main.go b/main.go index 8c0117fa19..16e6dbf60a 100644 --- a/main.go +++ b/main.go @@ -285,7 +285,7 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options // Tests are always run in the package directory. cmd.Dir = result.MainDir - // wasmtime is the default emulator used for `-target=wasi`. wasmtime + // wasmtime is the default emulator used for `-target=wasip1`. wasmtime // is a WebAssembly runtime CLI with WASI enabled by default. However, // only stdio are allowed by default. For example, while STDOUT routes // to the host, other files don't. It also does not inherit environment @@ -1548,6 +1548,11 @@ func main() { options.PrintCommands = printCommand } + // Compatibility with legacy -target=wasi + if options.Target == "wasi" { + options.Target = "wasip1" + } + err = options.Verify() if err != nil { fmt.Fprintln(os.Stderr, err.Error()) diff --git a/src/crypto/rand/rand_urandom.go b/src/crypto/rand/rand_urandom.go index 2a55a0ea5e..e9a8d485e9 100644 --- a/src/crypto/rand/rand_urandom.go +++ b/src/crypto/rand/rand_urandom.go @@ -1,4 +1,4 @@ -//go:build linux && !baremetal && !wasi +//go:build linux && !baremetal && !wasip1 // This implementation of crypto/rand uses the /dev/urandom pseudo-file to // generate random numbers. diff --git a/src/os/dir_test.go b/src/os/dir_test.go index c09067df1c..d661c98b44 100644 --- a/src/os/dir_test.go +++ b/src/os/dir_test.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal && !js && !wasi && !386 && !arm) +//go:build darwin || (linux && !baremetal && !js && !wasip1 && !386 && !arm) package os_test diff --git a/src/os/dir_unix.go b/src/os/dir_unix.go index baacdd68dc..a531e0a639 100644 --- a/src/os/dir_unix.go +++ b/src/os/dir_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build linux && !baremetal && !wasi && !wasip1 +//go:build linux && !baremetal && !wasip1 package os diff --git a/src/os/dir_wasi.go b/src/os/dir_wasip1.go similarity index 98% rename from src/os/dir_wasi.go rename to src/os/dir_wasip1.go index 23be3950ee..0be0da4d64 100644 --- a/src/os/dir_wasi.go +++ b/src/os/dir_wasip1.go @@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// This file was derived from src/os/dir_darwin.go since the logic for wasi is +// This file was derived from src/os/dir_darwin.go since the logic for WASI is // fairly similar: we use fdopendir, fdclosedir, and readdir from wasi-libc in // a similar way that the darwin code uses functions from libc. -//go:build wasi || wasip1 +//go:build wasip1 package os diff --git a/src/os/dirent_linux.go b/src/os/dirent_linux.go index 2527182fb6..90f7086db8 100644 --- a/src/os/dirent_linux.go +++ b/src/os/dirent_linux.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !js && !wasi +//go:build !baremetal && !js && !wasip1 // Copyright 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/file_other.go b/src/os/file_other.go index 0ceee0020b..e7fabddcaf 100644 --- a/src/os/file_other.go +++ b/src/os/file_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || (wasm && !wasi && !wasip1) +//go:build baremetal || (wasm && !wasip1) package os diff --git a/src/os/is_wasi_no_test.go b/src/os/is_wasi_no_test.go index aa7745085b..f98070cdc7 100644 --- a/src/os/is_wasi_no_test.go +++ b/src/os/is_wasi_no_test.go @@ -1,4 +1,4 @@ -//go:build !wasi && !wasip1 +//go:build !wasip1 package os_test diff --git a/src/os/is_wasi_test.go b/src/os/is_wasi_test.go index 619b1cb64f..1e9ad898c9 100644 --- a/src/os/is_wasi_test.go +++ b/src/os/is_wasi_test.go @@ -1,4 +1,4 @@ -//go:build wasi || wasip1 +//go:build wasip1 package os_test diff --git a/src/os/os_chmod_test.go b/src/os/os_chmod_test.go index 911438d954..b8f7d354f4 100644 --- a/src/os/os_chmod_test.go +++ b/src/os/os_chmod_test.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !js && !wasi && !wasip1 +//go:build !baremetal && !js && !wasip1 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/os_symlink_test.go b/src/os/os_symlink_test.go index f252116f5a..f885dd32c4 100644 --- a/src/os/os_symlink_test.go +++ b/src/os/os_symlink_test.go @@ -1,4 +1,4 @@ -//go:build !windows && !baremetal && !js && !wasi && !wasip1 +//go:build !windows && !baremetal && !js && !wasip1 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/read_test.go b/src/os/read_test.go index e037b23498..679d961132 100644 --- a/src/os/read_test.go +++ b/src/os/read_test.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !js && !wasi && !wasip1 +//go:build !baremetal && !js && !wasip1 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/removeall_noat.go b/src/os/removeall_noat.go index ae945c2497..8b03756e31 100644 --- a/src/os/removeall_noat.go +++ b/src/os/removeall_noat.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !baremetal && !js && !wasi && !wasip1 +//go:build !baremetal && !js && !wasip1 package os diff --git a/src/os/stat_other.go b/src/os/stat_other.go index ff1bc37745..7340fbb574 100644 --- a/src/os/stat_other.go +++ b/src/os/stat_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || (wasm && !wasi && !wasip1) +//go:build baremetal || (wasm && !wasip1) // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/tempfile_test.go b/src/os/tempfile_test.go index 4b7416f4e0..cf3fd46d70 100644 --- a/src/os/tempfile_test.go +++ b/src/os/tempfile_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !baremetal && !js && !wasi && !wasip1 +//go:build !baremetal && !js && !wasip1 package os_test diff --git a/src/runtime/memhash_fnv.go b/src/runtime/memhash_fnv.go index 4d82971d0b..69802e0535 100644 --- a/src/runtime/memhash_fnv.go +++ b/src/runtime/memhash_fnv.go @@ -1,4 +1,4 @@ -//go:build (!wasi && !runtime_memhash_tsip && !runtime_memhash_leveldb) || (wasi && runtime_memhash_fnv) +//go:build (!wasip1 && !runtime_memhash_tsip && !runtime_memhash_leveldb) || (wasip1 && runtime_memhash_fnv) // This is the default for all targets except WASI, unless a more specific build // tag is set. diff --git a/src/runtime/memhash_leveldb.go b/src/runtime/memhash_leveldb.go index 22ff829e41..f57c9cf93e 100644 --- a/src/runtime/memhash_leveldb.go +++ b/src/runtime/memhash_leveldb.go @@ -1,4 +1,4 @@ -//go:build runtime_memhash_leveldb || (wasi && !runtime_memhash_fnv && !runtime_memhash_tsip) +//go:build runtime_memhash_leveldb || (wasip1 && !runtime_memhash_fnv && !runtime_memhash_tsip) // This is the default for WASI, but can also be used on other targets with the // right build tag. diff --git a/src/runtime/os_linux.go b/src/runtime/os_linux.go index 3f27dfd16c..9780e54018 100644 --- a/src/runtime/os_linux.go +++ b/src/runtime/os_linux.go @@ -1,4 +1,4 @@ -//go:build linux && !baremetal && !nintendoswitch && !wasi && !wasm_unknown +//go:build linux && !baremetal && !nintendoswitch && !wasip1 && !wasm_unknown package runtime diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index f1f1c4df70..323c8909ac 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -1,4 +1,4 @@ -//go:build (darwin || (linux && !baremetal && !wasi && !wasm_unknown)) && !nintendoswitch +//go:build (darwin || (linux && !baremetal && !wasip1 && !wasm_unknown)) && !nintendoswitch package runtime diff --git a/src/runtime/runtime_wasm_wasi.go b/src/runtime/runtime_wasip1.go similarity index 98% rename from src/runtime/runtime_wasm_wasi.go rename to src/runtime/runtime_wasip1.go index f258039ae6..595cab9bf0 100644 --- a/src/runtime/runtime_wasm_wasi.go +++ b/src/runtime/runtime_wasip1.go @@ -1,4 +1,4 @@ -//go:build tinygo.wasm && (wasi || wasip1) +//go:build wasip1 package runtime diff --git a/src/runtime/runtime_wasm_js.go b/src/runtime/runtime_wasm_js.go index 18ca44abec..96e923c89b 100644 --- a/src/runtime/runtime_wasm_js.go +++ b/src/runtime/runtime_wasm_js.go @@ -1,4 +1,4 @@ -//go:build wasm && !wasi && !wasip1 +//go:build wasm && !wasip1 package runtime diff --git a/src/syscall/errno_other.go b/src/syscall/errno_other.go index a001096525..839b5f4357 100644 --- a/src/syscall/errno_other.go +++ b/src/syscall/errno_other.go @@ -1,4 +1,4 @@ -//go:build !wasi && !wasip1 && !darwin +//go:build !wasip1 && !darwin package syscall diff --git a/src/syscall/syscall_libc_wasi.go b/src/syscall/syscall_libc_wasip1.go similarity index 97% rename from src/syscall/syscall_libc_wasi.go rename to src/syscall/syscall_libc_wasip1.go index 29d79b50c1..eff7d5a5f9 100644 --- a/src/syscall/syscall_libc_wasi.go +++ b/src/syscall/syscall_libc_wasip1.go @@ -1,4 +1,4 @@ -//go:build wasi || wasip1 +//go:build wasip1 package syscall @@ -397,8 +397,16 @@ func Chmod(path string, mode uint32) (err error) { return Lstat(path, &stat) } +// wasmPageSize is the size of a page in WebAssembly's 32-bit memory. This +// is also its only unit of change. +// +// See https://www.w3.org/TR/wasm-core-1/#page-size +// +// FIXME: this is also defined in package runtime. +const wasmPageSize = 64 * 1024 + func Getpagesize() int { - return libc_getpagesize() + return wasmPageSize } type Utsname struct { diff --git a/src/testing/is_wasi_no_test.go b/src/testing/is_wasi_no_test.go index 630467ec0b..08b6d56c5b 100644 --- a/src/testing/is_wasi_no_test.go +++ b/src/testing/is_wasi_no_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // -//go:build !wasi && !wasip1 +//go:build !wasip1 package testing_test diff --git a/src/testing/is_wasi_test.go b/src/testing/is_wasi_test.go index e20e15fc04..d39b6c867f 100644 --- a/src/testing/is_wasi_test.go +++ b/src/testing/is_wasi_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // -//go:build wasi || wasip1 +//go:build wasip1 package testing_test diff --git a/targets/wasi.json b/targets/wasip1.json similarity index 85% rename from targets/wasi.json rename to targets/wasip1.json index 1e1fff415f..409be9ec93 100644 --- a/targets/wasi.json +++ b/targets/wasip1.json @@ -2,9 +2,9 @@ "llvm-target": "wasm32-unknown-wasi", "cpu": "generic", "features": "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext", - "build-tags": ["tinygo.wasm", "wasi"], - "goos": "linux", - "goarch": "arm", + "build-tags": ["tinygo.wasm"], + "goos": "wasip1", + "goarch": "wasm", "linker": "wasm-ld", "libc": "wasi-libc", "rtlib": "compiler-rt", From 32a056c32a38729c96c47aeaa5bf69cd7209a7bf Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Mon, 4 Mar 2024 10:31:13 -0800 Subject: [PATCH 014/444] syscall: use libc_getpagesize instead of hard-coded constant TODO: should Getpagesize on wasip1 just return wasmPageSize? --- src/syscall/syscall_libc_wasip1.go | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/syscall/syscall_libc_wasip1.go b/src/syscall/syscall_libc_wasip1.go index eff7d5a5f9..5242690cc4 100644 --- a/src/syscall/syscall_libc_wasip1.go +++ b/src/syscall/syscall_libc_wasip1.go @@ -397,16 +397,9 @@ func Chmod(path string, mode uint32) (err error) { return Lstat(path, &stat) } -// wasmPageSize is the size of a page in WebAssembly's 32-bit memory. This -// is also its only unit of change. -// -// See https://www.w3.org/TR/wasm-core-1/#page-size -// -// FIXME: this is also defined in package runtime. -const wasmPageSize = 64 * 1024 - +// TODO: should this return runtime.wasmPageSize? func Getpagesize() int { - return wasmPageSize + return libc_getpagesize() } type Utsname struct { From 8bd0155c515c4863e4bbac87cf495df5b69bd976 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Mon, 4 Mar 2024 10:48:20 -0800 Subject: [PATCH 015/444] os, testing: just check runtime.GOOS == "wasip1" --- src/os/is_wasi_no_test.go | 5 ----- src/os/is_wasi_test.go | 5 ----- src/os/os_anyos_test.go | 4 ++-- src/testing/is_wasi_no_test.go | 9 --------- src/testing/is_wasi_test.go | 9 --------- src/testing/testing_test.go | 5 +++-- 6 files changed, 5 insertions(+), 32 deletions(-) delete mode 100644 src/os/is_wasi_no_test.go delete mode 100644 src/os/is_wasi_test.go delete mode 100644 src/testing/is_wasi_no_test.go delete mode 100644 src/testing/is_wasi_test.go diff --git a/src/os/is_wasi_no_test.go b/src/os/is_wasi_no_test.go deleted file mode 100644 index f98070cdc7..0000000000 --- a/src/os/is_wasi_no_test.go +++ /dev/null @@ -1,5 +0,0 @@ -//go:build !wasip1 - -package os_test - -const isWASI = false diff --git a/src/os/is_wasi_test.go b/src/os/is_wasi_test.go deleted file mode 100644 index 1e9ad898c9..0000000000 --- a/src/os/is_wasi_test.go +++ /dev/null @@ -1,5 +0,0 @@ -//go:build wasip1 - -package os_test - -const isWASI = true diff --git a/src/os/os_anyos_test.go b/src/os/os_anyos_test.go index 44606e163b..8a082d6522 100644 --- a/src/os/os_anyos_test.go +++ b/src/os/os_anyos_test.go @@ -275,7 +275,7 @@ func TestDirFS(t *testing.T) { t.Log("TODO: implement Readdir for Windows") return } - if isWASI { + if runtime.GOOS == "wasip1" { t.Log("TODO: allow foo/bar/. as synonym for path foo/bar on wasi?") return } @@ -296,7 +296,7 @@ func TestDirFSPathsValid(t *testing.T) { t.Log("skipping on Windows") return } - if isWASI { + if runtime.GOOS == "wasip1" { t.Log("skipping on wasi because it fails on wasi on windows") return } diff --git a/src/testing/is_wasi_no_test.go b/src/testing/is_wasi_no_test.go deleted file mode 100644 index 08b6d56c5b..0000000000 --- a/src/testing/is_wasi_no_test.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. -// -//go:build !wasip1 - -package testing_test - -const isWASI = false diff --git a/src/testing/is_wasi_test.go b/src/testing/is_wasi_test.go deleted file mode 100644 index d39b6c867f..0000000000 --- a/src/testing/is_wasi_test.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. -// -//go:build wasip1 - -package testing_test - -const isWASI = true diff --git a/src/testing/testing_test.go b/src/testing/testing_test.go index 631a313414..9f5249b0c3 100644 --- a/src/testing/testing_test.go +++ b/src/testing/testing_test.go @@ -13,6 +13,7 @@ import ( "io/fs" "os" "path/filepath" + "runtime" "testing" ) @@ -25,7 +26,7 @@ func TestMain(m *testing.M) { } func TestTempDirInCleanup(t *testing.T) { - if isWASI { + if runtime.GOOS == "wasip1" { t.Log("Skipping. TODO: implement RemoveAll for wasi") return } @@ -62,7 +63,7 @@ func TestTempDirInBenchmark(t *testing.T) { } func TestTempDir(t *testing.T) { - if isWASI { + if runtime.GOOS == "wasip1" { t.Log("Skipping. TODO: implement RemoveAll for wasi") return } From cfcc894855b517b2c0788ea1a423c4ed9b3c8a69 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Tue, 5 Mar 2024 08:54:22 -0800 Subject: [PATCH 016/444] targets: add wasi.json that inherits wasip1.json PR feedback --- main.go | 5 ----- targets/wasi.json | 3 +++ 2 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 targets/wasi.json diff --git a/main.go b/main.go index 16e6dbf60a..b10f45cd73 100644 --- a/main.go +++ b/main.go @@ -1548,11 +1548,6 @@ func main() { options.PrintCommands = printCommand } - // Compatibility with legacy -target=wasi - if options.Target == "wasi" { - options.Target = "wasip1" - } - err = options.Verify() if err != nil { fmt.Fprintln(os.Stderr, err.Error()) diff --git a/targets/wasi.json b/targets/wasi.json new file mode 100644 index 0000000000..21d5694178 --- /dev/null +++ b/targets/wasi.json @@ -0,0 +1,3 @@ +{ + "inherits": ["wasip1"] +} From 055950421aa563c0b03e9904ce68785ec597133b Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Tue, 5 Mar 2024 09:19:31 -0800 Subject: [PATCH 017/444] all: change references of 'wasi' to 'wasip1'; test hygiene --- builder/builder_test.go | 2 +- corpus_test.go | 5 +++-- main_test.go | 13 ++++++++----- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/builder/builder_test.go b/builder/builder_test.go index 0ed632da3b..3bb3e98411 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -33,7 +33,7 @@ func TestClangAttributes(t *testing.T) { "k210", "nintendoswitch", "riscv-qemu", - "wasi", + "wasip1", "wasm", "wasm-unknown", } diff --git a/corpus_test.go b/corpus_test.go index 1b27d6f6b0..f17a9b9f50 100644 --- a/corpus_test.go +++ b/corpus_test.go @@ -51,6 +51,7 @@ func TestCorpus(t *testing.T) { if *testTarget != "" { target = *testTarget } + isWASI := strings.HasPrefix(target, "wasi") repos, err := loadRepos(*corpus) if err != nil { @@ -69,7 +70,7 @@ func TestCorpus(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - if target == "wasi" && repo.SkipWASI { + if isWASI && repo.SkipWASI { t.Skip("skip wasi") } if repo.Slow && testing.Short() { @@ -135,7 +136,7 @@ func TestCorpus(t *testing.T) { t.Run(dir.Pkg, func(t *testing.T) { t.Parallel() - if target == "wasi" && dir.SkipWASI { + if isWASI && dir.SkipWASI { t.Skip("skip wasi") } if dir.Slow && testing.Short() { diff --git a/main_test.go b/main_test.go index 9c28eb86d6..0f0d52f4d6 100644 --- a/main_test.go +++ b/main_test.go @@ -179,7 +179,7 @@ func TestBuild(t *testing.T) { }) t.Run("WASI", func(t *testing.T) { t.Parallel() - runPlatTests(optionsFromTarget("wasi", sema), tests, t) + runPlatTests(optionsFromTarget("wasip1", sema), tests, t) }) } } @@ -192,7 +192,10 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { t.Fatal("failed to load target spec:", err) } - isWebAssembly := options.Target == "wasi" || options.Target == "wasm" || (options.Target == "" && options.GOARCH == "wasm") + // FIXME: this should really be: + // isWebAssembly := strings.HasPrefix(spec.Triple, "wasm") + isWASI := strings.HasPrefix(options.Target, "wasi") + isWebAssembly := isWASI || strings.HasPrefix(options.Target, "wasm") || (options.Target == "" && strings.HasPrefix(options.GOARCH, "wasm")) for _, name := range tests { if options.GOOS == "linux" && (options.GOARCH == "arm" || options.GOARCH == "386") { @@ -252,13 +255,13 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { runTest("alias.go", options, t, nil, nil) }) } - if options.Target == "" || options.Target == "wasi" { + if options.Target == "" || isWASI { t.Run("filesystem.go", func(t *testing.T) { t.Parallel() runTest("filesystem.go", options, t, nil, nil) }) } - if options.Target == "" || options.Target == "wasi" || options.Target == "wasm" { + if options.Target == "" || options.Target == "wasm" || isWASI { t.Run("rand.go", func(t *testing.T) { t.Parallel() runTest("rand.go", options, t, nil, nil) @@ -492,7 +495,7 @@ func TestTest(t *testing.T) { // Node/Wasmtime targ{"WASM", optionsFromTarget("wasm", sema)}, - targ{"WASI", optionsFromTarget("wasi", sema)}, + targ{"WASI", optionsFromTarget("wasip1", sema)}, ) } for _, targ := range targs { From 331cfb65b772612ad65f69718400f1ee1fb07d63 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 28 Mar 2024 12:25:58 +0100 Subject: [PATCH 018/444] nix: fix wasi-libc include headers Apparently Nix really doesn't like --sysroot, so we have to use `-nostdlibinc` and `-isystem` instead. --- compileopts/config.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compileopts/config.go b/compileopts/config.go index 24da4e280d..e3f67b11a2 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -310,7 +310,9 @@ func (c *Config) CFlags(libclang bool) []string { ) case "wasi-libc": root := goenv.Get("TINYGOROOT") - cflags = append(cflags, "--sysroot="+root+"/lib/wasi-libc/sysroot") + cflags = append(cflags, + "-nostdlibinc", + "-isystem", root+"/lib/wasi-libc/sysroot/include") case "wasmbuiltins": // nothing to add (library is purely for builtins) case "mingw-w64": From 2733e376bdca4af9e47e90d6c27acbfd3b3e3636 Mon Sep 17 00:00:00 2001 From: leongross Date: Thu, 21 Mar 2024 16:48:52 +0100 Subject: [PATCH 019/444] fix square alias for ed25519 Signed-off-by: leongross --- compiler/alias.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/alias.go b/compiler/alias.go index b16cbce863..9490d84d9a 100644 --- a/compiler/alias.go +++ b/compiler/alias.go @@ -16,8 +16,8 @@ import "tinygo.org/x/go-llvm" var stdlibAliases = map[string]string{ // crypto packages - "crypto/ed25519/internal/edwards25519/field.feMul": "crypto/ed25519/internal/edwards25519/field.feMulGeneric", - "crypto/ed25519/internal/edwards25519/field.feSquare": "crypto/ed25519/internal/edwards25519/field.feSquareGeneric", + "crypto/ed25519/internal/edwards25519/field.feMul": "crypto/ed25519/internal/edwards25519/field.feMulGeneric", + "crypto/internal/edwards25519/field.feSquare": "crypto/ed25519/internal/edwards25519/field.feSquareGeneric", "crypto/md5.block": "crypto/md5.blockGeneric", "crypto/sha1.block": "crypto/sha1.blockGeneric", "crypto/sha1.blockAMD64": "crypto/sha1.blockGeneric", From c55ac9f28ecef310931318f197fbd7f8606875e9 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 30 Mar 2024 13:24:55 +0100 Subject: [PATCH 020/444] rp2040: move UART0 and UART1 to common file This is makes it easier to use these in simulation, plus it avoids duplication. The only downside I see is that more UARTs get defined than a board supports, but no existing code should break (no UARTs get renamed for example). --- src/machine/board_ae_rp2040.go | 25 ------------------- src/machine/board_badger2040.go | 18 -------------- src/machine/board_challenger_rp2040.go | 25 ------------------- src/machine/board_feather_rp2040.go | 25 ------------------- src/machine/board_gopher-badge.go | 18 -------------- src/machine/board_kb2040.go | 25 ------------------- src/machine/board_macropad-rp2040.go | 18 -------------- src/machine/board_nano-rp2040.go | 29 ++-------------------- src/machine/board_pico.go | 25 ------------------- src/machine/board_qtpy_rp2040.go | 25 ------------------- src/machine/board_thingplus_rp2040.go | 18 -------------- src/machine/board_thumby.go | 18 -------------- src/machine/board_tufty2040.go | 18 -------------- src/machine/board_waveshare-rp2040-zero.go | 24 ------------------ src/machine/board_xiao-rp2040.go | 18 -------------- src/machine/machine_rp2040_gpio.go | 20 +++++++++++++++ 16 files changed, 22 insertions(+), 327 deletions(-) diff --git a/src/machine/board_ae_rp2040.go b/src/machine/board_ae_rp2040.go index 91432e4b41..716cf72b31 100644 --- a/src/machine/board_ae_rp2040.go +++ b/src/machine/board_ae_rp2040.go @@ -2,11 +2,6 @@ package machine -import ( - "device/rp" - "runtime/interrupt" -) - // GPIO pins const ( GP0 Pin = GPIO0 @@ -77,28 +72,8 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } - - UART1 = &_UART1 - _UART1 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART1, - } -) - var DefaultUART = UART0 -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) - UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) -} - // USB identifiers const ( usb_STRING_PRODUCT = "AE-RP2040" diff --git a/src/machine/board_badger2040.go b/src/machine/board_badger2040.go index d1b29f4c64..049e3cb899 100644 --- a/src/machine/board_badger2040.go +++ b/src/machine/board_badger2040.go @@ -7,11 +7,6 @@ // - Badger 2040 schematic: https://cdn.shopify.com/s/files/1/0174/1800/files/badger_2040_schematic.pdf?v=1645702148 package machine -import ( - "device/rp" - "runtime/interrupt" -) - const ( LED Pin = GPIO25 @@ -92,17 +87,4 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } -) - var DefaultUART = UART0 - -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) -} diff --git a/src/machine/board_challenger_rp2040.go b/src/machine/board_challenger_rp2040.go index b67b3245d3..9a85aa0aef 100644 --- a/src/machine/board_challenger_rp2040.go +++ b/src/machine/board_challenger_rp2040.go @@ -2,11 +2,6 @@ package machine -import ( - "device/rp" - "runtime/interrupt" -) - const ( LED = GPIO24 @@ -84,28 +79,8 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } - - UART1 = &_UART1 - _UART1 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART1, - } -) - var DefaultUART = UART0 -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) - UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) -} - // USB identifiers const ( usb_STRING_PRODUCT = "Challenger 2040 LoRa" diff --git a/src/machine/board_feather_rp2040.go b/src/machine/board_feather_rp2040.go index f121a1e832..44091e56e9 100644 --- a/src/machine/board_feather_rp2040.go +++ b/src/machine/board_feather_rp2040.go @@ -2,11 +2,6 @@ package machine -import ( - "device/rp" - "runtime/interrupt" -) - // Onboard crystal oscillator frequency, in MHz. const xoscFreq = 12 // MHz @@ -73,28 +68,8 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } - - UART1 = &_UART1 - _UART1 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART1, - } -) - var DefaultUART = UART0 -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) - UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) -} - // USB identifiers const ( usb_STRING_PRODUCT = "Feather RP2040" diff --git a/src/machine/board_gopher-badge.go b/src/machine/board_gopher-badge.go index e7eff25f84..7af27118b2 100644 --- a/src/machine/board_gopher-badge.go +++ b/src/machine/board_gopher-badge.go @@ -5,11 +5,6 @@ // For more information, see: https://gopherbadge.com/ package machine -import ( - "device/rp" - "runtime/interrupt" -) - const ( /*ADC0 Pin = GPIO26 ADC1 Pin = GPIO27 @@ -92,17 +87,4 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART1 = &_UART1 - _UART1 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART1, - } -) - var DefaultUART = UART1 - -func init() { - UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) -} diff --git a/src/machine/board_kb2040.go b/src/machine/board_kb2040.go index 288f1ddf3c..1a6f353623 100644 --- a/src/machine/board_kb2040.go +++ b/src/machine/board_kb2040.go @@ -2,11 +2,6 @@ package machine -import ( - "device/rp" - "runtime/interrupt" -) - // Onboard crystal oscillator frequency, in MHz. const xoscFreq = 12 // MHz @@ -75,28 +70,8 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } - - UART1 = &_UART1 - _UART1 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART1, - } -) - var DefaultUART = UART0 -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) - UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) -} - // USB identifiers const ( usb_STRING_PRODUCT = "KB2040" diff --git a/src/machine/board_macropad-rp2040.go b/src/machine/board_macropad-rp2040.go index eca20f7970..78bd2b749e 100644 --- a/src/machine/board_macropad-rp2040.go +++ b/src/machine/board_macropad-rp2040.go @@ -2,11 +2,6 @@ package machine -import ( - "device/rp" - "runtime/interrupt" -) - const ( NeopixelCount = 12 @@ -78,21 +73,8 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } -) - var DefaultUART = UART0 -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) -} - // USB identifiers const ( usb_STRING_PRODUCT = "MacroPad RP2040" diff --git a/src/machine/board_nano-rp2040.go b/src/machine/board_nano-rp2040.go index 4f5757e865..8155523134 100644 --- a/src/machine/board_nano-rp2040.go +++ b/src/machine/board_nano-rp2040.go @@ -11,11 +11,6 @@ // - Nano RP2040 Connect technical reference: https://docs.arduino.cc/tutorials/nano-rp2040-connect/rp2040-01-technical-reference package machine -import ( - "device/rp" - "runtime/interrupt" -) - // Digital Pins const ( D2 Pin = GPIO25 @@ -125,27 +120,7 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } - - UART1 = &_UART1 - _UART1 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART1, - } - - // UART_NINA on the Arduino Nano RP2040 connects to the NINA HCI. - UART_NINA = UART1 -) +// UART_NINA on the Arduino Nano RP2040 connects to the NINA HCI. +var UART_NINA = UART1 var DefaultUART = UART0 - -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) - UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) -} diff --git a/src/machine/board_pico.go b/src/machine/board_pico.go index 41109af03d..efbd6ef7dc 100644 --- a/src/machine/board_pico.go +++ b/src/machine/board_pico.go @@ -2,11 +2,6 @@ package machine -import ( - "device/rp" - "runtime/interrupt" -) - // GPIO pins const ( GP0 Pin = GPIO0 @@ -79,28 +74,8 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } - - UART1 = &_UART1 - _UART1 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART1, - } -) - var DefaultUART = UART0 -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) - UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) -} - // USB identifiers const ( usb_STRING_PRODUCT = "Pico" diff --git a/src/machine/board_qtpy_rp2040.go b/src/machine/board_qtpy_rp2040.go index 3f37023f89..3eabf0c9b6 100644 --- a/src/machine/board_qtpy_rp2040.go +++ b/src/machine/board_qtpy_rp2040.go @@ -2,11 +2,6 @@ package machine -import ( - "device/rp" - "runtime/interrupt" -) - // Onboard crystal oscillator frequency, in MHz. const xoscFreq = 12 // MHz @@ -84,28 +79,8 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } - - UART1 = &_UART1 - _UART1 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART1, - } -) - var DefaultUART = UART0 -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) - UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) -} - // USB identifiers const ( usb_STRING_PRODUCT = "QT Py RP2040" diff --git a/src/machine/board_thingplus_rp2040.go b/src/machine/board_thingplus_rp2040.go index ac40ee4a4b..7f005412f1 100644 --- a/src/machine/board_thingplus_rp2040.go +++ b/src/machine/board_thingplus_rp2040.go @@ -2,11 +2,6 @@ package machine -import ( - "device/rp" - "runtime/interrupt" -) - // Onboard crystal oscillator frequency, in MHz. const xoscFreq = 12 // MHz @@ -90,21 +85,8 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } -) - var DefaultUART = UART0 -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) -} - // USB identifiers const ( usb_STRING_PRODUCT = "Thing Plus RP2040" diff --git a/src/machine/board_thumby.go b/src/machine/board_thumby.go index dc1f82beb3..f89a8b7059 100644 --- a/src/machine/board_thumby.go +++ b/src/machine/board_thumby.go @@ -5,11 +5,6 @@ // https://thumby.us/ package machine -import ( - "device/rp" - "runtime/interrupt" -) - const ( THUMBY_SCK_PIN = I2C1_SDA_PIN THUMBY_SDA_PIN = I2C1_SCL_PIN @@ -78,17 +73,4 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the Thumby -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } -) - var DefaultUART = UART0 - -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) -} diff --git a/src/machine/board_tufty2040.go b/src/machine/board_tufty2040.go index d82cb99515..57d244f28b 100644 --- a/src/machine/board_tufty2040.go +++ b/src/machine/board_tufty2040.go @@ -7,11 +7,6 @@ // - Tufty 2040 schematic: https://cdn.shopify.com/s/files/1/0174/1800/files/tufty_schematic.pdf?v=1655385675 package machine -import ( - "device/rp" - "runtime/interrupt" -) - const ( LED Pin = GPIO25 @@ -87,17 +82,4 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } -) - var DefaultUART = UART0 - -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) -} diff --git a/src/machine/board_waveshare-rp2040-zero.go b/src/machine/board_waveshare-rp2040-zero.go index 65e7a9481c..ea9223c154 100644 --- a/src/machine/board_waveshare-rp2040-zero.go +++ b/src/machine/board_waveshare-rp2040-zero.go @@ -7,11 +7,6 @@ // - https://www.waveshare.com/wiki/RP2040-Zero package machine -import ( - "device/rp" - "runtime/interrupt" -) - // Digital Pins const ( D0 Pin = GPIO0 @@ -94,27 +89,8 @@ const ( UART1_RX_PIN = GPIO9 ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } - UART1 = &_UART1 - _UART1 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART1, - } -) - var DefaultUART = UART0 -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) - UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) -} - // USB CDC identifiers const ( usb_STRING_PRODUCT = "RP2040-Zero" diff --git a/src/machine/board_xiao-rp2040.go b/src/machine/board_xiao-rp2040.go index 272fcc599d..b010314557 100644 --- a/src/machine/board_xiao-rp2040.go +++ b/src/machine/board_xiao-rp2040.go @@ -7,11 +7,6 @@ // - https://wiki.seeedstudio.com/XIAO-RP2040/ package machine -import ( - "device/rp" - "runtime/interrupt" -) - // Digital Pins const ( D0 Pin = GPIO26 @@ -81,21 +76,8 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } -) - var DefaultUART = UART0 -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) -} - // USB CDC identifiers const ( usb_STRING_PRODUCT = "XIAO RP2040" diff --git a/src/machine/machine_rp2040_gpio.go b/src/machine/machine_rp2040_gpio.go index 86cb09eb9c..89c5f40b16 100644 --- a/src/machine/machine_rp2040_gpio.go +++ b/src/machine/machine_rp2040_gpio.go @@ -331,3 +331,23 @@ func (p Pin) ioIntBit(change PinChange) uint32 { func getIntChange(p Pin, status uint32) PinChange { return PinChange(status>>(4*(p%8))) & 0xf } + +// UART on the RP2040 +var ( + UART0 = &_UART0 + _UART0 = UART{ + Buffer: NewRingBuffer(), + Bus: rp.UART0, + } + + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: rp.UART1, + } +) + +func init() { + UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) + UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) +} From 90b0bf646c271157f97aa59ffb2efec7eee4d2ec Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 30 Mar 2024 13:37:52 +0100 Subject: [PATCH 021/444] rp2040: make all RP2040 boards available for simulation This makes all rp2040 boards available for simulation using -tags=. Importantly, this includes the Gopher Badge which I'm working on to add to the TinyGo Playground. --- GNUmakefile | 2 + src/machine/machine_generic.go | 10 +++-- src/machine/machine_generic_peripherals.go | 7 +++- src/machine/machine_rp2040.go | 40 -------------------- src/machine/machine_rp2040_pins.go | 43 ++++++++++++++++++++++ 5 files changed, 57 insertions(+), 45 deletions(-) create mode 100644 src/machine/machine_rp2040_pins.go diff --git a/GNUmakefile b/GNUmakefile index 2d7da5c590..18ab754e64 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -525,6 +525,8 @@ ifneq ($(WASM), 0) @$(MD5SUM) test.wasm $(TINYGO) build -size short -o test.wasm -tags=mch2022 examples/serial @$(MD5SUM) test.wasm + $(TINYGO) build -size short -o test.wasm -tags=gopher_badge examples/blinky1 + @$(MD5SUM) test.wasm endif # test all targets/boards $(TINYGO) build -size short -o test.hex -target=pca10040-s132v6 examples/blinky1 diff --git a/src/machine/machine_generic.go b/src/machine/machine_generic.go index d1070a9a03..a981f5bffd 100644 --- a/src/machine/machine_generic.go +++ b/src/machine/machine_generic.go @@ -7,15 +7,14 @@ package machine const deviceName = "generic" var ( - UART0 = &UART{0} - USB = &UART{100} + USB = &UART{100} ) // The Serial port always points to the default UART in a simulated environment. // // TODO: perhaps this should be a special serial object that outputs via WASI // stdout calls. -var Serial = UART0 +var Serial = hardwareUART0 const ( PinInput PinMode = iota @@ -176,6 +175,11 @@ func uartRead(bus uint8, buf *byte, bufLen int) int //export __tinygo_uart_write func uartWrite(bus uint8, buf *byte, bufLen int) int +var ( + hardwareUART0 = &UART{0} + hardwareUART1 = &UART{1} +) + // Some objects used by Atmel SAM D chips (samd21, samd51). // Defined here (without build tag) for convenience. var ( diff --git a/src/machine/machine_generic_peripherals.go b/src/machine/machine_generic_peripherals.go index 5491a26983..28539f0e7f 100644 --- a/src/machine/machine_generic_peripherals.go +++ b/src/machine/machine_generic_peripherals.go @@ -6,6 +6,9 @@ package machine // boards that define their peripherals in the board file (e.g. board_qtpy.go). var ( - SPI0 = SPI{0} - I2C0 = &I2C{0} + UART0 = hardwareUART0 + UART1 = hardwareUART1 + SPI0 = SPI{0} + SPI1 = SPI{1} + I2C0 = &I2C{0} ) diff --git a/src/machine/machine_rp2040.go b/src/machine/machine_rp2040.go index e76a85e199..45f9f510f5 100644 --- a/src/machine/machine_rp2040.go +++ b/src/machine/machine_rp2040.go @@ -10,46 +10,6 @@ import ( const deviceName = rp.Device -const ( - // GPIO pins - GPIO0 Pin = 0 // peripherals: PWM0 channel A - GPIO1 Pin = 1 // peripherals: PWM0 channel B - GPIO2 Pin = 2 // peripherals: PWM1 channel A - GPIO3 Pin = 3 // peripherals: PWM1 channel B - GPIO4 Pin = 4 // peripherals: PWM2 channel A - GPIO5 Pin = 5 // peripherals: PWM2 channel B - GPIO6 Pin = 6 // peripherals: PWM3 channel A - GPIO7 Pin = 7 // peripherals: PWM3 channel B - GPIO8 Pin = 8 // peripherals: PWM4 channel A - GPIO9 Pin = 9 // peripherals: PWM4 channel B - GPIO10 Pin = 10 // peripherals: PWM5 channel A - GPIO11 Pin = 11 // peripherals: PWM5 channel B - GPIO12 Pin = 12 // peripherals: PWM6 channel A - GPIO13 Pin = 13 // peripherals: PWM6 channel B - GPIO14 Pin = 14 // peripherals: PWM7 channel A - GPIO15 Pin = 15 // peripherals: PWM7 channel B - GPIO16 Pin = 16 // peripherals: PWM0 channel A - GPIO17 Pin = 17 // peripherals: PWM0 channel B - GPIO18 Pin = 18 // peripherals: PWM1 channel A - GPIO19 Pin = 19 // peripherals: PWM1 channel B - GPIO20 Pin = 20 // peripherals: PWM2 channel A - GPIO21 Pin = 21 // peripherals: PWM2 channel B - GPIO22 Pin = 22 // peripherals: PWM3 channel A - GPIO23 Pin = 23 // peripherals: PWM3 channel B - GPIO24 Pin = 24 // peripherals: PWM4 channel A - GPIO25 Pin = 25 // peripherals: PWM4 channel B - GPIO26 Pin = 26 // peripherals: PWM5 channel A - GPIO27 Pin = 27 // peripherals: PWM5 channel B - GPIO28 Pin = 28 // peripherals: PWM6 channel A - GPIO29 Pin = 29 // peripherals: PWM6 channel B - - // Analog pins - ADC0 Pin = GPIO26 - ADC1 Pin = GPIO27 - ADC2 Pin = GPIO28 - ADC3 Pin = GPIO29 -) - //go:linkname machineInit runtime.machineInit func machineInit() { // Reset all peripherals to put system into a known state, diff --git a/src/machine/machine_rp2040_pins.go b/src/machine/machine_rp2040_pins.go new file mode 100644 index 0000000000..9abbdb002e --- /dev/null +++ b/src/machine/machine_rp2040_pins.go @@ -0,0 +1,43 @@ +//go:build rp2040 || ae_rp2040 || badger2040 || challenger_rp2040 || feather_rp2040 || gopher_badge || kb2040 || macropad_rp2040 || nano_rp2040 || pico || qtpy_rp2040 || thingplus_rp2040 || thumby || tufty2040 || waveshare_rp2040_zero || xiao_rp2040 + +package machine + +const ( + // GPIO pins + GPIO0 Pin = 0 // peripherals: PWM0 channel A + GPIO1 Pin = 1 // peripherals: PWM0 channel B + GPIO2 Pin = 2 // peripherals: PWM1 channel A + GPIO3 Pin = 3 // peripherals: PWM1 channel B + GPIO4 Pin = 4 // peripherals: PWM2 channel A + GPIO5 Pin = 5 // peripherals: PWM2 channel B + GPIO6 Pin = 6 // peripherals: PWM3 channel A + GPIO7 Pin = 7 // peripherals: PWM3 channel B + GPIO8 Pin = 8 // peripherals: PWM4 channel A + GPIO9 Pin = 9 // peripherals: PWM4 channel B + GPIO10 Pin = 10 // peripherals: PWM5 channel A + GPIO11 Pin = 11 // peripherals: PWM5 channel B + GPIO12 Pin = 12 // peripherals: PWM6 channel A + GPIO13 Pin = 13 // peripherals: PWM6 channel B + GPIO14 Pin = 14 // peripherals: PWM7 channel A + GPIO15 Pin = 15 // peripherals: PWM7 channel B + GPIO16 Pin = 16 // peripherals: PWM0 channel A + GPIO17 Pin = 17 // peripherals: PWM0 channel B + GPIO18 Pin = 18 // peripherals: PWM1 channel A + GPIO19 Pin = 19 // peripherals: PWM1 channel B + GPIO20 Pin = 20 // peripherals: PWM2 channel A + GPIO21 Pin = 21 // peripherals: PWM2 channel B + GPIO22 Pin = 22 // peripherals: PWM3 channel A + GPIO23 Pin = 23 // peripherals: PWM3 channel B + GPIO24 Pin = 24 // peripherals: PWM4 channel A + GPIO25 Pin = 25 // peripherals: PWM4 channel B + GPIO26 Pin = 26 // peripherals: PWM5 channel A + GPIO27 Pin = 27 // peripherals: PWM5 channel B + GPIO28 Pin = 28 // peripherals: PWM6 channel A + GPIO29 Pin = 29 // peripherals: PWM6 channel B + + // Analog pins + ADC0 Pin = GPIO26 + ADC1 Pin = GPIO27 + ADC2 Pin = GPIO28 + ADC3 Pin = GPIO29 +) From 85b59e66da5b1741e8625655c49fcd85de379f4e Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 12 Apr 2024 14:54:27 +0200 Subject: [PATCH 022/444] machine: add __tinygo_spi_tx function to simulator This is much, _much_ faster than __tinygo_spi_transfer which can only transfer a single byte at a time. This is especially important for simulated displays. I've already implemented the browser side of this on the playground and have used this patch for local testing where it massively speeds up display operations. --- src/machine/machine_generic.go | 35 ++++++++++++++++++++++++++++++++++ src/machine/spi_tx.go | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/machine/machine_generic.go b/src/machine/machine_generic.go index a981f5bffd..e7d513ad1e 100644 --- a/src/machine/machine_generic.go +++ b/src/machine/machine_generic.go @@ -66,12 +66,47 @@ func (spi SPI) Transfer(w byte) (byte, error) { return spiTransfer(spi.Bus, w), nil } +// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read +// interface, there must always be the same number of bytes written as bytes read. +// The Tx method knows about this, and offers a few different ways of calling it. +// +// This form sends the bytes in tx buffer, putting the resulting bytes read into the rx buffer. +// Note that the tx and rx buffers must be the same size: +// +// spi.Tx(tx, rx) +// +// This form sends the tx buffer, ignoring the result. Useful for sending "commands" that return zeros +// until all the bytes in the command packet have been received: +// +// spi.Tx(tx, nil) +// +// This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": +// +// spi.Tx(nil, rx) +func (spi SPI) Tx(w, r []byte) error { + var wptr, rptr *byte + var wlen, rlen int + if len(w) != 0 { + wptr = &w[0] + wlen = len(w) + } + if len(r) != 0 { + rptr = &r[0] + rlen = len(r) + } + spiTX(spi.Bus, wptr, wlen, rptr, rlen) + return nil +} + //export __tinygo_spi_configure func spiConfigure(bus uint8, sck Pin, SDO Pin, SDI Pin) //export __tinygo_spi_transfer func spiTransfer(bus uint8, w uint8) uint8 +//export __tinygo_spi_tx +func spiTX(bus uint8, wptr *byte, wlen int, rptr *byte, rlen int) uint8 + // InitADC enables support for ADC peripherals. func InitADC() { // Nothing to do here. diff --git a/src/machine/spi_tx.go b/src/machine/spi_tx.go index bfc3bfb60c..bb6305fe73 100644 --- a/src/machine/spi_tx.go +++ b/src/machine/spi_tx.go @@ -1,4 +1,4 @@ -//go:build !baremetal || atmega || fe310 || k210 || (nxp && !mk66f18) || (stm32 && !stm32f7x2 && !stm32l5x2) +//go:build atmega || fe310 || k210 || (nxp && !mk66f18) || (stm32 && !stm32f7x2 && !stm32l5x2) // This file implements the SPI Tx function for targets that don't have a custom // (faster) implementation for it. From 9d6e30701b98f856125e55ba3fd26fa6b8f4a280 Mon Sep 17 00:00:00 2001 From: Dan Kegel Date: Fri, 12 Apr 2024 12:19:48 -0700 Subject: [PATCH 023/444] lint: add "make lint" target, run it from ci See https://github.com/tinygo-org/tinygo/issues/4225 Runs in both circleci and github, circleci is run on branch push, github is run on PR Revive builds so fast, don't bother installing it; saves us wondering which one we get Uses tools.go idiom to give control over linter versions to go.mod. Also pacifies linter re AppendToGlobal as a token first fix. TODO: gradually expand the number of directories that are linted, uncomment more entries in revive.toml, and fix or suppress the warnings lint finds. TODO: add linters "go vet" and staticcheck NOT TODO: don't add metalinters like golangci-lint that pull in lots of new of dependencies; we'd rather not clutter go.mod that much, let alone open ourselves up to the additional attack surface. --- .circleci/config.yml | 2 +- .github/workflows/linux.yml | 2 ++ GNUmakefile | 6 +++++ compiler/llvmutil/llvm.go | 2 +- go.mod | 16 ++++++++--- go.sum | 54 +++++++++++++++++++++++++++++-------- internal/tools/tools.go | 7 +++++ revive.toml | 30 +++++++++++++++++++++ 8 files changed, 103 insertions(+), 16 deletions(-) create mode 100644 internal/tools/tools.go create mode 100644 revive.toml diff --git a/.circleci/config.yml b/.circleci/config.yml index 6827366a7b..c9c9d65043 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -88,7 +88,7 @@ commands: # Do this before gen-device so that it doesn't check the # formatting of generated files. name: Check Go code formatting - command: make fmt-check + command: make fmt-check lint - run: make gen-device -j4 - run: make smoketest XTENSA=0 - save_cache: diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 978e5a1232..9d9a779a9c 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -113,6 +113,8 @@ jobs: gem install --version 4.0.7 public_suffix gem install --version 2.7.6 dotenv gem install --no-document fpm + - name: Run linter + run: make lint - name: Build TinyGo release run: | make release deb -j3 STATIC=1 diff --git a/GNUmakefile b/GNUmakefile index 18ab754e64..8c4f00b29d 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -911,3 +911,9 @@ ifneq ($(RELEASEONLY), 1) release: build/release deb: build/release endif + +lint: + # Only run on compiler dir for now, expand as we clean up other dirs + # This obviously won't scale, but it's a start, and it's fast + go run github.com/mgechev/revive --version + go run github.com/mgechev/revive --config revive.toml compiler/... diff --git a/compiler/llvmutil/llvm.go b/compiler/llvmutil/llvm.go index c07cd0560b..5490106d84 100644 --- a/compiler/llvmutil/llvm.go +++ b/compiler/llvmutil/llvm.go @@ -173,7 +173,7 @@ func SplitBasicBlock(builder llvm.Builder, afterInst llvm.Value, insertAfter llv return newBlock } -// Append the given values to a global array like llvm.used. The global might +// AppendToGlobal appends the given values to a global array like llvm.used. The global might // not exist yet. The values can be any pointer type, they will be cast to i8*. func AppendToGlobal(mod llvm.Module, globalName string, values ...llvm.Value) { // Read the existing values in the llvm.used array (if it exists). diff --git a/go.mod b/go.mod index aa10ee7b69..d5294c2175 100644 --- a/go.mod +++ b/go.mod @@ -11,11 +11,11 @@ require ( github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892 - github.com/mattn/go-colorable v0.1.8 + github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-tty v0.0.4 + github.com/mgechev/revive v1.3.7 github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 go.bug.st/serial v1.6.0 - golang.org/x/net v0.20.0 golang.org/x/sys v0.16.0 golang.org/x/tools v0.17.0 gopkg.in/yaml.v2 v2.4.0 @@ -23,13 +23,23 @@ require ( ) require ( + github.com/BurntSushi/toml v1.3.2 // indirect + github.com/chavacava/garif v0.1.0 // indirect github.com/chromedp/sysutil v1.0.0 // indirect github.com/creack/goselect v0.1.2 // indirect + github.com/fatih/color v1.16.0 // indirect + github.com/fatih/structtag v1.2.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/ws v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-isatty v0.0.12 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/spf13/afero v1.11.0 // indirect golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index 1fe5d75765..fd5a55473f 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,11 @@ +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/aykevl/go-wasm v0.0.2-0.20240312204833-50275154210c h1:4T0Vj1UkGgcpkRrmn7SbokebnlfxJcMZPgWtOYACAAA= github.com/aykevl/go-wasm v0.0.2-0.20240312204833-50275154210c/go.mod h1:7sXyiaA0WtSogCu67R2252fQpVmJMh9JWJ9ddtGkpWw= github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI= github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= +github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= +github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= github.com/chromedp/cdproto v0.0.0-20211126220118-81fa0469ad77/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U= github.com/chromedp/cdproto v0.0.0-20220113222801-0725d94bb6ee h1:+SFdIVfQpG0s0DHYzou0kgfE0n0ZjKPwbiRJsXrZegU= github.com/chromedp/cdproto v0.0.0-20220113222801-0725d94bb6ee/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U= @@ -11,7 +15,13 @@ github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3I github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= 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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= 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= @@ -31,33 +41,53 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892 h1:6J+qramlHVLmiBOgRiBOnQkno8uprqG6YFFQTt6uYIw= github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-tty v0.0.4 h1:NVikla9X8MN0SQAqCYzpGyXv0jY7MNl3HOWD2dkle7E= github.com/mattn/go-tty v0.0.4/go.mod h1:u5GGXBtZU6RQoKV8gY5W6UhMudbR5vXnUe7j3pxse28= +github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 h1:zpIH83+oKzcpryru8ceC6BxnoG8TBrhgAvRg8obzup0= +github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= +github.com/mgechev/revive v1.3.7 h1:502QY0vQGe9KtYJ9FpxMz9rL+Fc/P13CI5POL4uHCcE= +github.com/mgechev/revive v1.3.7/go.mod h1:RJ16jUbF0OWC3co/+XTxmFNgEpUPwnnA0BRllX2aDNA= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5 h1:1SoBaSPudixRecmlHXb/GxmaD3fLMtHIDN13QujwQuc= github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= go.bug.st/serial v1.6.0 h1:mAbRGN4cKE2J5gMwsMHC2KQisdLRQssO9WSM+rbZJ8A= go.bug.st/serial v1.6.0/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= 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= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= @@ -68,6 +98,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= tinygo.org/x/go-llvm v0.0.0-20240106122909-c2c543540318 h1:4KjZvPtcN1UwobevcGbdzeinx0L1i8HDdJu84bu7NI8= tinygo.org/x/go-llvm v0.0.0-20240106122909-c2c543540318/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0= diff --git a/internal/tools/tools.go b/internal/tools/tools.go new file mode 100644 index 0000000000..c22654a78b --- /dev/null +++ b/internal/tools/tools.go @@ -0,0 +1,7 @@ +// Install linter versions specified in go.mod +// See https://marcofranssen.nl/manage-go-tools-via-go-modules for idom +package tools + +import ( + _ "github.com/mgechev/revive" +) diff --git a/revive.toml b/revive.toml new file mode 100644 index 0000000000..f09127ca6a --- /dev/null +++ b/revive.toml @@ -0,0 +1,30 @@ +ignoreGeneratedHeader = false +severity = "warning" +confidence = 0.8 +errorCode = 0 +warningCode = 0 + +# Enable these as we fix them +[rule.blank-imports] +[rule.context-as-argument] +[rule.context-keys-type] +[rule.dot-imports] +[rule.error-return] +[rule.error-strings] +[rule.error-naming] +[rule.exported] +[rule.increment-decrement] +[rule.var-naming] +[rule.var-declaration] +#[rule.package-comments] +[rule.range] +[rule.receiver-naming] +[rule.time-naming] +[rule.unexported-return] +#[rule.indent-error-flow] +[rule.errorf] +#[rule.empty-block] +[rule.superfluous-else] +#[rule.unused-parameter] +[rule.unreachable-code] +#[rule.redefines-builtin-id] From dcee228c4e29af5c06112f0ccc093c8f6483d64f Mon Sep 17 00:00:00 2001 From: leongross Date: Tue, 26 Mar 2024 13:21:50 +0100 Subject: [PATCH 024/444] add os.Link Signed-off-by: leongross --- src/os/file_unix.go | 13 +++++++++ src/os/os_hardlink_test.go | 53 +++++++++++++++++++++++++++++++++++++ src/syscall/syscall_libc.go | 15 +++++++++++ 3 files changed, 81 insertions(+) create mode 100644 src/os/os_hardlink_test.go diff --git a/src/os/file_unix.go b/src/os/file_unix.go index 665fb0937e..25f0266a67 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -74,6 +74,19 @@ func tempDir() string { return dir } +// Link creates newname as a hard link to the oldname file. +// If there is an error, it will be of type *LinkError. +func Link(oldname, newname string) error { + e := ignoringEINTR(func() error { + return syscall.Link(oldname, newname) + }) + + if e != nil { + return &LinkError{"link", oldname, newname, e} + } + return nil +} + // Symlink creates newname as a symbolic link to oldname. // On Windows, a symlink to a non-existent oldname creates a file symlink; // if oldname is later created as a directory the symlink will not work. diff --git a/src/os/os_hardlink_test.go b/src/os/os_hardlink_test.go new file mode 100644 index 0000000000..96f1c9bbb6 --- /dev/null +++ b/src/os/os_hardlink_test.go @@ -0,0 +1,53 @@ +//go:build !windows && !baremetal && !js && !wasi && !wasip1 && !wasm_unknown + +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package os_test + +import ( + . "os" + "syscall" + "testing" +) + +func TestHardlink(t *testing.T) { + defer chtmpdir(t)() + from, to := "hardlinktestfrom", "hardlinktestto" + + file, err := Create(to) + if err != nil { + t.Fatalf("Create(%q) failed: %v", to, err) + } + if err = file.Close(); err != nil { + t.Errorf("Close(%q) failed: %v", to, err) + } + err = Link(to, from) + if err != nil { + t.Fatalf("Link(%q, %q) failed: %v", to, from, err) + } + + tostat, err := Lstat(to) + if err != nil { + t.Fatalf("Lstat(%q) failed: %v", to, err) + } + fromstat, err := Stat(from) + if err != nil { + t.Fatalf("Stat(%q) failed: %v", from, err) + } + if !SameFile(tostat, fromstat) { + t.Errorf("Symlink(%q, %q) did not create symlink", to, from) + } + + fromstat, err = Lstat(from) + if err != nil { + t.Fatalf("Lstat(%q) failed: %v", from, err) + } + // if they have the same inode, they are hard links + if fromstat.Sys().(*syscall.Stat_t).Ino != tostat.Sys().(*syscall.Stat_t).Ino { + t.Fatalf("Lstat(%q).Sys().Ino = %v, Lstat(%q).Sys().Ino = %v, want the same", to, tostat.Sys().(*syscall.Stat_t).Ino, from, fromstat.Sys().(*syscall.Stat_t).Ino) + } + + file.Close() +} diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index 7a13245b6d..fb2e23968e 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -134,6 +134,16 @@ func Rename(from, to string) (err error) { return } +func Link(oldname, newname string) (err error) { + fromdata := cstring(oldname) + todata := cstring(newname) + fail := int(libc_link(&fromdata[0], &todata[0])) + if fail < 0 { + err = getErrno() + } + return +} + func Symlink(from, to string) (err error) { fromdata := cstring(from) todata := cstring(to) @@ -416,6 +426,11 @@ func libc_rename(from, to *byte) int32 //export symlink func libc_symlink(from, to *byte) int32 +// int link(const char *oldname, *newname); +// +//export link +func libc_link(oldname, newname *byte) int32 + // int fsync(int fd); // //export fsync From 712275572554d0ba8ee85e722fef8d6c91d6531a Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sat, 9 Mar 2024 11:36:57 +0000 Subject: [PATCH 025/444] compileopts: apply OpenOCD commands after target configuration The openocd-commands option cannot refer to commands defined by the target configuration. Lift this restriction by moving the commands to after target loading. --- compileopts/config.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compileopts/config.go b/compileopts/config.go index e3f67b11a2..98dd307387 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -479,9 +479,6 @@ func (c *Config) OpenOCDConfiguration() (args []string, err error) { return nil, fmt.Errorf("unknown OpenOCD transport: %#v", c.Target.OpenOCDTransport) } args = []string{"-f", "interface/" + openocdInterface + ".cfg"} - for _, cmd := range c.Target.OpenOCDCommands { - args = append(args, "-c", cmd) - } if c.Target.OpenOCDTransport != "" { transport := c.Target.OpenOCDTransport if transport == "swd" { @@ -493,6 +490,9 @@ func (c *Config) OpenOCDConfiguration() (args []string, err error) { args = append(args, "-c", "transport select "+transport) } args = append(args, "-f", "target/"+c.Target.OpenOCDTarget+".cfg") + for _, cmd := range c.Target.OpenOCDCommands { + args = append(args, "-c", cmd) + } return args, nil } From 39029cc37604c175c8b512478ba60ab617e79960 Mon Sep 17 00:00:00 2001 From: dkegel-fastly <79674949+dkegel-fastly@users.noreply.github.com> Date: Fri, 19 Apr 2024 06:57:01 -0700 Subject: [PATCH 026/444] Run Nick G's spellchecker github.com/client9/misspell, carefuly fix what it found (#4235) --- compileopts/target.go | 2 +- compiler/compiler.go | 4 ++-- compiler/llvmutil/llvm.go | 4 ++-- loader/goroot.go | 2 +- loader/loader.go | 2 +- src/internal/bytealg/bytealg.go | 4 ++-- src/internal/task/task_stack.go | 2 +- src/machine/board_nano-33-ble.go | 2 +- src/machine/machine_atsamd21.go | 8 ++++---- src/machine/machine_atsamd51.go | 4 ++-- src/machine/machine_atsame5x_can.go | 2 +- src/machine/machine_esp32.go | 2 +- src/machine/machine_esp32c3.go | 2 +- src/machine/machine_esp32c3_spi.go | 2 +- src/machine/machine_generic.go | 2 +- src/machine/machine_nrf.go | 2 +- src/machine/machine_nrf51.go | 2 +- src/machine/machine_nrf52xxx.go | 2 +- src/machine/machine_rp2040_i2c.go | 2 +- src/machine/machine_rp2040_spi.go | 2 +- src/machine/machine_stm32l5x2.go | 2 +- src/machine/spi_tx.go | 2 +- src/os/filesystem.go | 2 +- src/os/seek_unix_bad.go | 2 +- src/runtime/chan.go | 4 ++-- src/runtime/gc_blocks.go | 4 ++-- src/runtime/gc_precise.go | 2 +- src/runtime/interrupt/interrupt.go | 2 +- src/runtime/interrupt/interrupt_esp32c3.go | 2 +- src/runtime/os_darwin.go | 2 +- src/runtime/runtime_atsamd21.go | 2 +- src/runtime/runtime_atsamd51.go | 2 +- src/runtime/runtime_avrtiny.go | 2 +- src/runtime/runtime_cortexm_hardfault.go | 2 +- src/runtime/runtime_fe310.go | 2 +- src/runtime/runtime_k210.go | 2 +- src/syscall/syscall_libc_wasip1.go | 2 +- stacksize/stacksize.go | 2 +- transform/optimizer.go | 2 +- 39 files changed, 48 insertions(+), 48 deletions(-) diff --git a/compileopts/target.go b/compileopts/target.go index 66f44b7ccf..da91cfa4ff 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -26,7 +26,7 @@ type TargetSpec struct { Inherits []string `json:"inherits,omitempty"` Triple string `json:"llvm-target,omitempty"` CPU string `json:"cpu,omitempty"` - ABI string `json:"target-abi,omitempty"` // rougly equivalent to -mabi= flag + ABI string `json:"target-abi,omitempty"` // roughly equivalent to -mabi= flag Features string `json:"features,omitempty"` GOOS string `json:"goos,omitempty"` GOARCH string `json:"goarch,omitempty"` diff --git a/compiler/compiler.go b/compiler/compiler.go index 63fc214c99..0ae32cc94c 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -2181,7 +2181,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { return llvm.Value{}, b.makeError(expr.Pos(), "todo: indexaddr: "+ptrTyp.String()) } - // Make sure index is at least the size of uintptr becuase getelementptr + // Make sure index is at least the size of uintptr because getelementptr // assumes index is a signed integer. index = b.extendInteger(index, expr.Index.Type(), b.uintptrType) @@ -2557,7 +2557,7 @@ func (b *builder) createBinOp(op token.Token, typ, ytyp types.Type, x, y llvm.Va sizeY := b.targetData.TypeAllocSize(y.Type()) // Check if the shift is bigger than the bit-width of the shifted value. - // This is UB in LLVM, so it needs to be handled seperately. + // This is UB in LLVM, so it needs to be handled separately. // The Go spec indirectly defines the result as 0. // Negative shifts are handled earlier, so we can treat y as unsigned. overshifted := b.CreateICmp(llvm.IntUGE, y, llvm.ConstInt(y.Type(), 8*sizeX, false), "shift.overflow") diff --git a/compiler/llvmutil/llvm.go b/compiler/llvmutil/llvm.go index 5490106d84..d4ceee3fbc 100644 --- a/compiler/llvmutil/llvm.go +++ b/compiler/llvmutil/llvm.go @@ -1,5 +1,5 @@ // Package llvmutil contains utility functions used across multiple compiler -// packages. For example, they may be used by both the compiler pacakge and +// packages. For example, they may be used by both the compiler package and // transformation packages. // // Normally, utility packages are avoided. However, in this case, the utility @@ -28,7 +28,7 @@ func CreateEntryBlockAlloca(builder llvm.Builder, t llvm.Type, name string) llvm } // CreateTemporaryAlloca creates a new alloca in the entry block and adds -// lifetime start infromation in the IR signalling that the alloca won't be used +// lifetime start information in the IR signalling that the alloca won't be used // before this point. // // This is useful for creating temporary allocas for intrinsics. Don't forget to diff --git a/loader/goroot.go b/loader/goroot.go index ea2f705dc9..c7341fc7fd 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -214,7 +214,7 @@ func listGorootMergeLinks(goroot, tinygoroot string, overrides map[string]bool) return merges, nil } -// needsSyscallPackage returns whether the syscall package should be overriden +// needsSyscallPackage returns whether the syscall package should be overridden // with the TinyGo version. This is the case on some targets. func needsSyscallPackage(buildTags []string) bool { for _, tag := range buildTags { diff --git a/loader/loader.go b/loader/loader.go index e66812a8e4..a874291a6a 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -430,7 +430,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) { var files []*ast.File var fileErrs []error - // Parse all files (incuding CgoFiles). + // Parse all files (including CgoFiles). parseFile := func(file string) { if !filepath.IsAbs(file) { file = filepath.Join(p.Dir, file) diff --git a/src/internal/bytealg/bytealg.go b/src/internal/bytealg/bytealg.go index eb5fcbfbd4..4cf442e8e6 100644 --- a/src/internal/bytealg/bytealg.go +++ b/src/internal/bytealg/bytealg.go @@ -57,7 +57,7 @@ func Count(b []byte, c byte) int { // Count the number of instances of a byte in a string. func CountString(s string, c byte) int { // Use a simple implementation, as there is no intrinsic that does this like we want. - // Currently, the compiler does not generate zero-copy byte-string conversions, so this needs to be seperate from Count. + // Currently, the compiler does not generate zero-copy byte-string conversions, so this needs to be separate from Count. n := 0 for i := 0; i < len(s); i++ { if s[i] == c { @@ -216,7 +216,7 @@ func HashStrRev[T string | []byte](sep T) (uint32, uint32) { } // IndexRabinKarpBytes uses the Rabin-Karp search algorithm to return the index of the -// first occurence of substr in s, or -1 if not present. +// first occurrence of substr in s, or -1 if not present. // // This function was removed in Go 1.22. func IndexRabinKarpBytes(s, sep []byte) int { diff --git a/src/internal/task/task_stack.go b/src/internal/task/task_stack.go index 81e0f9ad76..f566de041b 100644 --- a/src/internal/task/task_stack.go +++ b/src/internal/task/task_stack.go @@ -44,7 +44,7 @@ func Current() *Task { // This function may only be called when running on a goroutine stack, not when running on the system stack or in an interrupt. func Pause() { // Check whether the canary (the lowest address of the stack) is still - // valid. If it is not, a stack overflow has occured. + // valid. If it is not, a stack overflow has occurred. if *currentTask.state.canaryPtr != stackCanary { runtimePanic("goroutine stack overflow") } diff --git a/src/machine/board_nano-33-ble.go b/src/machine/board_nano-33-ble.go index 911be0add5..758d5434e1 100644 --- a/src/machine/board_nano-33-ble.go +++ b/src/machine/board_nano-33-ble.go @@ -26,7 +26,7 @@ // SoftDevice (s140v7) must be flashed first to enable use of bluetooth on this board. // See https://github.com/tinygo-org/bluetooth // -// SoftDevice overwrites original bootloader and flashing method described above is not avalable anymore. +// SoftDevice overwrites original bootloader and flashing method described above is not available anymore. // Instead, please use debug probe and flash your code with "nano-33-ble-s140v7" target. package machine diff --git a/src/machine/machine_atsamd21.go b/src/machine/machine_atsamd21.go index 34e2f11edc..b6cd850628 100644 --- a/src/machine/machine_atsamd21.go +++ b/src/machine/machine_atsamd21.go @@ -1304,7 +1304,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { return byte(spi.Bus.DATA.Get()), nil } -// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read // interface, there must always be the same number of bytes written as bytes read. // The Tx method knows about this, and offers a few different ways of calling it. // @@ -1459,7 +1459,7 @@ func (tcc *TCC) Configure(config PWMConfig) error { for tcc.timer().SYNCBUSY.Get() != 0 { } - // Return any error that might have occured in the tcc.setPeriod call. + // Return any error that might have occurred in the tcc.setPeriod call. return err } @@ -1606,7 +1606,7 @@ func (tcc *TCC) Counter() uint32 { return tcc.timer().COUNT.Get() } -// Some constans to make pinTimerMapping below easier to read. +// Some constants to make pinTimerMapping below easier to read. const ( pinTCC0 = 1 pinTCC1 = 2 @@ -1759,7 +1759,7 @@ func (tcc *TCC) Set(channel uint8, value uint32) { } } -// EnterBootloader should perform a system reset in preperation +// EnterBootloader should perform a system reset in preparation // to switch to the bootloader to flash new firmware. func EnterBootloader() { arm.DisableInterrupts() diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go index 0d786dba72..d770417419 100644 --- a/src/machine/machine_atsamd51.go +++ b/src/machine/machine_atsamd51.go @@ -1587,7 +1587,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { return byte(spi.Bus.DATA.Get()), nil } -// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read // interface, there must always be the same number of bytes written as bytes read. // The Tx method knows about this, and offers a few different ways of calling it. // @@ -1720,7 +1720,7 @@ func (tcc *TCC) Configure(config PWMConfig) error { for tcc.timer().SYNCBUSY.Get() != 0 { } - // Return any error that might have occured in the tcc.setPeriod call. + // Return any error that might have occurred in the tcc.setPeriod call. return err } diff --git a/src/machine/machine_atsame5x_can.go b/src/machine/machine_atsame5x_can.go index b404bdaa68..498646980d 100644 --- a/src/machine/machine_atsame5x_can.go +++ b/src/machine/machine_atsame5x_can.go @@ -155,7 +155,7 @@ func (can *CAN) Configure(config CANConfig) error { } // Callbacks to be called for CAN.SetInterrupt(). Wre're using the magic -// constant 2 and 32 here beacuse th SAM E51/E54 has 2 CAN and 32 interrupt +// constant 2 and 32 here because the SAM E51/E54 has 2 CAN and 32 interrupt // sources. var ( canInstances [2]*CAN diff --git a/src/machine/machine_esp32.go b/src/machine/machine_esp32.go index 0bc17b4416..d6f6599288 100644 --- a/src/machine/machine_esp32.go +++ b/src/machine/machine_esp32.go @@ -460,7 +460,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { return byte(spi.Bus.W0.Get()), nil } -// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read // interface, there must always be the same number of bytes written as bytes read. // This is accomplished by sending zero bits if r is bigger than w or discarding // the incoming data if w is bigger than r. diff --git a/src/machine/machine_esp32c3.go b/src/machine/machine_esp32c3.go index 2745f7af21..b1ad0bc2fc 100644 --- a/src/machine/machine_esp32c3.go +++ b/src/machine/machine_esp32c3.go @@ -312,7 +312,7 @@ func (uart *UART) configure(config UARTConfig, regs registerSet) error { initUARTClock(uart.Bus, regs) - // - disbale TX/RX clock to make sure the UART transmitter or receiver is not at work during configuration + // - disable TX/RX clock to make sure the UART transmitter or receiver is not at work during configuration uart.Bus.SetCLK_CONF_TX_SCLK_EN(0) uart.Bus.SetCLK_CONF_RX_SCLK_EN(0) diff --git a/src/machine/machine_esp32c3_spi.go b/src/machine/machine_esp32c3_spi.go index 5e90c9f5bb..2fb8abc200 100644 --- a/src/machine/machine_esp32c3_spi.go +++ b/src/machine/machine_esp32c3_spi.go @@ -234,7 +234,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { return byte(spi.Bus.GetW0()), nil } -// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read // interface, there must always be the same number of bytes written as bytes read. // This is accomplished by sending zero bits if r is bigger than w or discarding // the incoming data if w is bigger than r. diff --git a/src/machine/machine_generic.go b/src/machine/machine_generic.go index e7d513ad1e..ec03a03051 100644 --- a/src/machine/machine_generic.go +++ b/src/machine/machine_generic.go @@ -66,7 +66,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { return spiTransfer(spi.Bus, w), nil } -// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read // interface, there must always be the same number of bytes written as bytes read. // The Tx method knows about this, and offers a few different ways of calling it. // diff --git a/src/machine/machine_nrf.go b/src/machine/machine_nrf.go index cc68c1d916..99c49d3cab 100644 --- a/src/machine/machine_nrf.go +++ b/src/machine/machine_nrf.go @@ -108,7 +108,7 @@ func (p Pin) Get() bool { func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { // Some variables to easily check whether a channel was already configured // as an event channel for the given pin. - // This is not just an optimization, this is requred: the datasheet says + // This is not just an optimization, this is required: the datasheet says // that configuring more than one channel for a given pin results in // unpredictable behavior. expectedConfigMask := uint32(nrf.GPIOTE_CONFIG_MODE_Msk | nrf.GPIOTE_CONFIG_PSEL_Msk) diff --git a/src/machine/machine_nrf51.go b/src/machine/machine_nrf51.go index ae6ff61a84..8d27e6fc55 100644 --- a/src/machine/machine_nrf51.go +++ b/src/machine/machine_nrf51.go @@ -133,7 +133,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { return byte(r), nil } -// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read // interface, there must always be the same number of bytes written as bytes read. // The Tx method knows about this, and offers a few different ways of calling it. // diff --git a/src/machine/machine_nrf52xxx.go b/src/machine/machine_nrf52xxx.go index 04fbaaf9dc..ee035b74d6 100644 --- a/src/machine/machine_nrf52xxx.go +++ b/src/machine/machine_nrf52xxx.go @@ -277,7 +277,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { return buf[0], err } -// Tx handles read/write operation for SPI interface. Since SPI is a syncronous +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous // write/read interface, there must always be the same number of bytes written // as bytes read. Therefore, if the number of bytes don't match it will be // padded until they fit: if len(w) > len(r) the extra bytes received will be diff --git a/src/machine/machine_rp2040_i2c.go b/src/machine/machine_rp2040_i2c.go index d51e14b8f2..1b66a86876 100644 --- a/src/machine/machine_rp2040_i2c.go +++ b/src/machine/machine_rp2040_i2c.go @@ -348,7 +348,7 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) { } if abort || last { // If the transaction was aborted or if it completed - // successfully wait until the STOP condition has occured. + // successfully wait until the STOP condition has occurred. // TODO Could there be an abort while waiting for the STOP // condition here? If so, additional code would be needed here diff --git a/src/machine/machine_rp2040_spi.go b/src/machine/machine_rp2040_spi.go index cb60fdbcb0..dbb063cb30 100644 --- a/src/machine/machine_rp2040_spi.go +++ b/src/machine/machine_rp2040_spi.go @@ -48,7 +48,7 @@ type SPI struct { Bus *rp.SPI0_Type } -// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read // interface, there must always be the same number of bytes written as bytes read. // The Tx method knows about this, and offers a few different ways of calling it. // diff --git a/src/machine/machine_stm32l5x2.go b/src/machine/machine_stm32l5x2.go index 2fb3a0d638..d439b8fd93 100644 --- a/src/machine/machine_stm32l5x2.go +++ b/src/machine/machine_stm32l5x2.go @@ -23,7 +23,7 @@ const APB2_TIM_FREQ = 110e6 // 110MHz // Configure the UART. func (uart *UART) configurePins(config UARTConfig) { if config.RX.getPort() == stm32.GPIOG || config.TX.getPort() == stm32.GPIOG { - // Enable VDDIO2 power supply, which is an independant power supply for the PGx pins + // Enable VDDIO2 power supply, which is an independent power supply for the PGx pins stm32.PWR.CR2.SetBits(stm32.PWR_CR2_IOSV) } diff --git a/src/machine/spi_tx.go b/src/machine/spi_tx.go index bb6305fe73..67076b2bdf 100644 --- a/src/machine/spi_tx.go +++ b/src/machine/spi_tx.go @@ -5,7 +5,7 @@ package machine -// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read // interface, there must always be the same number of bytes written as bytes read. // The Tx method knows about this, and offers a few different ways of calling it. // diff --git a/src/os/filesystem.go b/src/os/filesystem.go index f38b22660d..8b44367075 100644 --- a/src/os/filesystem.go +++ b/src/os/filesystem.go @@ -30,7 +30,7 @@ type Filesystem interface { // OpenFile opens the named file. OpenFile(name string, flag int, perm FileMode) (uintptr, error) - // Mkdir creates a new directoy with the specified permission (before + // Mkdir creates a new directory with the specified permission (before // umask). Some filesystems may not support directories or permissions. Mkdir(name string, perm FileMode) error diff --git a/src/os/seek_unix_bad.go b/src/os/seek_unix_bad.go index 1f09426aec..4243ced642 100644 --- a/src/os/seek_unix_bad.go +++ b/src/os/seek_unix_bad.go @@ -11,7 +11,7 @@ import ( // In particular, on i386 and arm, the function syscall.seek is missing, breaking syscall.Seek. // This in turn causes os.(*File).Seek, time, io/fs, and path/filepath to fail to link. // -// To temporarly let all the above at least link, provide a stub for syscall.seek. +// To temporarily let all the above at least link, provide a stub for syscall.seek. // This belongs in syscall, but on linux, we use upstream's syscall. // Remove once we support Go Assembly. // TODO: make this a non-stub, and thus fix the whole problem? diff --git a/src/runtime/chan.go b/src/runtime/chan.go index 6253722467..532b31cc59 100644 --- a/src/runtime/chan.go +++ b/src/runtime/chan.go @@ -76,7 +76,7 @@ func (b *channelBlockedList) remove(old *channelBlockedList) *channelBlockedList return b } -// detatch removes all other channel operations that are part of the same select statement. +// detach removes all other channel operations that are part of the same select statement. // If the input is not part of a select statement, this is a no-op. // This must be called before resuming any task blocked on a channel operation in order to ensure that it is not placed on the runqueue twice. func (b *channelBlockedList) detach() { @@ -88,7 +88,7 @@ func (b *channelBlockedList) detach() { // cancel all other channel operations that are part of this select statement switch { case &b.allSelectOps[i] == b: - // This entry is the one that was already detatched. + // This entry is the one that was already detached. continue case v.t == nil: // This entry is not used (nil channel). diff --git a/src/runtime/gc_blocks.go b/src/runtime/gc_blocks.go index 02a8277300..7db6b7a1cf 100644 --- a/src/runtime/gc_blocks.go +++ b/src/runtime/gc_blocks.go @@ -410,7 +410,7 @@ func GC() { runGC() } -// runGC performs a garbage colleciton cycle. It is the internal implementation +// runGC performs a garbage collection cycle. It is the internal implementation // of the runtime.GC() function. The difference is that it returns the number of // free bytes in the heap after the GC is finished. func runGC() (freeBytes uintptr) { @@ -424,7 +424,7 @@ func runGC() (freeBytes uintptr) { if baremetal && hasScheduler { // Channel operations in interrupts may move task pointers around while we are marking. - // Therefore we need to scan the runqueue seperately. + // Therefore we need to scan the runqueue separately. var markedTaskQueue task.Queue runqueueScan: for !runqueue.Empty() { diff --git a/src/runtime/gc_precise.go b/src/runtime/gc_precise.go index 4c7463b8ec..aa716585c8 100644 --- a/src/runtime/gc_precise.go +++ b/src/runtime/gc_precise.go @@ -7,7 +7,7 @@ // however use a bit more RAM to store the layout of each object. // // The pointer/non-pointer information for objects is stored in the first word -// of the object. It is described below but in essense it contains a bitstring +// of the object. It is described below but in essence it contains a bitstring // of a particular size. This size does not indicate the size of the object: // instead the allocated object is a multiple of the bitstring size. This is so // that arrays and slices can store the size of the object efficiently. The diff --git a/src/runtime/interrupt/interrupt.go b/src/runtime/interrupt/interrupt.go index a8cf6f4e98..e0376a52f9 100644 --- a/src/runtime/interrupt/interrupt.go +++ b/src/runtime/interrupt/interrupt.go @@ -26,7 +26,7 @@ func New(id int, handler func(Interrupt)) Interrupt // and use that in an Interrupt object. That way the compiler will be able to // optimize away all interrupt handles that are never used in a program. // This system only works when interrupts need to be enabled before use and this -// is done only through calling Enable() on this object. If interrups cannot +// is done only through calling Enable() on this object. If interrupts cannot // individually be enabled/disabled, the compiler should create a pseudo-call // (like runtime/interrupt.use()) that keeps the interrupt alive. type handle struct { diff --git a/src/runtime/interrupt/interrupt_esp32c3.go b/src/runtime/interrupt/interrupt_esp32c3.go index 7d9be3937e..b1a5bb1b3c 100644 --- a/src/runtime/interrupt/interrupt_esp32c3.go +++ b/src/runtime/interrupt/interrupt_esp32c3.go @@ -169,7 +169,7 @@ func handleInterrupt() { // save MSTATUS & MEPC, which could be overwritten by another CPU interrupt mstatus := riscv.MSTATUS.Get() mepc := riscv.MEPC.Get() - // Useing threshold to temporary disable this interrupts. + // Using threshold to temporary disable this interrupts. // FYI: using CPU interrupt enable bit make runtime to loose interrupts. reg := (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.INTERRUPT_CORE0.CPU_INT_PRI_0), interruptNumber*4)) thresholdSave := reg.Get() diff --git a/src/runtime/os_darwin.go b/src/runtime/os_darwin.go index d9894bc1a4..eeb192dda8 100644 --- a/src/runtime/os_darwin.go +++ b/src/runtime/os_darwin.go @@ -107,7 +107,7 @@ func findGlobals(found func(start, end uintptr)) { } } - // Move on to the next load command (wich may or may not be a + // Move on to the next load command (which may or may not be a // LC_SEGMENT_64). cmd = (*segmentLoadCommand)(unsafe.Add(unsafe.Pointer(cmd), cmd.cmdsize)) } diff --git a/src/runtime/runtime_atsamd21.go b/src/runtime/runtime_atsamd21.go index 166df214fa..d30fc7f6f0 100644 --- a/src/runtime/runtime_atsamd21.go +++ b/src/runtime/runtime_atsamd21.go @@ -318,7 +318,7 @@ func readRTC() uint32 { // ticks are in microseconds // Returns true if the timer completed. -// Returns false if another interrupt occured which requires an early return to scheduler. +// Returns false if another interrupt occurred which requires an early return to scheduler. func timerSleep(ticks uint32) bool { timerWakeup.Set(0) if ticks < 7 { diff --git a/src/runtime/runtime_atsamd51.go b/src/runtime/runtime_atsamd51.go index 586ab00911..151f815813 100644 --- a/src/runtime/runtime_atsamd51.go +++ b/src/runtime/runtime_atsamd51.go @@ -307,7 +307,7 @@ func readRTC() uint32 { // ticks are in microseconds // Returns true if the timer completed. -// Returns false if another interrupt occured which requires an early return to scheduler. +// Returns false if another interrupt occurred which requires an early return to scheduler. func timerSleep(ticks uint32) bool { timerWakeup.Set(0) if ticks < 8 { diff --git a/src/runtime/runtime_avrtiny.go b/src/runtime/runtime_avrtiny.go index 63eb5943a2..8ce324938c 100644 --- a/src/runtime/runtime_avrtiny.go +++ b/src/runtime/runtime_avrtiny.go @@ -115,7 +115,7 @@ func sleepTicks(d timeUnit) { // Sleep until the next interrupt happens. avr.Asm("sei\nsleep\ncli") if cmpMatch.Get() != 0 { - // The CMP interrupt occured, so we have slept long enough. + // The CMP interrupt occurred, so we have slept long enough. cmpMatch.Set(0) break } diff --git a/src/runtime/runtime_cortexm_hardfault.go b/src/runtime/runtime_cortexm_hardfault.go index 1e264c286a..b2449ed910 100644 --- a/src/runtime/runtime_cortexm_hardfault.go +++ b/src/runtime/runtime_cortexm_hardfault.go @@ -8,7 +8,7 @@ import ( // This function is called at HardFault. // Before this function is called, the stack pointer is reset to the initial -// stack pointer (loaded from addres 0x0) and the previous stack pointer is +// stack pointer (loaded from address 0x0) and the previous stack pointer is // passed as an argument to this function. This allows for easy inspection of // the stack the moment a HardFault occurs, but it means that the stack will be // corrupted by this function and thus this handler must not attempt to recover. diff --git a/src/runtime/runtime_fe310.go b/src/runtime/runtime_fe310.go index 01cc3ba119..f65c39a4df 100644 --- a/src/runtime/runtime_fe310.go +++ b/src/runtime/runtime_fe310.go @@ -85,7 +85,7 @@ func handleInterrupt() { riscv.MCAUSE.Set(0) } -// initPeripherals configures periperhals the way the runtime expects them. +// initPeripherals configures peripherals the way the runtime expects them. func initPeripherals() { // Configure PLL to output 320MHz. // R=2: divide 16MHz to 8MHz diff --git a/src/runtime/runtime_k210.go b/src/runtime/runtime_k210.go index 5998de69db..96cdd63408 100644 --- a/src/runtime/runtime_k210.go +++ b/src/runtime/runtime_k210.go @@ -105,7 +105,7 @@ func handleInterrupt() { riscv.MCAUSE.Set(0) } -// initPeripherals configures periperhals the way the runtime expects them. +// initPeripherals configures peripherals the way the runtime expects them. func initPeripherals() { // Enable APB0 clock. kendryte.SYSCTL.CLK_EN_CENT.SetBits(kendryte.SYSCTL_CLK_EN_CENT_APB0_CLK_EN) diff --git a/src/syscall/syscall_libc_wasip1.go b/src/syscall/syscall_libc_wasip1.go index 5242690cc4..42a5b096d9 100644 --- a/src/syscall/syscall_libc_wasip1.go +++ b/src/syscall/syscall_libc_wasip1.go @@ -344,7 +344,7 @@ func Fdclosedir(dir uintptr) (err error) { func Readdir(dir uintptr) (dirent *Dirent, err error) { // There might be a leftover errno value in the global variable, so we have // to clear it before calling readdir because we cannot know whether a nil - // return means that we reached EOF or that an error occured. + // return means that we reached EOF or that an error occurred. libcErrno = 0 dirent = libc_readdir(unsafe.Pointer(dir)) diff --git a/stacksize/stacksize.go b/stacksize/stacksize.go index 8cccbaec61..2cc099f2ac 100644 --- a/stacksize/stacksize.go +++ b/stacksize/stacksize.go @@ -224,7 +224,7 @@ func CallGraph(f *elf.File, callsIndirectFunction []string) (map[string][]*CallN for name, size := range knownFrameSizes { if sym, ok := symbolNames[name]; ok { if len(sym) > 1 { - return nil, fmt.Errorf("expected zero or one occurence of the symbol %s, found %d", name, len(sym)) + return nil, fmt.Errorf("expected zero or one occurrence of the symbol %s, found %d", name, len(sym)) } sym[0].FrameSize = size sym[0].FrameSizeType = Bounded diff --git a/transform/optimizer.go b/transform/optimizer.go index cd4d1ee883..4c0ccfc2ea 100644 --- a/transform/optimizer.go +++ b/transform/optimizer.go @@ -27,7 +27,7 @@ func OptimizePackage(mod llvm.Module, config *compileopts.Config) { // passes. // // Please note that some optimizations are not optional, thus Optimize must -// alwasy be run before emitting machine code. +// always be run before emitting machine code. func Optimize(mod llvm.Module, config *compileopts.Config) []error { optLevel, speedLevel, _ := config.OptLevel() From 22bf045c9a5194001b1d52c094094cf8926823a1 Mon Sep 17 00:00:00 2001 From: Patrick Ting Date: Fri, 29 Mar 2024 22:37:40 -0700 Subject: [PATCH 027/444] add stm32 nucleol476rg support --- GNUmakefile | 2 + src/machine/board_nucleol476rg.go | 105 ++++++++++++++++++++++++++++++ src/machine/machine_stm32l4x6.go | 25 +++++++ src/runtime/runtime_stm32l4x6.go | 29 +++++++++ targets/nucleo-l476rg.json | 12 ++++ targets/stm32l4x6.ld | 11 ++++ 6 files changed, 184 insertions(+) create mode 100644 src/machine/board_nucleol476rg.go create mode 100644 src/machine/machine_stm32l4x6.go create mode 100644 src/runtime/runtime_stm32l4x6.go create mode 100644 targets/nucleo-l476rg.json create mode 100644 targets/stm32l4x6.ld diff --git a/GNUmakefile b/GNUmakefile index 8c4f00b29d..c15b7724de 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -708,6 +708,8 @@ ifneq ($(STM32), 0) @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=nucleo-l432kc examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=nucleo-l476rg examples/blinky1 + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=nucleo-l552ze examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=nucleo-wl55jc examples/blinky1 diff --git a/src/machine/board_nucleol476rg.go b/src/machine/board_nucleol476rg.go new file mode 100644 index 0000000000..0a173afee9 --- /dev/null +++ b/src/machine/board_nucleol476rg.go @@ -0,0 +1,105 @@ +//go:build nucleol476rg + +// Schematic: https://www.st.com/resource/en/user_manual/um1724-stm32-nucleo64-boards-mb1136-stmicroelectronics.pdf +// Datasheet: https://www.st.com/resource/en/datasheet/stm32l476je.pdf + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +const ( + // Arduino Pins + A0 = PA0 + A1 = PA1 + A2 = PA4 + A3 = PB0 + A4 = PC1 + A5 = PC0 + + D0 = PA3 + D1 = PA2 + D2 = PA10 + D3 = PB3 + D4 = PB5 + D5 = PB4 + D6 = PB10 + D7 = PA8 + D8 = PA9 + D9 = PC7 + D10 = PB6 + D11 = PA7 + D12 = PA6 + D13 = PA5 + D14 = PB9 + D15 = PB8 +) + +// User LD2: the green LED is a user LED connected to ARDUINO® signal D13 corresponding +// to STM32 I/O PA5 (pin 21) or PB13 (pin 34) depending on the STM32 target. +const ( + LED = LED_BUILTIN + LED_BUILTIN = LED_GREEN + LED_GREEN = PA5 +) + +const ( + // This board does not have a user button, so + // use first GPIO pin by default + BUTTON = PA0 +) + +const ( + // UART pins + // PA2 and PA3 are connected to the ST-Link Virtual Com Port (VCP) + UART_TX_PIN = PA2 + UART_RX_PIN = PA3 + + // I2C pins + // With default solder bridge settings: + // PB8 / Arduino D5 / CN3 Pin 8 is SCL + // PB7 / Arduino D4 / CN3 Pin 7 is SDA + I2C0_SCL_PIN = PB8 + I2C0_SDA_PIN = PB9 + + // SPI pins + SPI1_SCK_PIN = PA5 + SPI1_SDI_PIN = PA6 + SPI1_SDO_PIN = PA7 + SPI0_SCK_PIN = SPI1_SCK_PIN + SPI0_SDI_PIN = SPI1_SDI_PIN + SPI0_SDO_PIN = SPI1_SDO_PIN +) + +var ( + // USART2 is the hardware serial port connected to the onboard ST-LINK + // debugger to be exposed as virtual COM port over USB on Nucleo boards. + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART2, + TxAltFuncSelector: AF7_USART1_2_3, + RxAltFuncSelector: AF7_USART1_2_3, + } + DefaultUART = UART1 + + // I2C1 is documented, alias to I2C0 as well + I2C1 = &I2C{ + Bus: stm32.I2C1, + AltFuncSelector: AF4_I2C1_2_3, + } + I2C0 = I2C1 + + // SPI1 is documented, alias to SPI0 as well + SPI1 = &SPI{ + Bus: stm32.SPI1, + AltFuncSelector: AF5_SPI1_2, + } + SPI0 = SPI1 +) + +func init() { + UART1.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART1.handleInterrupt) +} diff --git a/src/machine/machine_stm32l4x6.go b/src/machine/machine_stm32l4x6.go new file mode 100644 index 0000000000..cf546303af --- /dev/null +++ b/src/machine/machine_stm32l4x6.go @@ -0,0 +1,25 @@ +//go:build stm32l4x6 + +package machine + +// Peripheral abstraction layer for the stm32l4x6 + +func CPUFrequency() uint32 { + return 80e6 +} + +// Internal use: configured speed of the APB1 and APB2 timers, this should be kept +// in sync with any changes to runtime package which configures the oscillators +// and clock frequencies +const APB1_TIM_FREQ = 80e6 // 80MHz +const APB2_TIM_FREQ = 80e6 // 80MHz + +//---------- I2C related code + +// Gets the value for TIMINGR register +func (i2c *I2C) getFreqRange() uint32 { + // This is a 'magic' value calculated by STM32CubeMX + // for 80MHz PCLK1. + // TODO: Do calculations based on PCLK1 + return 0x10909CEC +} diff --git a/src/runtime/runtime_stm32l4x6.go b/src/runtime/runtime_stm32l4x6.go new file mode 100644 index 0000000000..6a56d8bd18 --- /dev/null +++ b/src/runtime/runtime_stm32l4x6.go @@ -0,0 +1,29 @@ +//go:build stm32 && stm32l4x6 + +package runtime + +import ( + "device/stm32" +) + +/* +clock settings + + +-------------+-----------+ + | LSE | 32.768khz | + | SYSCLK | 80mhz | + | HCLK | 80mhz | + | APB1(PCLK1) | 80mhz | + | APB2(PCLK2) | 80mhz | + +-------------+-----------+ +*/ +const ( + HSE_STARTUP_TIMEOUT = 0x0500 + PLL_M = 1 + PLL_N = 40 + PLL_P = RCC_PLLP_DIV7 + PLL_Q = RCC_PLLQ_DIV2 + PLL_R = RCC_PLLR_DIV2 + + MSIRANGE = stm32.RCC_CR_MSIRANGE_Range4M +) diff --git a/targets/nucleo-l476rg.json b/targets/nucleo-l476rg.json new file mode 100644 index 0000000000..73eddee813 --- /dev/null +++ b/targets/nucleo-l476rg.json @@ -0,0 +1,12 @@ +{ + "inherits": ["cortex-m4"], + "build-tags": ["nucleol476rg", "stm32l476", "stm32l4x6", "stm32l4", "stm32"], + "serial": "uart", + "linkerscript": "targets/stm32l4x6.ld", + "extra-files": [ + "src/device/stm32/stm32l4x6.s" + ], + "flash-method": "openocd", + "openocd-interface": "stlink-v2-1", + "openocd-target": "stm32l4x" + } diff --git a/targets/stm32l4x6.ld b/targets/stm32l4x6.ld new file mode 100644 index 0000000000..4f1ed77322 --- /dev/null +++ b/targets/stm32l4x6.ld @@ -0,0 +1,11 @@ + +MEMORY +{ + FLASH_TEXT (rx) : ORIGIN = 0x08000000, LENGTH = 1024K + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 96K + RAM2 (xrw) : ORIGIN = 0x10000000, LENGTH = 32K +} + +_stack_size = 4K; + +INCLUDE "targets/arm.ld" From 1154212c15e6e97048e122068730dab5a1a9427f Mon Sep 17 00:00:00 2001 From: hongkuang Date: Sun, 7 Apr 2024 16:17:09 +0800 Subject: [PATCH 028/444] chore: fix function names in comment Signed-off-by: hongkuang --- cgo/cgo_test.go | 2 +- src/machine/machine_atsamd21.go | 2 +- src/machine/machine_atsamd51.go | 2 +- src/os/tempfile.go | 2 +- src/reflect/type.go | 2 +- src/sync/mutex_test.go | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cgo/cgo_test.go b/cgo/cgo_test.go index 5425d2779b..60af3e6f2a 100644 --- a/cgo/cgo_test.go +++ b/cgo/cgo_test.go @@ -216,7 +216,7 @@ func (i simpleImporter) Import(path string) (*types.Package, error) { } } -// formatDiagnostics formats the error message to be an indented comment. It +// formatDiagnostic formats the error message to be an indented comment. It // also fixes Windows path name issues (backward slashes). func formatDiagnostic(err error) string { msg := err.Error() diff --git a/src/machine/machine_atsamd21.go b/src/machine/machine_atsamd21.go index b6cd850628..755bedf9a2 100644 --- a/src/machine/machine_atsamd21.go +++ b/src/machine/machine_atsamd21.go @@ -1657,7 +1657,7 @@ var pinTimerMapping = [...]uint8{ PB30 / 2: pinTCC0Ch0 | pinTCC1Ch2<<4, } -// findPinPadMapping returns the pin mode (PinTCC or PinTCCAlt) and the channel +// findPinTimerMapping returns the pin mode (PinTCC or PinTCCAlt) and the channel // number for a given timer and pin. A zero PinMode is returned if no mapping // could be found. func findPinTimerMapping(timer uint8, pin Pin) (PinMode, uint8) { diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go index d770417419..fdc368a63d 100644 --- a/src/machine/machine_atsamd51.go +++ b/src/machine/machine_atsamd51.go @@ -1928,7 +1928,7 @@ var pinTimerMapping = [...]struct{ F, G uint8 }{ PB02 / 2: {pinTCC2_2, 0}, } -// findPinPadMapping returns the pin mode (PinTCCF or PinTCCG) and the channel +// findPinTimerMapping returns the pin mode (PinTCCF or PinTCCG) and the channel // number for a given timer and pin. A zero PinMode is returned if no mapping // could be found. func findPinTimerMapping(timer uint8, pin Pin) (PinMode, uint8) { diff --git a/src/os/tempfile.go b/src/os/tempfile.go index 383d4c1815..5a94a6e2aa 100644 --- a/src/os/tempfile.go +++ b/src/os/tempfile.go @@ -142,7 +142,7 @@ func joinPath(dir, name string) string { return dir + string(PathSeparator) + name } -// LastIndexByte from the strings package. +// lastIndex from the strings package. func lastIndex(s string, sep byte) int { for i := len(s) - 1; i >= 0; i-- { if s[i] == sep { diff --git a/src/reflect/type.go b/src/reflect/type.go index 56d4767f71..1356f67cdd 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -980,7 +980,7 @@ func (t *rawType) Comparable() bool { return (t.meta & flagComparable) == flagComparable } -// isbinary() returns if the hashmapAlgorithmBinary functions can be used on this type +// isBinary returns if the hashmapAlgorithmBinary functions can be used on this type func (t *rawType) isBinary() bool { return (t.meta & flagIsBinary) == flagIsBinary } diff --git a/src/sync/mutex_test.go b/src/sync/mutex_test.go index accb01c975..1e398e5aca 100644 --- a/src/sync/mutex_test.go +++ b/src/sync/mutex_test.go @@ -196,7 +196,7 @@ func TestRWMutexWriteToRead(t *testing.T) { } } -// TestRWMutexWriteToRead tests the transition from a read lock to a write lock while contended. +// TestRWMutexReadToWrite tests the transition from a read lock to a write lock while contended. func TestRWMutexReadToWrite(t *testing.T) { // Create a new RWMutex and read-lock it several times. const n = 3 From 3d433fe9f15dc6cff400ce7fd03cf15ad55f64fc Mon Sep 17 00:00:00 2001 From: dkegel-fastly <79674949+dkegel-fastly@users.noreply.github.com> Date: Mon, 22 Apr 2024 11:10:13 -0700 Subject: [PATCH 029/444] Lint: lint and fix src/{os,reflect} (#4228) * lint: expand to src/{os,reflect}, fix or suppress what it found * internal/tools/tools.go: the tools idiom requires a build tag guard to avoid go test complaints --- GNUmakefile | 10 ++++++---- internal/tools/tools.go | 2 ++ revive.toml | 5 +++++ src/os/file.go | 1 + src/os/path_unix.go | 4 ++-- src/os/path_windows.go | 4 ++-- src/reflect/value_test.go | 4 ++-- 7 files changed, 20 insertions(+), 10 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index c15b7724de..8987fcaa55 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -915,7 +915,9 @@ deb: build/release endif lint: - # Only run on compiler dir for now, expand as we clean up other dirs - # This obviously won't scale, but it's a start, and it's fast - go run github.com/mgechev/revive --version - go run github.com/mgechev/revive --config revive.toml compiler/... + go run github.com/mgechev/revive -version + # TODO: lint more directories! + # revive.toml isn't flexible enough to filter out just one kind of error from a checker, so do it with grep here. + # Can't use grep with friendly formatter. Plain output isn't too bad, though. + # Use 'grep .' to get rid of stray blank line + go run github.com/mgechev/revive -config revive.toml compiler/... src/{os,reflect}/*.go | grep -v "should have comment or be unexported" | grep '.' | awk '{print}; END {exit NR>0}' diff --git a/internal/tools/tools.go b/internal/tools/tools.go index c22654a78b..60e6a8cb61 100644 --- a/internal/tools/tools.go +++ b/internal/tools/tools.go @@ -1,3 +1,5 @@ +//go:build tools + // Install linter versions specified in go.mod // See https://marcofranssen.nl/manage-go-tools-via-go-modules for idom package tools diff --git a/revive.toml b/revive.toml index f09127ca6a..37778be501 100644 --- a/revive.toml +++ b/revive.toml @@ -6,15 +6,19 @@ warningCode = 0 # Enable these as we fix them [rule.blank-imports] + Exclude=["src/os/file_other.go"] [rule.context-as-argument] [rule.context-keys-type] [rule.dot-imports] + Exclude=["**/*_test.go"] [rule.error-return] [rule.error-strings] [rule.error-naming] [rule.exported] + Exclude=["src/reflect/*.go"] [rule.increment-decrement] [rule.var-naming] + Exclude=["src/os/*.go"] [rule.var-declaration] #[rule.package-comments] [rule.range] @@ -27,4 +31,5 @@ warningCode = 0 [rule.superfluous-else] #[rule.unused-parameter] [rule.unreachable-code] + Exclude=["src/reflect/visiblefields_test.go", "src/reflect/all_test.go"] #[rule.redefines-builtin-id] diff --git a/src/os/file.go b/src/os/file.go index ff62899bd9..e003d47dab 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -310,6 +310,7 @@ func (e *LinkError) Unwrap() error { return e.Err } +// OpenFile flag values. const ( O_RDONLY int = syscall.O_RDONLY O_WRONLY int = syscall.O_WRONLY diff --git a/src/os/path_unix.go b/src/os/path_unix.go index 97a028c5c5..9bb5c726b8 100644 --- a/src/os/path_unix.go +++ b/src/os/path_unix.go @@ -7,8 +7,8 @@ package os const ( - PathSeparator = '/' // OS-specific path separator - PathListSeparator = ':' // OS-specific path list separator + PathSeparator = '/' // PathSeparator is the OS-specific path separator + PathListSeparator = ':' // PathListSeparator is the OS-specific path list separator ) // IsPathSeparator reports whether c is a directory separator character. diff --git a/src/os/path_windows.go b/src/os/path_windows.go index a96245f358..7118c45b5a 100644 --- a/src/os/path_windows.go +++ b/src/os/path_windows.go @@ -5,8 +5,8 @@ package os const ( - PathSeparator = '\\' // OS-specific path separator - PathListSeparator = ';' // OS-specific path list separator + PathSeparator = '\\' // PathSeparator is the OS-specific path separator + PathListSeparator = ';' // PathListSeparator is the OS-specific path list separator ) // IsPathSeparator reports whether c is a directory separator character. diff --git a/src/reflect/value_test.go b/src/reflect/value_test.go index 6641750b9b..40f0919bdc 100644 --- a/src/reflect/value_test.go +++ b/src/reflect/value_test.go @@ -489,7 +489,7 @@ func TestTinyStruct(t *testing.T) { func TestTinyZero(t *testing.T) { s := "hello, world" - var sptr *string = &s + sptr := &s v := ValueOf(&sptr).Elem() v.Set(Zero(v.Type())) @@ -535,7 +535,7 @@ func TestTinyAddr(t *testing.T) { } func TestTinyNilType(t *testing.T) { - var a any = nil + var a any typ := TypeOf(a) if typ != nil { t.Errorf("Type of any{nil} is not nil") From 797a5a203171316c45ff54fa40d6d6ecef59bb97 Mon Sep 17 00:00:00 2001 From: sago35 Date: Mon, 15 Apr 2024 09:20:56 +0900 Subject: [PATCH 030/444] machine/thingplus_rp2040, machine/waveshare-rp2040-zero:add WS2812 definition --- src/machine/board_thingplus_rp2040.go | 6 +++++- src/machine/board_waveshare-rp2040-zero.go | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/machine/board_thingplus_rp2040.go b/src/machine/board_thingplus_rp2040.go index 7f005412f1..48292d261e 100644 --- a/src/machine/board_thingplus_rp2040.go +++ b/src/machine/board_thingplus_rp2040.go @@ -46,7 +46,11 @@ const ( A3 = GPIO29 ) -const LED = GPIO25 +// Onboard LEDs +const ( + LED = GPIO25 + WS2812 = GPIO8 +) // I2C Pins. const ( diff --git a/src/machine/board_waveshare-rp2040-zero.go b/src/machine/board_waveshare-rp2040-zero.go index ea9223c154..00ddc53a51 100644 --- a/src/machine/board_waveshare-rp2040-zero.go +++ b/src/machine/board_waveshare-rp2040-zero.go @@ -52,6 +52,7 @@ const ( // Onboard LEDs const ( NEOPIXEL = GPIO16 + WS2812 = GPIO16 ) // I2C pins From f986ea84aca1420698da56667f57e69ec585530a Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 26 Apr 2024 13:20:26 +0200 Subject: [PATCH 031/444] rp2040: fix timeUnit type It should be int64 like for all other systems, not uint64. --- src/runtime/runtime_rp2040.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/runtime_rp2040.go b/src/runtime/runtime_rp2040.go index e5659d625d..8a8c343c58 100644 --- a/src/runtime/runtime_rp2040.go +++ b/src/runtime/runtime_rp2040.go @@ -14,7 +14,7 @@ func machineTicks() uint64 // machineLightSleep is provided by package machine. func machineLightSleep(uint64) -type timeUnit uint64 +type timeUnit int64 // ticks returns the number of ticks (microseconds) elapsed since power up. func ticks() timeUnit { From 50f700ddb58f4f920ddf3e377537ee545f26bb26 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Fri, 26 Apr 2024 16:18:56 +0200 Subject: [PATCH 032/444] runtime: skip negative sleep durations in sleepTicks sleepTicks calls machineLightSleep with the duration cast to an unsigned integer. This underflow for negative durations. Signed-off-by: Elias Naur --- src/runtime/runtime_rp2040.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/runtime_rp2040.go b/src/runtime/runtime_rp2040.go index 8a8c343c58..fa048117b9 100644 --- a/src/runtime/runtime_rp2040.go +++ b/src/runtime/runtime_rp2040.go @@ -31,7 +31,7 @@ func nanosecondsToTicks(ns int64) timeUnit { } func sleepTicks(d timeUnit) { - if d == 0 { + if d <= 0 { return } From 7748c4922e426798ebb878eb48ba0d2d0dc0db09 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 27 Apr 2024 10:03:59 +0200 Subject: [PATCH 033/444] compileopts: fix race condition Appending to a slice can lead to a race condition if the capacity of the slice is larger than the length (and therefore the returned slice will overwrite some fields in the underlying array). This fixes that race condition. I don't know how severe this particular one is. It shouldn't be that severe, but it is a bug and it makes it easier to hunt for possibly more serious race conditions. --- compileopts/config.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compileopts/config.go b/compileopts/config.go index 98dd307387..ab6de4e44c 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -74,7 +74,8 @@ func (c *Config) GOARM() string { // BuildTags returns the complete list of build tags used during this build. func (c *Config) BuildTags() []string { - tags := append(c.Target.BuildTags, []string{ + tags := append([]string(nil), c.Target.BuildTags...) // copy slice (avoid a race) + tags = append(tags, []string{ "tinygo", // that's the compiler "purego", // to get various crypto packages to work "math_big_pure_go", // to get math/big to work From 2b3b870bfe63ab0b8e866df3ceef9e0c8a6e6c56 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sat, 9 Mar 2024 11:41:45 +0000 Subject: [PATCH 034/444] mksnanov3: limit programming speed to 1800 kHz Both of my development boards exhibit stability problems when programming at the default 4000 kHz speed of the target/stmf32d4x OpenOCD configuration. Note that the speed limit must be set by an event handler, because it is hard-coded by a similar event handler in stmf32f4x.cfg: $_TARGETNAME configure -event reset-init { # Configure PLL to boost clock to HSI x 4 (64 MHz) ... # Boost JTAG frequency adapter speed 8000 <-- resolves to 4000 kHz by the SWD interface } While here, replace the reference to the deprecated "stlink-v2" configuration. --- targets/mksnanov3.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/targets/mksnanov3.json b/targets/mksnanov3.json index 9b202d3abc..fc57a71d9b 100644 --- a/targets/mksnanov3.json +++ b/targets/mksnanov3.json @@ -7,6 +7,7 @@ "src/device/stm32/stm32f407.s" ], "flash-method": "openocd", - "openocd-interface": "stlink-v2", - "openocd-target": "stm32f4x" + "openocd-interface": "stlink", + "openocd-target": "stm32f4x", + "openocd-commands": ["stm32f4x.cpu configure -event reset-init { adapter speed 1800 }"] } From 441dfc98d7f9af9e9c03675dd223222c30292f96 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 28 Apr 2024 14:31:31 +0200 Subject: [PATCH 035/444] simulator: fix I2C support - Add ReadRegister and WriteRegister (because they're still used by some packages). - Fix out-of-bounds panic in I2C.Tx when either w or r has a length of zero (or is nil). --- src/machine/i2c.go | 2 +- src/machine/machine_generic.go | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/machine/i2c.go b/src/machine/i2c.go index 5c4d1a5c0b..96c192cb82 100644 --- a/src/machine/i2c.go +++ b/src/machine/i2c.go @@ -1,4 +1,4 @@ -//go:build atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || mimxrt1062 || (esp32c3 && !m5stamp_c3) +//go:build !baremetal || atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || mimxrt1062 || (esp32c3 && !m5stamp_c3) package machine diff --git a/src/machine/machine_generic.go b/src/machine/machine_generic.go index ec03a03051..fa12b4b53d 100644 --- a/src/machine/machine_generic.go +++ b/src/machine/machine_generic.go @@ -150,7 +150,17 @@ func (i2c *I2C) SetBaudRate(br uint32) error { // Tx does a single I2C transaction at the specified address. func (i2c *I2C) Tx(addr uint16, w, r []byte) error { - i2cTransfer(i2c.Bus, &w[0], len(w), &r[0], len(r)) + var wptr, rptr *byte + var wlen, rlen int + if len(w) != 0 { + wptr = &w[0] + wlen = len(w) + } + if len(r) != 0 { + rptr = &r[0] + rlen = len(r) + } + i2cTransfer(i2c.Bus, wptr, wlen, rptr, rlen) // TODO: do something with the returned error code. return nil } From d419cc11bf095d146dd2638ca99c0b278a6555ff Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 28 Apr 2024 14:36:27 +0200 Subject: [PATCH 036/444] simulator: add support for GetRNG This is needed to be able to simulate the Gopher Badge code: https://github.com/conejoninja/gopherbadge --- src/machine/machine_generic.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/machine/machine_generic.go b/src/machine/machine_generic.go index fa12b4b53d..4f040fdbbd 100644 --- a/src/machine/machine_generic.go +++ b/src/machine/machine_generic.go @@ -2,6 +2,10 @@ package machine +import ( + "crypto/rand" +) + // Dummy machine package that calls out to external functions. const deviceName = "generic" @@ -253,3 +257,13 @@ var ( sercomSPIM6 = SPI{6} sercomSPIM7 = SPI{7} ) + +// GetRNG returns 32 bits of random data from the WASI random source. +func GetRNG() (uint32, error) { + var buf [4]byte + _, err := rand.Read(buf[:]) + if err != nil { + return 0, err + } + return uint32(buf[0])<<0 | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24, nil +} From da23cdeae4e80767cb2e8025269bd844c096ead1 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Mon, 29 Apr 2024 16:26:45 +0200 Subject: [PATCH 037/444] make: use release esp-17.0.1_20240419 tag for source from espressif LLVM fork for reproducible builds Signed-off-by: deadprogram --- GNUmakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GNUmakefile b/GNUmakefile index 8987fcaa55..384a933d3d 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -239,7 +239,7 @@ gen-device-renesas: build/gen-device-svd # Get LLVM sources. $(LLVM_PROJECTDIR)/llvm: - git clone -b xtensa_release_17.0.1 --depth=1 https://github.com/espressif/llvm-project $(LLVM_PROJECTDIR) + git clone -b esp-17.0.1_20240419 --depth=1 https://github.com/espressif/llvm-project $(LLVM_PROJECTDIR) llvm-source: $(LLVM_PROJECTDIR)/llvm # Configure LLVM. From ad0af607ef1d5d75639c3836f66994fc5640e276 Mon Sep 17 00:00:00 2001 From: Dan Kegel Date: Sun, 28 Apr 2024 07:54:02 -0700 Subject: [PATCH 038/444] Add 'make spell' target, fix what it finds. In .go files, only checks comments. --- GNUmakefile | 4 ++++ builder/cc1as.h | 2 +- go.mod | 2 ++ go.sum | 4 ++++ internal/tools/tools.go | 1 + src/internal/task/task_stack_avr.S | 2 +- src/internal/task/task_stack_esp32.S | 2 +- 7 files changed, 14 insertions(+), 3 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 384a933d3d..379f6a7b22 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -921,3 +921,7 @@ lint: # Can't use grep with friendly formatter. Plain output isn't too bad, though. # Use 'grep .' to get rid of stray blank line go run github.com/mgechev/revive -config revive.toml compiler/... src/{os,reflect}/*.go | grep -v "should have comment or be unexported" | grep '.' | awk '{print}; END {exit NR>0}' + +spell: + # Check for typos in comments. Skip git submodules etc. + go run github.com/client9/misspell/cmd/misspell -i 'ackward,devided,extint,inbetween,programmmer,rela' $$( find . -depth 1 -type d | egrep -w -v 'lib|llvm|src/net' ) diff --git a/builder/cc1as.h b/builder/cc1as.h index 4b22fc3e81..423a916a34 100644 --- a/builder/cc1as.h +++ b/builder/cc1as.h @@ -93,7 +93,7 @@ struct AssemblerInvocation { EmitDwarfUnwindType EmitDwarfUnwind; // Whether to emit compact-unwind for non-canonical entries. - // Note: maybe overriden by other constraints. + // Note: may be overridden by other constraints. unsigned EmitCompactUnwindNonCanonical : 1; /// The name of the relocation model to use. diff --git a/go.mod b/go.mod index d5294c2175..bf6d87ae6e 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 github.com/chromedp/cdproto v0.0.0-20220113222801-0725d94bb6ee github.com/chromedp/chromedp v0.7.6 + github.com/client9/misspell v0.3.4 github.com/gofrs/flock v0.8.1 github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf @@ -16,6 +17,7 @@ require ( github.com/mgechev/revive v1.3.7 github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 go.bug.st/serial v1.6.0 + golang.org/x/net v0.20.0 golang.org/x/sys v0.16.0 golang.org/x/tools v0.17.0 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index fd5a55473f..7746bf7eb1 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,8 @@ github.com/chromedp/chromedp v0.7.6 h1:2juGaktzjwULlsn+DnvIZXFUckEp5xs+GOBroaea+ github.com/chromedp/chromedp v0.7.6/go.mod h1:ayT4YU/MGAALNfOg9gNrpGSAdnU51PMx+FCeuT1iXzo= github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -81,6 +83,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl go.bug.st/serial v1.6.0 h1:mAbRGN4cKE2J5gMwsMHC2KQisdLRQssO9WSM+rbZJ8A= go.bug.st/serial v1.6.0/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= 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= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/internal/tools/tools.go b/internal/tools/tools.go index 60e6a8cb61..cda6891015 100644 --- a/internal/tools/tools.go +++ b/internal/tools/tools.go @@ -5,5 +5,6 @@ package tools import ( + _ "github.com/client9/misspell" _ "github.com/mgechev/revive" ) diff --git a/src/internal/task/task_stack_avr.S b/src/internal/task/task_stack_avr.S index 2e68b17545..d8aed8b96b 100644 --- a/src/internal/task/task_stack_avr.S +++ b/src/internal/task/task_stack_avr.S @@ -28,7 +28,7 @@ tinygo_startTask: // After return, exit this goroutine. This is a tail call. #if __AVR_ARCH__ == 2 || __AVR_ARCH__ == 25 // Small memory devices (≤8kB flash) that do not have the long call - // instruction availble will need to use rcall instead. + // instruction available will need to use rcall instead. // Note that they will probably not be able to run more than the main // goroutine anyway, but this file is compiled for all AVRs so it needs to // compile at least. diff --git a/src/internal/task/task_stack_esp32.S b/src/internal/task/task_stack_esp32.S index c2e4acc2c9..fe0afe98d6 100644 --- a/src/internal/task/task_stack_esp32.S +++ b/src/internal/task/task_stack_esp32.S @@ -78,7 +78,7 @@ tinygo_swapTask: // Switch to the new stack pointer (newStack). mov.n sp, a2 - // Load a0, which is the previous return addres from before the previous + // Load a0, which is the previous return address from before the previous // switch or the constructed return address to tinygo_startTask. This // register also stores the parent register window. l32i.n a0, sp, 0 From 293b620faf0bed2c7dbacde0efe84ed22a76788d Mon Sep 17 00:00:00 2001 From: deadprogram Date: Mon, 29 Apr 2024 21:08:51 +0200 Subject: [PATCH 039/444] build: update llvm cache to use tagged LLVM source version Signed-off-by: deadprogram --- .github/workflows/build-macos.yml | 4 ++-- .github/workflows/linux.yml | 12 ++++++------ .github/workflows/nix.yml | 2 +- .github/workflows/sizediff.yml | 4 ++-- .github/workflows/windows.yml | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 29e11284bd..1fefa27bf5 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -43,7 +43,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-17-${{ matrix.os }}-v1 + key: llvm-source-17-${{ matrix.os }}-v2 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -68,7 +68,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-17-${{ matrix.os }}-v1 + key: llvm-build-17-${{ matrix.os }}-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 9d9a779a9c..6decf0f7cf 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -43,7 +43,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-17-linux-alpine-v1 + key: llvm-source-17-linux-alpine-v2 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -68,7 +68,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-17-linux-alpine-v1 + key: llvm-build-17-linux-alpine-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -196,7 +196,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-17-linux-asserts-v1 + key: llvm-source-17-linux-asserts-v2 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -221,7 +221,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-17-linux-asserts-v1 + key: llvm-build-17-linux-asserts-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -309,7 +309,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-17-linux-v1 + key: llvm-source-17-linux-v2 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -334,7 +334,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-17-linux-${{ matrix.goarch }}-v1 + key: llvm-build-17-linux-${{ matrix.goarch }}-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 90695cebc6..2eae94fc04 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -24,7 +24,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-17-linux-nix-v1 + key: llvm-source-17-linux-nix-v2 path: | llvm-project/compiler-rt - name: Download LLVM source diff --git a/.github/workflows/sizediff.yml b/.github/workflows/sizediff.yml index c9c027bf0f..4146673aee 100644 --- a/.github/workflows/sizediff.yml +++ b/.github/workflows/sizediff.yml @@ -28,7 +28,7 @@ jobs: uses: actions/cache@v4 id: cache-llvm-source with: - key: llvm-source-17-sizediff-v1 + key: llvm-source-17-sizediff-v2 path: | llvm-project/compiler-rt - name: Download LLVM source @@ -37,7 +37,7 @@ jobs: - name: Cache Go uses: actions/cache@v4 with: - key: go-cache-linux-sizediff-v1-${{ hashFiles('go.mod') }} + key: go-cache-linux-sizediff-v2-${{ hashFiles('go.mod') }} path: | ~/.cache/go-build ~/go/pkg/mod diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d03d2b5073..013ff1a276 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -41,7 +41,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-17-windows-v1 + key: llvm-source-17-windows-v2 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -66,7 +66,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-17-windows-v1 + key: llvm-build-17-windows-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' From 72f30ca6ec13b8c5eaa74e4881839fd7360f37ed Mon Sep 17 00:00:00 2001 From: Christian Stewart Date: Sat, 4 May 2024 16:44:44 -0700 Subject: [PATCH 040/444] Makefile: add lld to list of build targets for wasm-ld The current list of targets does not build wasm-ld. wasm-ld is a symlink created in ./llvm-build/bin pointing to ./lld. Add the "lld" build target to get wasm-ld into ./llvm-build/bin. Fixes a build failure where wasm-ld is not found. Signed-off-by: Christian Stewart --- GNUmakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GNUmakefile b/GNUmakefile index 379f6a7b22..5496cbb0cf 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -166,7 +166,7 @@ LIB_NAMES = clang $(CLANG_LIB_NAMES) $(LLD_LIB_NAMES) $(EXTRA_LIB_NAMES) # library path (for ninja). # This list also includes a few tools that are necessary as part of the full # TinyGo build. -NINJA_BUILD_TARGETS = clang llvm-config llvm-ar llvm-nm $(addprefix lib/lib,$(addsuffix .a,$(LIB_NAMES))) +NINJA_BUILD_TARGETS = clang llvm-config llvm-ar llvm-nm lld $(addprefix lib/lib,$(addsuffix .a,$(LIB_NAMES))) # For static linking. ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","") From 7d6b667bba801205e32c5ff3b460100b0409fda2 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Mon, 6 May 2024 19:29:11 +0200 Subject: [PATCH 041/444] machine/stm32: add i2c Frequency and SetBaudRate() function for boards that were missing implementation Signed-off-by: deadprogram --- src/machine/machine_stm32_i2c_revb.go | 34 ++++++++++++++++++++++----- src/machine/machine_stm32f7x2.go | 15 ++++++++++-- src/machine/machine_stm32l0.go | 17 +++++++++++--- src/machine/machine_stm32l4x2.go | 17 +++++++++++--- src/machine/machine_stm32l4x5.go | 15 ++++++++++-- src/machine/machine_stm32l4x6.go | 15 ++++++++++-- src/machine/machine_stm32l5x2.go | 15 ++++++++++-- src/machine/machine_stm32wlx.go | 15 ++++++++++-- 8 files changed, 121 insertions(+), 22 deletions(-) diff --git a/src/machine/machine_stm32_i2c_revb.go b/src/machine/machine_stm32_i2c_revb.go index 9253902940..006661f9ab 100644 --- a/src/machine/machine_stm32_i2c_revb.go +++ b/src/machine/machine_stm32_i2c_revb.go @@ -45,11 +45,21 @@ type I2C struct { // I2CConfig is used to store config info for I2C. type I2CConfig struct { - SCL Pin - SDA Pin + Frequency uint32 + SCL Pin + SDA Pin } func (i2c *I2C) Configure(config I2CConfig) error { + // Frequency range + switch config.Frequency { + case 0: + config.Frequency = 100 * KHz + case 10 * KHz, 100 * KHz, 400 * KHz, 500 * KHz: + default: + return errI2CNotImplemented + } + // disable I2C interface before any configuration changes i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_PE) @@ -63,8 +73,7 @@ func (i2c *I2C) Configure(config I2CConfig) error { } i2c.configurePins(config) - // Frequency range - i2c.Bus.TIMINGR.Set(i2c.getFreqRange()) + i2c.Bus.TIMINGR.Set(i2c.getFreqRange(config.Frequency)) // Disable Own Address1 before set the Own Address1 configuration i2c.Bus.OAR1.ClearBits(stm32.I2C_OAR1_OA1EN) @@ -86,8 +95,21 @@ func (i2c *I2C) Configure(config I2CConfig) error { // SetBaudRate sets the communication speed for I2C. func (i2c *I2C) SetBaudRate(br uint32) error { - // TODO: implement - return errI2CNotImplemented + switch br { + case 10 * KHz, 100 * KHz, 400 * KHz, 500 * KHz: + default: + return errI2CNotImplemented + } + + // disable I2C interface before any configuration changes + i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_PE) + + i2c.Bus.TIMINGR.Set(i2c.getFreqRange(br)) + + // Disable Generalcall and NoStretch, Enable peripheral + i2c.Bus.CR1.Set(stm32.I2C_CR1_PE) + + return nil } func (i2c *I2C) Tx(addr uint16, w, r []byte) error { diff --git a/src/machine/machine_stm32f7x2.go b/src/machine/machine_stm32f7x2.go index 8755bc8fff..7da407071f 100644 --- a/src/machine/machine_stm32f7x2.go +++ b/src/machine/machine_stm32f7x2.go @@ -51,9 +51,20 @@ func (uart *UART) setRegisters() { //---------- I2C related code // Gets the value for TIMINGR register -func (i2c *I2C) getFreqRange() uint32 { +func (i2c *I2C) getFreqRange(br uint32) uint32 { // This is a 'magic' value calculated by STM32CubeMX // for 27MHz PCLK1 (216MHz CPU Freq / 8). // TODO: Do calculations based on PCLK1 - return 0x00606A9B + switch br { + case 10 * KHz: + return 0x5010C0FF + case 100 * KHz: + return 0x00606A9B + case 400 * KHz: + return 0x00201625 + case 500 * KHz: + return 0x00100429 + default: + return 0 + } } diff --git a/src/machine/machine_stm32l0.go b/src/machine/machine_stm32l0.go index 844cfccb49..e3fcefb572 100644 --- a/src/machine/machine_stm32l0.go +++ b/src/machine/machine_stm32l0.go @@ -298,9 +298,20 @@ func (spi SPI) configurePins(config SPIConfig) { //---------- I2C related types and code // Gets the value for TIMINGR register -func (i2c I2C) getFreqRange() uint32 { +func (i2c I2C) getFreqRange(br uint32) uint32 { // This is a 'magic' value calculated by STM32CubeMX - // for 80MHz PCLK1. + // for 16MHz PCLK1. // TODO: Do calculations based on PCLK1 - return 0x00303D5B + switch br { + case 10 * KHz: + return 0x40003EFF + case 100 * KHz: + return 0x00303D5B + case 400 * KHz: + return 0x0010061A + case 500 * KHz: + return 0x00000117 + default: + return 0 + } } diff --git a/src/machine/machine_stm32l4x2.go b/src/machine/machine_stm32l4x2.go index 497cf3e1d5..142a8f5f36 100644 --- a/src/machine/machine_stm32l4x2.go +++ b/src/machine/machine_stm32l4x2.go @@ -17,9 +17,20 @@ const APB2_TIM_FREQ = 80e6 // 80MHz //---------- I2C related code // Gets the value for TIMINGR register -func (i2c *I2C) getFreqRange() uint32 { - // This is a 'magic' value calculated by STM32CubeMX +func (i2c *I2C) getFreqRange(br uint32) uint32 { + // These are 'magic' values calculated by STM32CubeMX // for 80MHz PCLK1. // TODO: Do calculations based on PCLK1 - return 0x10909CEC + switch br { + case 10 * KHz: + return 0xF010F3FE + case 100 * KHz: + return 0x10909CEC + case 400 * KHz: + return 0x00702991 + case 500 * KHz: + return 0x00300E84 + default: + return 0 + } } diff --git a/src/machine/machine_stm32l4x5.go b/src/machine/machine_stm32l4x5.go index ec06544ba5..c8c550c3da 100644 --- a/src/machine/machine_stm32l4x5.go +++ b/src/machine/machine_stm32l4x5.go @@ -17,9 +17,20 @@ const APB2_TIM_FREQ = 120e6 // 120MHz //---------- I2C related code // Gets the value for TIMINGR register -func (i2c *I2C) getFreqRange() uint32 { +func (i2c *I2C) getFreqRange(br uint32) uint32 { // This is a 'magic' value calculated by STM32CubeMX // for 120MHz PCLK1. // TODO: Do calculations based on PCLK1 - return 0x307075B1 + switch br { + case 10 * KHz: + return 0x0 // does this even work? zero is weird here. + case 100 * KHz: + return 0x307075B1 + case 400 * KHz: + return 0x00B03FDB + case 500 * KHz: + return 0x005017C7 + default: + return 0 + } } diff --git a/src/machine/machine_stm32l4x6.go b/src/machine/machine_stm32l4x6.go index cf546303af..de878ebe32 100644 --- a/src/machine/machine_stm32l4x6.go +++ b/src/machine/machine_stm32l4x6.go @@ -17,9 +17,20 @@ const APB2_TIM_FREQ = 80e6 // 80MHz //---------- I2C related code // Gets the value for TIMINGR register -func (i2c *I2C) getFreqRange() uint32 { +func (i2c *I2C) getFreqRange(br uint32) uint32 { // This is a 'magic' value calculated by STM32CubeMX // for 80MHz PCLK1. // TODO: Do calculations based on PCLK1 - return 0x10909CEC + switch br { + case 10 * KHz: + return 0xF010F3FE + case 100 * KHz: + return 0x10909CEC + case 400 * KHz: + return 0x00702991 + case 500 * KHz: + return 0x00300E84 + default: + return 0 + } } diff --git a/src/machine/machine_stm32l5x2.go b/src/machine/machine_stm32l5x2.go index d439b8fd93..82ca1ecf6c 100644 --- a/src/machine/machine_stm32l5x2.go +++ b/src/machine/machine_stm32l5x2.go @@ -49,9 +49,20 @@ func (uart *UART) setRegisters() { //---------- I2C related code // Gets the value for TIMINGR register -func (i2c *I2C) getFreqRange() uint32 { +func (i2c *I2C) getFreqRange(br uint32) uint32 { // This is a 'magic' value calculated by STM32CubeMX // for 110MHz PCLK1. // TODO: Do calculations based on PCLK1 - return 0x40505681 + switch br { + case 10 * KHz: + return 0x0 // does this even work? zero is weird here. + case 100 * KHz: + return 0x40505681 + case 400 * KHz: + return 0x00A03AC8 + case 500 * KHz: + return 0x005015B6 + default: + return 0 + } } diff --git a/src/machine/machine_stm32wlx.go b/src/machine/machine_stm32wlx.go index d42ef2e383..b27c779feb 100644 --- a/src/machine/machine_stm32wlx.go +++ b/src/machine/machine_stm32wlx.go @@ -289,11 +289,22 @@ func (spi SPI) getBaudRate(config SPIConfig) uint32 { //---------- I2C related code // Gets the value for TIMINGR register -func (i2c *I2C) getFreqRange() uint32 { +func (i2c *I2C) getFreqRange(br uint32) uint32 { // This is a 'magic' value calculated by STM32CubeMX // for 48Mhz PCLK1. // TODO: Do calculations based on PCLK1 - return 0x20303E5D + switch br { + case 10 * KHz: + return 0x9010DEFF + case 100 * KHz: + return 0x20303E5D + case 400 * KHz: + return 0x2010091A + case 500 * KHz: + return 0x00201441 + default: + return 0 + } } //---------- UART related code From 6e58c44390303261af4a721df522affbc99c88ad Mon Sep 17 00:00:00 2001 From: sago35 Date: Fri, 10 May 2024 22:35:36 +0900 Subject: [PATCH 042/444] machine: add TxFifoFreeLevel() for CAN --- src/machine/machine_atsame5x_can.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/machine/machine_atsame5x_can.go b/src/machine/machine_atsame5x_can.go index 498646980d..bf38cd8988 100644 --- a/src/machine/machine_atsame5x_can.go +++ b/src/machine/machine_atsame5x_can.go @@ -221,6 +221,11 @@ func (can *CAN) TxFifoIsFull() bool { return (can.Bus.TXFQS.Get() & sam.CAN_TXFQS_TFQF_Msk) == sam.CAN_TXFQS_TFQF_Msk } +// TxFifoFreeLevel returns how many messages can still be set in the TxFifo. +func (can *CAN) TxFifoFreeLevel() int { + return int(can.Bus.GetTXFQS_TFFL()) +} + // TxRaw sends a CAN Frame according to CANTxBufferElement. func (can *CAN) TxRaw(e *CANTxBufferElement) { putIndex := (can.Bus.TXFQS.Get() & sam.CAN_TXFQS_TFQPI_Msk) >> sam.CAN_TXFQS_TFQPI_Pos From 1d9f26cee1f1501b09647186a25ba29aa6a0c58c Mon Sep 17 00:00:00 2001 From: Johann Freymuth Date: Wed, 8 May 2024 13:23:45 +0200 Subject: [PATCH 043/444] targets: add support for m5paper --- GNUmakefile | 2 + src/machine/board_m5paper.go | 112 +++++++++++++++++++++++++++++++++++ targets/m5paper.json | 5 ++ 3 files changed, 119 insertions(+) create mode 100644 src/machine/board_m5paper.go create mode 100644 targets/m5paper.json diff --git a/GNUmakefile b/GNUmakefile index 5496cbb0cf..cbc18c146e 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -766,6 +766,8 @@ ifneq ($(XTENSA), 0) @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target m5stick-c examples/serial @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target m5paper examples/serial + @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target mch2022 examples/serial @$(MD5SUM) test.bin endif diff --git a/src/machine/board_m5paper.go b/src/machine/board_m5paper.go new file mode 100644 index 0000000000..7c20f4dba3 --- /dev/null +++ b/src/machine/board_m5paper.go @@ -0,0 +1,112 @@ +//go:build m5paper + +package machine + +const ( + IO0 = GPIO0 + IO1 = GPIO1 + IO2 = GPIO2 + IO3 = GPIO3 + IO4 = GPIO4 + IO5 = GPIO5 + IO6 = GPIO6 + IO7 = GPIO7 + IO8 = GPIO8 + IO9 = GPIO9 + IO10 = GPIO10 + IO11 = GPIO11 + IO12 = GPIO12 + IO13 = GPIO13 + IO14 = GPIO14 + IO15 = GPIO15 + IO16 = GPIO16 + IO17 = GPIO17 + IO18 = GPIO18 + IO19 = GPIO19 + IO21 = GPIO21 + IO22 = GPIO22 + IO23 = GPIO23 + IO25 = GPIO25 + IO26 = GPIO26 + IO27 = GPIO27 + IO32 = GPIO32 + IO33 = GPIO33 + IO34 = GPIO34 + IO35 = GPIO35 + IO36 = GPIO36 + IO37 = GPIO37 + IO38 = GPIO38 + IO39 = GPIO39 +) + +const ( + POWER_PIN = IO2 + EXT_POWER_PIN = IO5 + EPD_POWER_PIN = IO23 + + // Buttons + BUTTON_RIGHT = IO39 + BUTTON_PUSH = IO38 + BUTTON_LEFT = IO37 + BUTTON = BUTTON_PUSH + + // Touch Screen Interrupt + TOUCH_INT = IO36 +) + +// SPI pins +const ( + SPI0_SCK_PIN = IO14 + SPI0_SDO_PIN = IO12 + SPI0_SDI_PIN = IO13 + + // EPD (IT8951) + EPD_SCK_PIN = SPI0_SCK_PIN + EPD_SDO_PIN = SPI0_SDO_PIN + EPD_SDI_PIN = SPI0_SDI_PIN + EPD_CS_PIN = IO15 + EPD_BUSY_PIN = IO27 + + // SD CARD + SDCARD_SCK_PIN = SPI0_SCK_PIN + SDCARD_SDO_PIN = SPI0_SDO_PIN + SDCARD_SDI_PIN = SPI0_SDI_PIN + SDCARD_CS_PIN = IO4 +) + +// I2C pins +const ( + SDA0_PIN = IO21 + SCL0_PIN = IO22 + + SDA_PIN = SDA0_PIN + SCL_PIN = SCL0_PIN + + I2C_TEMP_ADDR = 0x44 // temperature sensor (SHT30) + I2C_CLOCK_ADDR = 0x51 // real time clock (BM8563) + I2C_TOUCH_ADDR = 0x5D // touch screen controller (GT911) +) + +// ADC pins +const ( + ADC1 Pin = IO35 + ADC2 Pin = IO36 + + BATTERY_ADC_PIN = ADC1 +) + +// DAC pins +const ( + DAC1 Pin = IO25 + DAC2 Pin = IO26 +) + +// UART pins +const ( + // UART0 (CP2104) + UART0_TX_PIN = IO1 + UART0_RX_PIN = IO3 + + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) diff --git a/targets/m5paper.json b/targets/m5paper.json new file mode 100644 index 0000000000..9ee5d43b4c --- /dev/null +++ b/targets/m5paper.json @@ -0,0 +1,5 @@ +{ + "inherits": ["esp32"], + "build-tags": ["m5paper"], + "serial-port": ["1a86:55d4"] +} From cef2a82c97eeef34fffe72b4cbc60239114dec26 Mon Sep 17 00:00:00 2001 From: Marco Manino Date: Tue, 13 Feb 2024 17:48:19 +0100 Subject: [PATCH 044/444] Checking for methodset existance --- interp/interpreter.go | 4 ++++ testdata/init.go | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/interp/interpreter.go b/interp/interpreter.go index 605f4d8fc6..e0074ba26a 100644 --- a/interp/interpreter.go +++ b/interp/interpreter.go @@ -427,6 +427,10 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent if err != nil { return nil, mem, r.errorAt(inst, err) } + if typecodePtr.offset() == 0 { + locals[inst.localIndex] = literalValue{uint8(0)} + break + } typecodePtrOffset, err := typecodePtr.addOffset(-int64(r.pointerSize)) if err != nil { return nil, mem, r.errorAt(inst, err) // unlikely diff --git a/testdata/init.go b/testdata/init.go index fa470fd54f..8b34db3f38 100644 --- a/testdata/init.go +++ b/testdata/init.go @@ -109,3 +109,14 @@ func sliceString(s string, start, end int) string { func sliceSlice(s []int, start, end int) []int { return s[start:end] } + +type outside struct{} + +func init() { + _, _ = any(0).(interface{ DoesNotExist() }) + _, _ = any("").(interface{ DoesNotExist() }) + _, _ = any(outside{}).(interface{ DoesNotExist() }) + + type inside struct{} + _, _ = any(inside{}).(interface{ DoesNotExist() }) +} From 71878c2c0c3ed83aa332b32afa9fbef16de2bccc Mon Sep 17 00:00:00 2001 From: Marco Manino Date: Mon, 18 Mar 2024 08:32:17 +0100 Subject: [PATCH 045/444] Adding a comment --- interp/interpreter.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/interp/interpreter.go b/interp/interpreter.go index e0074ba26a..ea5eeaa12e 100644 --- a/interp/interpreter.go +++ b/interp/interpreter.go @@ -427,13 +427,20 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent if err != nil { return nil, mem, r.errorAt(inst, err) } + // typecodePtr always point to the numMethod field in the type + // description struct. The methodSet, when present, comes right + // before the numMethod field (the compiler doesn't generate + // method sets for concrete types without methods). + // Considering that the compiler doesn't emit interface type + // asserts for interfaces with no methods (as the always succeed) + // then if the offset is zero, this assert must always fail. if typecodePtr.offset() == 0 { locals[inst.localIndex] = literalValue{uint8(0)} break } typecodePtrOffset, err := typecodePtr.addOffset(-int64(r.pointerSize)) if err != nil { - return nil, mem, r.errorAt(inst, err) // unlikely + return nil, mem, r.errorAt(inst, err) } methodSetPtr, err := mem.load(typecodePtrOffset, r.pointerSize).asPointer(r) if err != nil { From 930720411159e41639f9de5b91e7a27a8dea963e Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Fri, 2 Feb 2024 15:43:52 +0000 Subject: [PATCH 046/444] Revert "flake.nix: explicitly add libcxx as dependency" This reverts commit 7b8ae2d6b62bf2bc76b8529147d24137df6ff8ec. --- flake.nix | 1 - 1 file changed, 1 deletion(-) diff --git a/flake.nix b/flake.nix index 52bac72d11..60c2b2534e 100644 --- a/flake.nix +++ b/flake.nix @@ -51,7 +51,6 @@ go llvmPackages_17.llvm llvmPackages_17.libclang - llvmPackages_17.libcxx # Additional dependencies needed at runtime, for building and/or # flashing. llvmPackages_17.lld From ee3a05f1de2cfac22eff91d4bc6651ff43bbc320 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Mon, 13 May 2024 18:41:37 +0200 Subject: [PATCH 047/444] targets: add support for Badger2040 W Signed-off-by: deadprogram --- GNUmakefile | 2 + src/machine/board_badger2040-w.go | 95 +++++++++++++++++++++++++++++++ src/machine/board_badger2040.go | 8 ++- targets/badger2040-w.json | 13 +++++ 4 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 src/machine/board_badger2040-w.go create mode 100644 targets/badger2040-w.json diff --git a/GNUmakefile b/GNUmakefile index cbc18c146e..5454896bf8 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -659,6 +659,8 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=badger2040 examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=badger2040-w examples/blinky1 + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=tufty2040 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=thingplus-rp2040 examples/blinky1 diff --git a/src/machine/board_badger2040-w.go b/src/machine/board_badger2040-w.go new file mode 100644 index 0000000000..d0982653b3 --- /dev/null +++ b/src/machine/board_badger2040-w.go @@ -0,0 +1,95 @@ +//go:build badger2040_w + +// This contains the pin mappings for the Badger 2040 W board. +// +// For more information, see: https://shop.pimoroni.com/products/badger-2040-w +// Also +// - Badger 2040 W schematic: https://cdn.shopify.com/s/files/1/0174/1800/files/badger_w_schematic.pdf?v=1675859004 +package machine + +const ( + LED Pin = GPIO22 + + BUTTON_A Pin = GPIO12 + BUTTON_B Pin = GPIO13 + BUTTON_C Pin = GPIO14 + BUTTON_UP Pin = GPIO15 + BUTTON_DOWN Pin = GPIO11 + BUTTON_USER Pin = NoPin // Not available on Badger 2040 W + + EPD_BUSY_PIN Pin = GPIO26 + EPD_RESET_PIN Pin = GPIO21 + EPD_DC_PIN Pin = GPIO20 + EPD_CS_PIN Pin = GPIO17 + EPD_SCK_PIN Pin = GPIO18 + EPD_SDO_PIN Pin = GPIO19 + + VBUS_DETECT Pin = GPIO24 + VREF_POWER Pin = GPIO27 + VREF_1V24 Pin = GPIO28 + VBAT_SENSE Pin = GPIO29 + ENABLE_3V3 Pin = GPIO10 + + BATTERY = VBAT_SENSE + RTC_ALARM = GPIO8 +) + +// I2C pins +const ( + I2C0_SDA_PIN Pin = GPIO4 + I2C0_SCL_PIN Pin = GPIO5 + + I2C1_SDA_PIN Pin = NoPin + I2C1_SCL_PIN Pin = NoPin +) + +// SPI pins. +const ( + SPI0_SCK_PIN Pin = GPIO18 + SPI0_SDO_PIN Pin = GPIO19 + SPI0_SDI_PIN Pin = GPIO16 + + SPI1_SCK_PIN Pin = NoPin + SPI1_SDO_PIN Pin = NoPin + SPI1_SDI_PIN Pin = NoPin +) + +// QSPI pins¿? +const ( +/* + TODO + +SPI0_SD0_PIN Pin = QSPI_SD0 +SPI0_SD1_PIN Pin = QSPI_SD1 +SPI0_SD2_PIN Pin = QSPI_SD2 +SPI0_SD3_PIN Pin = QSPI_SD3 +SPI0_SCK_PIN Pin = QSPI_SCLKGPIO6 +SPI0_CS_PIN Pin = QSPI_CS +*/ +) + +// Onboard crystal oscillator frequency, in MHz. +const ( + xoscFreq = 12 // MHz +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Badger 2040 W" + usb_STRING_MANUFACTURER = "Pimoroni" +) + +var ( + usb_VID uint16 = 0x2e8a + usb_PID uint16 = 0x0003 +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 diff --git a/src/machine/board_badger2040.go b/src/machine/board_badger2040.go index 049e3cb899..73f802a909 100644 --- a/src/machine/board_badger2040.go +++ b/src/machine/board_badger2040.go @@ -1,6 +1,6 @@ //go:build badger2040 -// This contains the pin mappings for the Badger 2040 Connect board. +// This contains the pin mappings for the Badger 2040 board. // // For more information, see: https://shop.pimoroni.com/products/badger-2040 // Also @@ -25,8 +25,12 @@ const ( EPD_SDO_PIN Pin = GPIO19 VBUS_DETECT Pin = GPIO24 - BATTERY Pin = GPIO29 + VREF_POWER Pin = GPIO27 + VREF_1V24 Pin = GPIO28 + VBAT_SENSE Pin = GPIO29 ENABLE_3V3 Pin = GPIO10 + + BATTERY = VBAT_SENSE ) // I2C pins diff --git a/targets/badger2040-w.json b/targets/badger2040-w.json new file mode 100644 index 0000000000..4b29dc85b9 --- /dev/null +++ b/targets/badger2040-w.json @@ -0,0 +1,13 @@ +{ + "inherits": [ + "rp2040" + ], + "serial-port": ["2e8a:0003"], + "build-tags": ["badger2040_w"], + "ldflags": [ + "--defsym=__flash_size=1020K" + ], + "extra-files": [ + "targets/pico-boot-stage2.S" + ] +} From 8890b57ba0f5d015560bf1c8e30d0393fc809c1f Mon Sep 17 00:00:00 2001 From: Johann Freymuth Date: Mon, 13 May 2024 21:58:06 +0200 Subject: [PATCH 048/444] Add I2C support for esp32 (#4259) machine/esp32: add i2c support --- src/machine/i2c.go | 2 +- src/machine/machine_esp32_i2c.go | 412 +++++++++++++++++++++++++++++++ 2 files changed, 413 insertions(+), 1 deletion(-) create mode 100644 src/machine/machine_esp32_i2c.go diff --git a/src/machine/i2c.go b/src/machine/i2c.go index 96c192cb82..24fbf6897f 100644 --- a/src/machine/i2c.go +++ b/src/machine/i2c.go @@ -1,4 +1,4 @@ -//go:build !baremetal || atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || mimxrt1062 || (esp32c3 && !m5stamp_c3) +//go:build !baremetal || atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || mimxrt1062 || (esp32c3 && !m5stamp_c3) || esp32 package machine diff --git a/src/machine/machine_esp32_i2c.go b/src/machine/machine_esp32_i2c.go new file mode 100644 index 0000000000..746e722dc2 --- /dev/null +++ b/src/machine/machine_esp32_i2c.go @@ -0,0 +1,412 @@ +//go:build esp32 + +package machine + +import ( + "device/esp" + "runtime/volatile" + "unsafe" +) + +var ( + I2C0 = &I2C{Bus: esp.I2C0, funcSCL: 29, funcSDA: 30} + I2C1 = &I2C{Bus: esp.I2C1, funcSCL: 95, funcSDA: 96} +) + +type I2C struct { + Bus *esp.I2C_Type + funcSCL, funcSDA uint32 + config I2CConfig +} + +// I2CConfig is used to store config info for I2C. +type I2CConfig struct { + Frequency uint32 // in Hz + SCL Pin + SDA Pin +} + +const ( + i2cClkSourceFrequency = uint32(80 * MHz) +) + +func (i2c *I2C) Configure(config I2CConfig) error { + if config.Frequency == 0 { + config.Frequency = 400 * KHz + } + if config.SCL == 0 { + config.SCL = SCL_PIN + } + if config.SDA == 0 { + config.SDA = SDA_PIN + } + i2c.config = config + + i2c.initAll() + return nil +} + +func (i2c *I2C) initAll() { + i2c.initClock() + i2c.initNoiseFilter() + i2c.initPins() + i2c.initFrequency() + i2c.startMaster() +} + +//go:inline +func (i2c *I2C) initClock() { + // reset I2C clock + if i2c.Bus == esp.I2C0 { + esp.DPORT.SetPERIP_RST_EN_I2C0_EXT0_RST(1) + esp.DPORT.SetPERIP_CLK_EN_I2C0_EXT0_CLK_EN(1) + esp.DPORT.SetPERIP_RST_EN_I2C0_EXT0_RST(0) + } else { + esp.DPORT.SetPERIP_RST_EN_I2C_EXT1_RST(1) + esp.DPORT.SetPERIP_CLK_EN_I2C_EXT1_CLK_EN(1) + esp.DPORT.SetPERIP_RST_EN_I2C_EXT1_RST(0) + } + // disable interrupts + i2c.Bus.INT_ENA.Set(0) + i2c.Bus.INT_CLR.Set(0x3fff) + + i2c.Bus.SetCTR_CLK_EN(1) +} + +//go:inline +func (i2c *I2C) initNoiseFilter() { + i2c.Bus.SCL_FILTER_CFG.Set(0xF) + i2c.Bus.SDA_FILTER_CFG.Set(0xF) +} + +//go:inline +func (i2c *I2C) initPins() { + var muxConfig uint32 + const function = 2 // function 2 is just GPIO + + // SDA + muxConfig = function << esp.IO_MUX_GPIO0_MCU_SEL_Pos + // Make this pin an input pin (always). + muxConfig |= esp.IO_MUX_GPIO0_FUN_IE + // Set drive strength: 0 is lowest, 3 is highest. + muxConfig |= 1 << esp.IO_MUX_GPIO0_FUN_DRV_Pos + i2c.config.SDA.mux().Set(muxConfig) + i2c.config.SDA.outFunc().Set(i2c.funcSDA) + inFunc(i2c.funcSDA).Set(uint32(esp.GPIO_FUNC_IN_SEL_CFG_SEL | i2c.config.SDA)) + i2c.config.SDA.Set(true) + // Configure the pad with the given IO mux configuration. + i2c.config.SDA.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER) + + esp.GPIO.ENABLE_W1TS.Set(1 << int(i2c.config.SDA)) + i2c.Bus.SetCTR_SDA_FORCE_OUT(1) + + // SCL + muxConfig = function << esp.IO_MUX_GPIO0_MCU_SEL_Pos + // Make this pin an input pin (always). + muxConfig |= esp.IO_MUX_GPIO0_FUN_IE + // Set drive strength: 0 is lowest, 3 is highest. + muxConfig |= 1 << esp.IO_MUX_GPIO0_FUN_DRV_Pos + i2c.config.SCL.mux().Set(muxConfig) + i2c.config.SCL.outFunc().Set(i2c.funcSCL) + inFunc(i2c.funcSCL).Set(uint32(esp.GPIO_FUNC_IN_SEL_CFG_SEL | i2c.config.SCL)) + i2c.config.SCL.Set(true) + // Configure the pad with the given IO mux configuration. + i2c.config.SCL.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER) + + esp.GPIO.ENABLE_W1TS.Set(1 << int(i2c.config.SCL)) + i2c.Bus.SetCTR_SCL_FORCE_OUT(1) +} + +//go:inline +func (i2c *I2C) initFrequency() { + clkmDiv := i2cClkSourceFrequency/(i2c.config.Frequency*1024) + 1 + sclkFreq := i2cClkSourceFrequency / clkmDiv + halfCycle := sclkFreq / i2c.config.Frequency / 2 + //SCL + sclLow := halfCycle + sclWaitHigh := uint32(0) + if i2c.config.Frequency > 50000 { + sclWaitHigh = halfCycle / 8 // compensate the time when freq > 50K + } + sclHigh := halfCycle - sclWaitHigh + // SDA + sdaHold := halfCycle / 4 + sda_sample := halfCycle / 2 + setup := halfCycle + hold := halfCycle + + i2c.Bus.SetSCL_LOW_PERIOD(sclLow - 1) + i2c.Bus.SetSCL_HIGH_PERIOD(sclHigh) + i2c.Bus.SetSCL_RSTART_SETUP_TIME(setup) + i2c.Bus.SetSCL_STOP_SETUP_TIME(setup) + i2c.Bus.SetSCL_START_HOLD_TIME(hold - 1) + i2c.Bus.SetSCL_STOP_HOLD_TIME(hold - 1) + i2c.Bus.SetSDA_SAMPLE_TIME(sda_sample) + i2c.Bus.SetSDA_HOLD_TIME(sdaHold) + // set timeout value + i2c.Bus.SetTO_TIME_OUT(20 * halfCycle) +} + +//go:inline +func (i2c *I2C) startMaster() { + // FIFO mode for data + i2c.Bus.SetFIFO_CONF_NONFIFO_EN(0) + // Reset TX & RX buffers + i2c.Bus.SetFIFO_CONF_RX_FIFO_RST(1) + i2c.Bus.SetFIFO_CONF_RX_FIFO_RST(0) + i2c.Bus.SetFIFO_CONF_TX_FIFO_RST(1) + i2c.Bus.SetFIFO_CONF_TX_FIFO_RST(0) + // enable master mode + i2c.Bus.SetCTR_MS_MODE(1) +} + +func (i2c *I2C) resetBus() { + // unlike esp32c3, the esp32 i2c modules do not have a reset fsm register, + // so we need to: + // 1. disconnect the pins + // 2. generate a stop condition manually + // 3. do a full reset + // 4. redo all configuration + + i2c.config.SDA.mux().Set(2< 0 && c.head < len(c.data); count, c.head = count-1, c.head+1 { + i2c.Bus.SetDATA_FIFO_RDATA(uint32(c.data[c.head])) + } + reg.Set(i2cCMD_WRITE | uint32(32-count)) + reg = nextAddress(reg) + + if c.head < len(c.data) { + reg.Set(i2cCMD_END) + reg = nil + } else { + cmdIdx++ + } + needRestart = true + + case i2cCMD_READ: + if needAddress { + needAddress = false + i2c.Bus.SetDATA_FIFO_RDATA((uint32(addr)&0x7f)<<1 | 1) + i2c.Bus.SLAVE_ADDR.Set(uint32(addr)) + reg.Set(i2cCMD_WRITE | 1) + reg = nextAddress(reg) + } + if needRestart { + // We need to send RESTART again after i2cCMD_WRITE. + reg.Set(i2cCMD_RSTART) + + reg = nextAddress(reg) + reg.Set(i2cCMD_WRITE | 1) + + reg = nextAddress(reg) + i2c.Bus.SetDATA_FIFO_RDATA((uint32(addr)&0x7f)<<1 | 1) + needRestart = false + } + count := 32 + bytes := len(c.data) - c.head + // Only last byte in sequence must be sent with ACK set to 1 to indicate end of data. + split := bytes <= count + if split { + bytes-- + } + if bytes > 32 { + bytes = 32 + } + if bytes > 0 { + reg.Set(i2cCMD_READ | uint32(bytes)) + reg = nextAddress(reg) + } + + if split { + readLast = true + reg.Set(i2cCMD_READLAST | 1) + reg = nextAddress(reg) + readTo = c.data[c.head : c.head+bytes+1] // read bytes + 1 last byte + cmdIdx++ + } else { + reg.Set(i2cCMD_END) + readTo = c.data[c.head : c.head+bytes] + reg = nil + } + + case i2cCMD_STOP: + reg.Set(i2cCMD_STOP) + reg = nil + cmdIdx++ + } + if reg == nil { + // transmit now + i2c.Bus.SetCTR_TRANS_START(1) + end := nanotime() + timeoutNS + var mask uint32 + for mask = i2c.Bus.INT_STATUS.Get(); mask&intMask == 0; mask = i2c.Bus.INT_STATUS.Get() { + if nanotime() > end { + // timeout leaves the bus in an undefined state, reset + i2c.resetBus() + if readTo != nil { + return errI2CReadTimeout + } + return errI2CWriteTimeout + } + } + switch { + case mask&esp.I2C_INT_STATUS_ACK_ERR_INT_ST_Msk != 0 && !readLast: + return errI2CAckExpected + case mask&esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk != 0: + // timeout leaves the bus in an undefined state, reset + i2c.resetBus() + if readTo != nil { + return errI2CReadTimeout + } + return errI2CWriteTimeout + } + i2c.Bus.INT_CLR.SetBits(intMask) + for i := 0; i < len(readTo); i++ { + readTo[i] = byte(i2c.Bus.GetDATA_FIFO_RDATA() & 0xff) + c.head++ + } + readTo = nil + reg = &i2c.Bus.COMD0 + } + } + return nil +} + +// Tx does a single I2C transaction at the specified address. +// It clocks out the given address, writes the bytes in w, reads back len(r) +// bytes and stores them in r, and generates a stop condition on the bus. +func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) { + // timeout in microseconds. + const timeout = 40 // 40ms is a reasonable time for a real-time system. + + cmd := make([]i2cCommand, 0, 8) + cmd = append(cmd, i2cCommand{cmd: i2cCMD_RSTART}) + if len(w) > 0 { + cmd = append(cmd, i2cCommand{cmd: i2cCMD_WRITE, data: w}) + } + if len(r) > 0 { + cmd = append(cmd, i2cCommand{cmd: i2cCMD_READ, data: r}) + } + cmd = append(cmd, i2cCommand{cmd: i2cCMD_STOP}) + + return i2c.transmit(addr, cmd, timeout) +} + +func (i2c *I2C) SetBaudRate(br uint32) error { + return errI2CNotImplemented +} + +func (p Pin) pinReg() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.PIN0)) + uintptr(p)*4))) +} + +func nextAddress(reg *volatile.Register32) *volatile.Register32 { + return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(reg), 4)) +} + +// CheckDevice does an empty I2C transaction at the specified address. +// This can be used to find out if any device with that address is +// connected, e.g. for enumerating all devices on the bus. +func (i2c *I2C) CheckDevice(addr uint16) bool { + // timeout in microseconds. + const timeout = 40 // 40ms is a reasonable time for a real-time system. + + cmd := []i2cCommand{ + {cmd: i2cCMD_RSTART}, + {cmd: i2cCMD_WRITE}, + {cmd: i2cCMD_STOP}, + } + return i2c.transmit(addr, cmd, timeout) == nil +} From c78dbcd3f26f43a28b08a846a56d3448313ad033 Mon Sep 17 00:00:00 2001 From: Mateusz Nowak <118080320+mateusznowakdev@users.noreply.github.com> Date: Tue, 16 Apr 2024 21:16:20 +0200 Subject: [PATCH 049/444] Support UF2 drives with space in their name on Linux Some devices, such as Seeed Studio XIAO with Adafruit bootloader, use complex names for UF2 device. With this fix applied, /proc/mounts should be parsed correctly --- main.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index b10f45cd73..3d030dba36 100644 --- a/main.go +++ b/main.go @@ -1033,9 +1033,10 @@ func findFATMounts(options *compileopts.Options) ([]mountPoint, error) { if fstype != "vfat" { continue } + fspath := strings.ReplaceAll(fields[1], "\\040", " ") points = append(points, mountPoint{ - name: filepath.Base(fields[1]), - path: fields[1], + name: filepath.Base(fspath), + path: fspath, }) } return points, nil From fe27b674cdacd1ad78a2412013844f13eb1b3b15 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Tue, 14 May 2024 05:04:54 -0700 Subject: [PATCH 050/444] reflect: use int in StringHeader and SliceHeader on non-AVR platforms (#4156) --- src/reflect/intw.go | 8 ++++++++ src/reflect/intw_avr.go | 8 ++++++++ src/reflect/intw_test.go | 30 ++++++++++++++++++++++++++++++ src/reflect/value.go | 16 +++++++++++++--- tests/runtime_wasi/malloc_test.go | 2 +- 5 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 src/reflect/intw.go create mode 100644 src/reflect/intw_avr.go create mode 100644 src/reflect/intw_test.go diff --git a/src/reflect/intw.go b/src/reflect/intw.go new file mode 100644 index 0000000000..20fbd4341e --- /dev/null +++ b/src/reflect/intw.go @@ -0,0 +1,8 @@ +//go:build !avr + +package reflect + +// intw is an integer type, used in places where an int is typically required, +// except architectures where the size of an int != word size. +// See https://github.com/tinygo-org/tinygo/issues/1284. +type intw = int diff --git a/src/reflect/intw_avr.go b/src/reflect/intw_avr.go new file mode 100644 index 0000000000..8f294eeee2 --- /dev/null +++ b/src/reflect/intw_avr.go @@ -0,0 +1,8 @@ +//go:build avr + +package reflect + +// intw is an integer type, used in places where an int is typically required, +// except architectures where the size of an int != word size. +// See https://github.com/tinygo-org/tinygo/issues/1284. +type intw = uintptr diff --git a/src/reflect/intw_test.go b/src/reflect/intw_test.go new file mode 100644 index 0000000000..1014a9ae4e --- /dev/null +++ b/src/reflect/intw_test.go @@ -0,0 +1,30 @@ +//go:build !avr + +package reflect_test + +import ( + "reflect" + "testing" + "unsafe" +) + +// Verify that SliceHeader is the same size as a slice. +var _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(reflect.SliceHeader{})]byte{} + +// TestSliceHeaderIntegerSize verifies that SliceHeader.Len and Cap are type int on non-AVR platforms. +// See https://github.com/tinygo-org/tinygo/issues/1284. +func TestSliceHeaderIntegerSize(t *testing.T) { + var h reflect.SliceHeader + h.Len = int(0) + h.Cap = int(0) +} + +// Verify that StringHeader is the same size as a string. +var _ [unsafe.Sizeof("hello")]byte = [unsafe.Sizeof(reflect.StringHeader{})]byte{} + +// TestStringHeaderIntegerSize verifies that StringHeader.Len and Cap are type int on non-AVR platforms. +// See https://github.com/tinygo-org/tinygo/issues/1284. +func TestStringHeaderIntegerSize(t *testing.T) { + var h reflect.StringHeader + h.Len = int(0) +} diff --git a/src/reflect/value.go b/src/reflect/value.go index 6e882ed17b..c8439ccef2 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -1578,8 +1578,8 @@ type funcHeader struct { type SliceHeader struct { Data uintptr - Len uintptr - Cap uintptr + Len intw + Cap intw } // Slice header that matches the underlying structure. Used for when we switch @@ -1592,7 +1592,7 @@ type sliceHeader struct { type StringHeader struct { Data uintptr - Len uintptr + Len intw } // Like sliceHeader, this type is used internally to make sure pointer and @@ -1602,6 +1602,16 @@ type stringHeader struct { len uintptr } +// Verify SliceHeader and StringHeader sizes. +// See https://github.com/tinygo-org/tinygo/pull/4156 +// and https://github.com/tinygo-org/tinygo/issues/1284. +var ( + _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(SliceHeader{})]byte{} + _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(sliceHeader{})]byte{} + _ [unsafe.Sizeof("")]byte = [unsafe.Sizeof(StringHeader{})]byte{} + _ [unsafe.Sizeof("")]byte = [unsafe.Sizeof(stringHeader{})]byte{} +) + type ValueError struct { Method string Kind Kind diff --git a/tests/runtime_wasi/malloc_test.go b/tests/runtime_wasi/malloc_test.go index e5bbb4ebb3..465e662a45 100644 --- a/tests/runtime_wasi/malloc_test.go +++ b/tests/runtime_wasi/malloc_test.go @@ -67,7 +67,7 @@ func checkFilledBuffer(t *testing.T, ptr uintptr, content string) { t.Helper() buf := *(*string)(unsafe.Pointer(&reflect.StringHeader{ Data: ptr, - Len: uintptr(len(content)), + Len: len(content), })) if buf != content { t.Errorf("expected %q, got %q", content, buf) From 30c4df16f27afa66e561f82914f0fee1acce7b9e Mon Sep 17 00:00:00 2001 From: leongross Date: Tue, 23 Apr 2024 15:55:06 +0200 Subject: [PATCH 051/444] add aes generic aliases --- compiler/alias.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/alias.go b/compiler/alias.go index 9490d84d9a..b0191a7a11 100644 --- a/compiler/alias.go +++ b/compiler/alias.go @@ -24,6 +24,10 @@ var stdlibAliases = map[string]string{ "crypto/sha256.block": "crypto/sha256.blockGeneric", "crypto/sha512.blockAMD64": "crypto/sha512.blockGeneric", + // AES + "crypto/aes.decryptBlockAsm": "crypto/aes.decryptBlock", + "crypto/aes.encryptBlockAsm": "crypto/aes.encryptBlock", + // math package "math.archHypot": "math.hypot", "math.archMax": "math.max", From ad0340d437fc7995e12a24b06bf82807c2cb661a Mon Sep 17 00:00:00 2001 From: leongross Date: Thu, 16 May 2024 15:06:56 +0200 Subject: [PATCH 052/444] fix fpm ci installion Signed-off-by: leongross --- .github/workflows/linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 6decf0f7cf..c1529aa8a1 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -24,7 +24,7 @@ jobs: # tar: needed for actions/cache@v4 # git+openssh: needed for checkout (I think?) # ruby: needed to install fpm - run: apk add tar git openssh make g++ ruby + run: apk add tar git openssh make g++ ruby-dev - name: Work around CVE-2022-24765 # We're not on a multi-user machine, so this is safe. run: git config --global --add safe.directory "$GITHUB_WORKSPACE" From c2776dcf78125abb86e3e7b4a110bb0d07386d09 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 24 May 2024 16:28:25 +0200 Subject: [PATCH 053/444] main: add -serial=rtt flag as possible option I added this a while ago but forgot to change the help documentation. --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 3d030dba36..6b3aeb3b03 100644 --- a/main.go +++ b/main.go @@ -1412,7 +1412,7 @@ func main() { gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)") panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)") scheduler := flag.String("scheduler", "", "which scheduler to use (none, tasks, asyncify)") - serial := flag.String("serial", "", "which serial output to use (none, uart, usb)") + serial := flag.String("serial", "", "which serial output to use (none, uart, usb, rtt)") work := flag.Bool("work", false, "print the name of the temporary build directory and do not delete this directory on exit") interpTimeout := flag.Duration("interp-timeout", 180*time.Second, "interp optimization pass timeout") var tags buildutil.TagsFlag From 81ce7fb738142361afba119f1f531cf6ffddc6d1 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 18 May 2024 12:55:35 +0200 Subject: [PATCH 054/444] LLVM 18 support --- .circleci/config.yml | 12 ++++++------ .github/workflows/build-macos.yml | 10 +++++----- .github/workflows/linux.yml | 12 ++++++------ .github/workflows/llvm.yml | 4 ++-- .github/workflows/nix.yml | 2 +- .github/workflows/sizediff-install-pkgs.sh | 10 +++++----- .github/workflows/sizediff.yml | 2 +- .github/workflows/windows.yml | 4 ++-- GNUmakefile | 8 ++++---- builder/build.go | 2 +- builder/builtins.go | 12 +++++------- builder/cc1as.cpp | 15 ++++++++------- builder/cc1as.h | 2 +- builder/sizes_test.go | 6 +++--- cgo/libclang_config_llvm17.go | 2 +- cgo/libclang_config_llvm18.go | 15 +++++++++++++++ compileopts/target.go | 6 +++--- compiler/asserts.go | 4 ++-- compiler/intrinsics.go | 10 ++++++++++ compiler/llvm.go | 8 ++++++-- compiler/llvmutil/llvm.go | 13 +++++++++++++ compiler/testdata/channel.ll | 4 ++-- compiler/testdata/defer-cortex-m-qemu.ll | 6 +++--- compiler/testdata/gc.ll | 18 +++++++++--------- compiler/testdata/slice.ll | 4 ++-- compiler/testdata/zeromap.ll | 8 ++++---- flake.nix | 2 +- go.mod | 2 +- go.sum | 4 ++-- src/runtime/runtime.go | 8 +++----- targets/esp32c3.json | 2 +- targets/fe310.json | 2 +- targets/k210.json | 2 +- targets/riscv-qemu.json | 2 +- transform/optimizer.go | 10 ++++++++-- 35 files changed, 139 insertions(+), 94 deletions(-) create mode 100644 cgo/libclang_config_llvm18.go diff --git a/.circleci/config.yml b/.circleci/config.yml index c9c9d65043..a940406065 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,12 +10,12 @@ commands: steps: - restore_cache: keys: - - llvm-source-17-v1 + - llvm-source-18-v1 - run: name: "Fetch LLVM source" command: make llvm-source - save_cache: - key: llvm-source-17-v1 + key: llvm-source-18-v1 paths: - llvm-project/clang/lib/Headers - llvm-project/clang/include @@ -105,12 +105,12 @@ jobs: - test-linux: llvm: "15" resource_class: large - test-llvm17-go122: + test-llvm18-go122: docker: - image: golang:1.22-bullseye steps: - test-linux: - llvm: "17" + llvm: "18" resource_class: large workflows: @@ -119,5 +119,5 @@ workflows: # This tests our lowest supported versions of Go and LLVM, to make sure at # least the smoke tests still pass. - test-llvm15-go118 - # This tests LLVM 17 support when linking against system libraries. - - test-llvm17-go122 + # This tests LLVM 18 support when linking against system libraries. + - test-llvm18-go122 diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 1fefa27bf5..7acd2e5b7d 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -43,7 +43,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-17-${{ matrix.os }}-v2 + key: llvm-source-18-${{ matrix.os }}-v2 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -68,7 +68,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-17-${{ matrix.os }}-v2 + key: llvm-build-18-${{ matrix.os }}-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -128,7 +128,7 @@ jobs: runs-on: macos-latest strategy: matrix: - version: [16, 17] + version: [16, 17, 18] steps: - name: Set up Homebrew uses: Homebrew/actions/setup-homebrew@master @@ -152,8 +152,8 @@ jobs: - name: Check binary run: tinygo version - name: Build TinyGo (default LLVM) - if: matrix.version == 17 + if: matrix.version == 18 run: go install - name: Check binary - if: matrix.version == 17 + if: matrix.version == 18 run: tinygo version diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index c1529aa8a1..38f0d92f5d 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -43,7 +43,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-17-linux-alpine-v2 + key: llvm-source-18-linux-alpine-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -68,7 +68,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-17-linux-alpine-v2 + key: llvm-build-18-linux-alpine-v1 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -196,7 +196,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-17-linux-asserts-v2 + key: llvm-source-18-linux-asserts-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -221,7 +221,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-17-linux-asserts-v2 + key: llvm-build-18-linux-asserts-v1 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -309,7 +309,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-17-linux-v2 + key: llvm-source-18-linux-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -334,7 +334,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-17-linux-${{ matrix.goarch }}-v2 + key: llvm-build-18-linux-${{ matrix.goarch }}-v1 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index d7fd574dfe..f97646f7b9 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -35,8 +35,8 @@ jobs: uses: docker/metadata-action@v5 with: images: | - tinygo/llvm-17 - ghcr.io/${{ github.repository_owner }}/llvm-17 + tinygo/llvm-18 + ghcr.io/${{ github.repository_owner }}/llvm-18 tags: | type=sha,format=long type=raw,value=latest diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 2eae94fc04..62da55672a 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -24,7 +24,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-17-linux-nix-v2 + key: llvm-source-18-linux-nix-v1 path: | llvm-project/compiler-rt - name: Download LLVM source diff --git a/.github/workflows/sizediff-install-pkgs.sh b/.github/workflows/sizediff-install-pkgs.sh index ce51d4d36f..31edc57506 100755 --- a/.github/workflows/sizediff-install-pkgs.sh +++ b/.github/workflows/sizediff-install-pkgs.sh @@ -2,11 +2,11 @@ # still works after checking out the dev branch (that is, when going from LLVM # 16 to LLVM 17 for example, both Clang 16 and Clang 17 are installed). -echo 'deb https://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main' | sudo tee /etc/apt/sources.list.d/llvm.list +echo 'deb https://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 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 --no-install-recommends -y \ - llvm-17-dev \ - clang-17 \ - libclang-17-dev \ - lld-17 + llvm-18-dev \ + clang-18 \ + libclang-18-dev \ + lld-18 diff --git a/.github/workflows/sizediff.yml b/.github/workflows/sizediff.yml index 4146673aee..5dac15409e 100644 --- a/.github/workflows/sizediff.yml +++ b/.github/workflows/sizediff.yml @@ -28,7 +28,7 @@ jobs: uses: actions/cache@v4 id: cache-llvm-source with: - key: llvm-source-17-sizediff-v2 + key: llvm-source-18-sizediff-v1 path: | llvm-project/compiler-rt - name: Download LLVM source diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 013ff1a276..5cfba9217d 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -41,7 +41,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-17-windows-v2 + key: llvm-source-18-windows-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -66,7 +66,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-17-windows-v2 + key: llvm-build-18-windows-v1 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' diff --git a/GNUmakefile b/GNUmakefile index 5454896bf8..ce833c656e 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -10,7 +10,7 @@ LLD_SRC ?= $(LLVM_PROJECTDIR)/lld # Try to autodetect LLVM build tools. # Versions are listed here in descending priority order. -LLVM_VERSIONS = 17 16 15 +LLVM_VERSIONS = 18 17 16 15 errifempty = $(if $(1),$(1),$(error $(2))) detect = $(shell which $(call errifempty,$(firstword $(foreach p,$(2),$(shell command -v $(p) 2> /dev/null && echo $(p)))),failed to locate $(1) at any of: $(2))) toolSearchPathsVersion = $(1)-$(2) @@ -111,7 +111,7 @@ endif .PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr gen-device-rp -LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf debuginfopdb executionengine frontendhlsl frontendopenmp instrumentation interpreter ipo irreader libdriver linker lto mc mcjit objcarcopts option profiledata scalaropts support target windowsdriver windowsmanifest +LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf debuginfopdb executionengine frontenddriver frontendhlsl frontendopenmp instrumentation interpreter ipo irreader libdriver linker lto mc mcjit objcarcopts option profiledata scalaropts support target windowsdriver windowsmanifest ifeq ($(OS),Windows_NT) EXE = .exe @@ -147,7 +147,7 @@ endif MD5SUM ?= md5sum # Libraries that should be linked in for the statically linked Clang. -CLANG_LIB_NAMES = clangAnalysis clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangExtractAPI clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangSupport clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions +CLANG_LIB_NAMES = clangAnalysis clangAPINotes clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangExtractAPI clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangSupport clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++ # Libraries that should be linked in for the statically linked LLD. @@ -239,7 +239,7 @@ gen-device-renesas: build/gen-device-svd # Get LLVM sources. $(LLVM_PROJECTDIR)/llvm: - git clone -b esp-17.0.1_20240419 --depth=1 https://github.com/espressif/llvm-project $(LLVM_PROJECTDIR) + git clone -b tinygo_xtensa_release_18.1.2 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR) llvm-source: $(LLVM_PROJECTDIR)/llvm # Configure LLVM. diff --git a/builder/build.go b/builder/build.go index 5a6683ae6f..940439667b 100644 --- a/builder/build.go +++ b/builder/build.go @@ -773,7 +773,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe if sizeLevel >= 2 { // Workaround with roughly the same effect as // https://reviews.llvm.org/D119342. - // Can hopefully be removed in LLVM 18. + // Can hopefully be removed in LLVM 19. ldflags = append(ldflags, "-mllvm", "--rotation-max-header-size=0") } diff --git a/builder/builtins.go b/builder/builtins.go index a1066b6714..0dbfc42a00 100644 --- a/builder/builtins.go +++ b/builder/builtins.go @@ -8,14 +8,14 @@ import ( "github.com/tinygo-org/tinygo/goenv" ) -// These are the GENERIC_SOURCES according to CMakeList.txt. +// These are the GENERIC_SOURCES according to CMakeList.txt except for +// divmodsi4.c and udivmodsi4.c. var genericBuiltins = []string{ "absvdi2.c", "absvsi2.c", "absvti2.c", "adddf3.c", "addsf3.c", - "addtf3.c", "addvdi3.c", "addvsi3.c", "addvti3.c", @@ -40,12 +40,12 @@ var genericBuiltins = []string{ "divdf3.c", "divdi3.c", "divmoddi4.c", + //"divmodsi4.c", + "divmodti4.c", "divsc3.c", "divsf3.c", "divsi3.c", - "divtc3.c", "divti3.c", - "divtf3.c", "extendsfdf2.c", "extendhfsf2.c", "ffsdi2.c", @@ -91,7 +91,6 @@ var genericBuiltins = []string{ "mulsc3.c", "mulsf3.c", "multi3.c", - "multf3.c", "mulvdi3.c", "mulvsi3.c", "mulvti3.c", @@ -111,13 +110,11 @@ var genericBuiltins = []string{ "popcountti2.c", "powidf2.c", "powisf2.c", - "powitf2.c", "subdf3.c", "subsf3.c", "subvdi3.c", "subvsi3.c", "subvti3.c", - "subtf3.c", "trampoline_setup.c", "truncdfhf2.c", "truncdfsf2.c", @@ -126,6 +123,7 @@ var genericBuiltins = []string{ "ucmpti2.c", "udivdi3.c", "udivmoddi4.c", + //"udivmodsi4.c", "udivmodti4.c", "udivsi3.c", "udivti3.c", diff --git a/builder/cc1as.cpp b/builder/cc1as.cpp index 9f9e377fac..e489866ec7 100644 --- a/builder/cc1as.cpp +++ b/builder/cc1as.cpp @@ -82,10 +82,10 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts, // Parse the arguments. const OptTable &OptTbl = getDriverOptTable(); - const unsigned IncludedFlagsBitmask = options::CC1AsOption; + llvm::opt::Visibility VisibilityMask(options::CC1AsOption); unsigned MissingArgIndex, MissingArgCount; - InputArgList Args = OptTbl.ParseArgs(Argv, MissingArgIndex, MissingArgCount, - IncludedFlagsBitmask); + InputArgList Args = + OptTbl.ParseArgs(Argv, MissingArgIndex, MissingArgCount, VisibilityMask); // Check for missing argument error. if (MissingArgCount) { @@ -98,7 +98,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts, for (const Arg *A : Args.filtered(OPT_UNKNOWN)) { auto ArgString = A->getAsString(Args); std::string Nearest; - if (OptTbl.findNearest(ArgString, Nearest, IncludedFlagsBitmask) > 1) + if (OptTbl.findNearest(ArgString, Nearest, VisibilityMask) > 1) Diags.Report(diag::err_drv_unknown_argument) << ArgString; else Diags.Report(diag::err_drv_unknown_argument_with_suggestion) @@ -521,9 +521,10 @@ int cc1as_main(ArrayRef Argv, const char *Argv0, void *MainAddr) { if (Asm.ShowHelp) { getDriverOptTable().printHelp( llvm::outs(), "clang -cc1as [options] file...", - "Clang Integrated Assembler", - /*Include=*/driver::options::CC1AsOption, /*Exclude=*/0, - /*ShowAllAliases=*/false); + "Clang Integrated Assembler", /*ShowHidden=*/false, + /*ShowAllAliases=*/false, + llvm::opt::Visibility(driver::options::CC1AsOption)); + return 0; } diff --git a/builder/cc1as.h b/builder/cc1as.h index 423a916a34..4b22fc3e81 100644 --- a/builder/cc1as.h +++ b/builder/cc1as.h @@ -93,7 +93,7 @@ struct AssemblerInvocation { EmitDwarfUnwindType EmitDwarfUnwind; // Whether to emit compact-unwind for non-canonical entries. - // Note: may be overridden by other constraints. + // Note: maybe overriden by other constraints. unsigned EmitCompactUnwindNonCanonical : 1; /// The name of the relocation model to use. diff --git a/builder/sizes_test.go b/builder/sizes_test.go index 429e3f3e98..9755abe819 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -41,9 +41,9 @@ func TestBinarySize(t *testing.T) { // This is a small number of very diverse targets that we want to test. tests := []sizeTest{ // microcontrollers - {"hifive1b", "examples/echo", 4476, 280, 0, 2252}, - {"microbit", "examples/serial", 2724, 388, 8, 2256}, - {"wioterminal", "examples/pininterrupt", 5996, 1484, 116, 6816}, + {"hifive1b", "examples/echo", 4484, 280, 0, 2252}, + {"microbit", "examples/serial", 2732, 388, 8, 2256}, + {"wioterminal", "examples/pininterrupt", 6016, 1484, 116, 6816}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the diff --git a/cgo/libclang_config_llvm17.go b/cgo/libclang_config_llvm17.go index c17cca09b8..6395d8a3af 100644 --- a/cgo/libclang_config_llvm17.go +++ b/cgo/libclang_config_llvm17.go @@ -1,4 +1,4 @@ -//go:build !byollvm && !llvm15 && !llvm16 +//go:build !byollvm && llvm17 package cgo diff --git a/cgo/libclang_config_llvm18.go b/cgo/libclang_config_llvm18.go new file mode 100644 index 0000000000..3b769c622d --- /dev/null +++ b/cgo/libclang_config_llvm18.go @@ -0,0 +1,15 @@ +//go:build !byollvm && !llvm15 && !llvm16 && !llvm17 + +package cgo + +/* +#cgo linux CFLAGS: -I/usr/include/llvm-18 -I/usr/include/llvm-c-18 -I/usr/lib/llvm-18/include +#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@18/include +#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@18/include +#cgo freebsd CFLAGS: -I/usr/local/llvm18/include +#cgo linux LDFLAGS: -L/usr/lib/llvm-18/lib -lclang +#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@18/lib -lclang +#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@18/lib -lclang +#cgo freebsd LDFLAGS: -L/usr/local/llvm18/lib -lclang +*/ +import "C" diff --git a/compileopts/target.go b/compileopts/target.go index da91cfa4ff..9a7d550fab 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -319,11 +319,11 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { case "arm64": spec.CPU = "generic" if goos == "darwin" { - spec.Features = "+neon" + spec.Features = "+fp-armv8,+neon" } else if goos == "windows" { - spec.Features = "+neon,-fmv" + spec.Features = "+fp-armv8,+neon,-fmv" } else { // linux - spec.Features = "+neon,-fmv,-outline-atomics" + spec.Features = "+fp-armv8,+neon,-fmv,-outline-atomics" } case "wasm": spec.CPU = "generic" diff --git a/compiler/asserts.go b/compiler/asserts.go index 0fb112e0bc..035fda6160 100644 --- a/compiler/asserts.go +++ b/compiler/asserts.go @@ -135,7 +135,7 @@ func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, // Calculate (^uintptr(0)) >> 1, which is the max value that fits in an // uintptr if uintptrs were signed. - maxBufSize := llvm.ConstLShr(llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)), llvm.ConstInt(b.uintptrType, 1, false)) + maxBufSize := b.CreateLShr(llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)), llvm.ConstInt(b.uintptrType, 1, false), "") if elementSize > maxBufSize.ZExtValue() { b.addError(pos, fmt.Sprintf("channel element type is too big (%v bytes)", elementSize)) return @@ -150,7 +150,7 @@ func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, // Make sure maxBufSize has the same type as bufSize. if maxBufSize.Type() != bufSize.Type() { - maxBufSize = llvm.ConstZExt(maxBufSize, bufSize.Type()) + maxBufSize = b.CreateZExt(maxBufSize, bufSize.Type(), "") } // Do the check for a too large (or negative) buffer size. diff --git a/compiler/intrinsics.go b/compiler/intrinsics.go index c1d05348b5..3c7edd7c95 100644 --- a/compiler/intrinsics.go +++ b/compiler/intrinsics.go @@ -23,6 +23,8 @@ func (b *builder) defineIntrinsicFunction() { b.createMemoryCopyImpl() case name == "runtime.memzero": b.createMemoryZeroImpl() + case name == "runtime.stacksave": + b.createStackSaveImpl() case name == "runtime.KeepAlive": b.createKeepAliveImpl() case strings.HasPrefix(name, "runtime/volatile.Load"): @@ -77,6 +79,14 @@ func (b *builder) createMemoryZeroImpl() { b.CreateRetVoid() } +// createStackSaveImpl creates a call to llvm.stacksave.p0 to read the current +// stack pointer. +func (b *builder) createStackSaveImpl() { + b.createFunctionStart(true) + sp := b.readStackPointer() + b.CreateRet(sp) +} + // Return the llvm.memset.p0.i8 function declaration. func (c *compilerContext) getMemsetFunc() llvm.Value { fnName := "llvm.memset.p0.i" + strconv.Itoa(c.uintptrType.IntTypeWidth()) diff --git a/compiler/llvm.go b/compiler/llvm.go index 968d28b88b..d693f6ed73 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -451,10 +451,14 @@ func (c *compilerContext) isThumb() bool { // readStackPointer emits a LLVM intrinsic call that returns the current stack // pointer as an *i8. func (b *builder) readStackPointer() llvm.Value { - stacksave := b.mod.NamedFunction("llvm.stacksave") + name := "llvm.stacksave.p0" + if llvmutil.Version() < 18 { + name = "llvm.stacksave" // backwards compatibility with LLVM 17 and below + } + stacksave := b.mod.NamedFunction(name) if stacksave.IsNil() { fnType := llvm.FunctionType(b.dataPtrType, nil, false) - stacksave = llvm.AddFunction(b.mod, "llvm.stacksave", fnType) + stacksave = llvm.AddFunction(b.mod, name, fnType) } return b.CreateCall(stacksave.GlobalValueType(), stacksave, nil, "") } diff --git a/compiler/llvmutil/llvm.go b/compiler/llvmutil/llvm.go index d4ceee3fbc..48fddffbe5 100644 --- a/compiler/llvmutil/llvm.go +++ b/compiler/llvmutil/llvm.go @@ -8,6 +8,9 @@ package llvmutil import ( + "strconv" + "strings" + "tinygo.org/x/go-llvm" ) @@ -203,3 +206,13 @@ func AppendToGlobal(mod llvm.Module, globalName string, values ...llvm.Value) { used.SetInitializer(usedInitializer) used.SetLinkage(llvm.AppendingLinkage) } + +// Return the LLVM major version. +func Version() int { + majorStr := strings.Split(llvm.Version, ".")[0] + major, err := strconv.Atoi(majorStr) + if err != nil { + panic("unexpected error while parsing LLVM version: " + err.Error()) // should not happen + } + return major +} diff --git a/compiler/testdata/channel.ll b/compiler/testdata/channel.ll index be769e8597..65e18dea85 100644 --- a/compiler/testdata/channel.ll +++ b/compiler/testdata/channel.ll @@ -81,11 +81,11 @@ entry: %select.send.value = alloca i32, align 4 store i32 1, ptr %select.send.value, align 4 call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %select.states.alloca) - store ptr %ch1, ptr %select.states.alloca, align 8 + store ptr %ch1, ptr %select.states.alloca, align 4 %select.states.alloca.repack1 = getelementptr inbounds %runtime.chanSelectState, ptr %select.states.alloca, i32 0, i32 1 store ptr %select.send.value, ptr %select.states.alloca.repack1, align 4 %0 = getelementptr inbounds [2 x %runtime.chanSelectState], ptr %select.states.alloca, i32 0, i32 1 - store ptr %ch2, ptr %0, align 8 + store ptr %ch2, ptr %0, align 4 %.repack3 = getelementptr inbounds [2 x %runtime.chanSelectState], ptr %select.states.alloca, i32 0, i32 1, i32 1 store ptr null, ptr %.repack3, align 4 %select.result = call { i32, i1 } @runtime.tryChanSelect(ptr undef, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr undef) #4 diff --git a/compiler/testdata/defer-cortex-m-qemu.ll b/compiler/testdata/defer-cortex-m-qemu.ll index 32697ccd5b..52a3bfbabf 100644 --- a/compiler/testdata/defer-cortex-m-qemu.ll +++ b/compiler/testdata/defer-cortex-m-qemu.ll @@ -25,7 +25,7 @@ entry: %deferPtr = alloca ptr, align 4 store ptr null, ptr %deferPtr, align 4 %deferframe.buf = alloca %runtime.deferFrame, align 4 - %0 = call ptr @llvm.stacksave() + %0 = call ptr @llvm.stacksave.p0() call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4 store i32 0, ptr %defer.alloca, align 4 %defer.alloca.repack15 = getelementptr inbounds { i32, ptr }, ptr %defer.alloca, i32 0, i32 1 @@ -113,7 +113,7 @@ rundefers.end3: ; preds = %rundefers.loophead6 } ; Function Attrs: nocallback nofree nosync nounwind willreturn -declare ptr @llvm.stacksave() #3 +declare ptr @llvm.stacksave.p0() #3 declare void @runtime.setupDeferFrame(ptr dereferenceable_or_null(24), ptr, ptr) #2 @@ -136,7 +136,7 @@ entry: %deferPtr = alloca ptr, align 4 store ptr null, ptr %deferPtr, align 4 %deferframe.buf = alloca %runtime.deferFrame, align 4 - %0 = call ptr @llvm.stacksave() + %0 = call ptr @llvm.stacksave.p0() call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4 store i32 0, ptr %defer.alloca, align 4 %defer.alloca.repack22 = getelementptr inbounds { i32, ptr }, ptr %defer.alloca, i32 0, i32 1 diff --git a/compiler/testdata/gc.ll b/compiler/testdata/gc.ll index de638dad2c..a696ee409f 100644 --- a/compiler/testdata/gc.ll +++ b/compiler/testdata/gc.ll @@ -16,9 +16,9 @@ target triple = "wasm32-unknown-wasi" @main.struct2 = hidden global ptr null, align 4 @main.struct3 = hidden global ptr null, align 4 @main.struct4 = hidden global ptr null, align 4 -@main.slice1 = hidden global { ptr, i32, i32 } zeroinitializer, align 8 -@main.slice2 = hidden global { ptr, i32, i32 } zeroinitializer, align 8 -@main.slice3 = hidden global { ptr, i32, i32 } zeroinitializer, align 8 +@main.slice1 = hidden global { ptr, i32, i32 } zeroinitializer, align 4 +@main.slice2 = hidden global { ptr, i32, i32 } zeroinitializer, align 4 +@main.slice3 = hidden global { ptr, i32, i32 } zeroinitializer, align 4 @"runtime/gc.layout:62-2000000000000001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00 " } @"runtime/gc.layout:62-0001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00\00" } @"reflect/types.type:basic:complex128" = linkonce_odr constant { i8, ptr } { i8 80, ptr @"reflect/types.type:pointer:basic:complex128" }, align 4 @@ -104,19 +104,19 @@ entry: %stackalloc = alloca i8, align 1 %makeslice = call dereferenceable(5) ptr @runtime.alloc(i32 5, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %makeslice, ptr nonnull %stackalloc, ptr undef) #3 - store ptr %makeslice, ptr @main.slice1, align 8 + store ptr %makeslice, ptr @main.slice1, align 4 store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice1, i32 0, i32 1), align 4 - store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice1, i32 0, i32 2), align 8 + store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice1, i32 0, i32 2), align 4 %makeslice1 = call dereferenceable(20) ptr @runtime.alloc(i32 20, ptr nonnull inttoptr (i32 67 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %makeslice1, ptr nonnull %stackalloc, ptr undef) #3 - store ptr %makeslice1, ptr @main.slice2, align 8 + store ptr %makeslice1, ptr @main.slice2, align 4 store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice2, i32 0, i32 1), align 4 - store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice2, i32 0, i32 2), align 8 + store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice2, i32 0, i32 2), align 4 %makeslice3 = call dereferenceable(60) ptr @runtime.alloc(i32 60, ptr nonnull inttoptr (i32 71 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %makeslice3, ptr nonnull %stackalloc, ptr undef) #3 - store ptr %makeslice3, ptr @main.slice3, align 8 + store ptr %makeslice3, ptr @main.slice3, align 4 store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice3, i32 0, i32 1), align 4 - store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice3, i32 0, i32 2), align 8 + store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice3, i32 0, i32 2), align 4 ret void } diff --git a/compiler/testdata/slice.ll b/compiler/testdata/slice.ll index bc01987419..29d5ed8a41 100644 --- a/compiler/testdata/slice.ll +++ b/compiler/testdata/slice.ll @@ -122,7 +122,7 @@ entry: br i1 %slice.maxcap, label %slice.throw, label %slice.next slice.next: ; preds = %entry - %makeslice.cap = shl i32 %len, 1 + %makeslice.cap = shl nuw i32 %len, 1 %makeslice.buf = call ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %0 = insertvalue { ptr, i32, i32 } undef, ptr %makeslice.buf, 0 %1 = insertvalue { ptr, i32, i32 } %0, i32 %len, 1 @@ -164,7 +164,7 @@ entry: br i1 %slice.maxcap, label %slice.throw, label %slice.next slice.next: ; preds = %entry - %makeslice.cap = shl i32 %len, 2 + %makeslice.cap = shl nuw i32 %len, 2 %makeslice.buf = call ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %0 = insertvalue { ptr, i32, i32 } undef, ptr %makeslice.buf, 0 %1 = insertvalue { ptr, i32, i32 } %0, i32 %len, 1 diff --git a/compiler/testdata/zeromap.ll b/compiler/testdata/zeromap.ll index 510010dbea..4ad263130a 100644 --- a/compiler/testdata/zeromap.ll +++ b/compiler/testdata/zeromap.ll @@ -26,7 +26,7 @@ entry: %2 = insertvalue %main.hasPadding %1, i1 %s.b2, 2 call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value) call void @llvm.lifetime.start.p0(i64 12, ptr nonnull %hashmap.key) - store %main.hasPadding %2, ptr %hashmap.key, align 8 + store %main.hasPadding %2, ptr %hashmap.key, align 4 %3 = getelementptr inbounds i8, ptr %hashmap.key, i32 1 call void @runtime.memzero(ptr nonnull %3, i32 3, ptr undef) #5 %4 = getelementptr inbounds i8, ptr %hashmap.key, i32 9 @@ -59,7 +59,7 @@ entry: call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value) store i32 5, ptr %hashmap.value, align 4 call void @llvm.lifetime.start.p0(i64 12, ptr nonnull %hashmap.key) - store %main.hasPadding %2, ptr %hashmap.key, align 8 + store %main.hasPadding %2, ptr %hashmap.key, align 4 %3 = getelementptr inbounds i8, ptr %hashmap.key, i32 1 call void @runtime.memzero(ptr nonnull %3, i32 3, ptr undef) #5 %4 = getelementptr inbounds i8, ptr %hashmap.key, i32 9 @@ -80,7 +80,7 @@ entry: call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value) call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %hashmap.key) %s.elt = extractvalue [2 x %main.hasPadding] %s, 0 - store %main.hasPadding %s.elt, ptr %hashmap.key, align 8 + store %main.hasPadding %s.elt, ptr %hashmap.key, align 4 %hashmap.key.repack1 = getelementptr inbounds [2 x %main.hasPadding], ptr %hashmap.key, i32 0, i32 1 %s.elt2 = extractvalue [2 x %main.hasPadding] %s, 1 store %main.hasPadding %s.elt2, ptr %hashmap.key.repack1, align 4 @@ -108,7 +108,7 @@ entry: store i32 5, ptr %hashmap.value, align 4 call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %hashmap.key) %s.elt = extractvalue [2 x %main.hasPadding] %s, 0 - store %main.hasPadding %s.elt, ptr %hashmap.key, align 8 + store %main.hasPadding %s.elt, ptr %hashmap.key, align 4 %hashmap.key.repack1 = getelementptr inbounds [2 x %main.hasPadding], ptr %hashmap.key, i32 0, i32 1 %s.elt2 = extractvalue [2 x %main.hasPadding] %s, 1 store %main.hasPadding %s.elt2, ptr %hashmap.key.repack1, align 4 diff --git a/flake.nix b/flake.nix index 60c2b2534e..9ba3278401 100644 --- a/flake.nix +++ b/flake.nix @@ -77,7 +77,7 @@ export MD5SUM=md5sum # Ugly hack to make the Clang resources directory available. - export GOFLAGS="\"-ldflags=-X github.com/tinygo-org/tinygo/goenv.clangResourceDir=${llvmPackages_17.clang.cc.lib}/lib/clang/17"\" + export GOFLAGS="\"-ldflags=-X github.com/tinygo-org/tinygo/goenv.clangResourceDir=${llvmPackages_17.clang.cc.lib}/lib/clang/17\" -tags=llvm17" ''; }; } diff --git a/go.mod b/go.mod index bf6d87ae6e..6d40cd6498 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( golang.org/x/sys v0.16.0 golang.org/x/tools v0.17.0 gopkg.in/yaml.v2 v2.4.0 - tinygo.org/x/go-llvm v0.0.0-20240106122909-c2c543540318 + tinygo.org/x/go-llvm v0.0.0-20240518103902-697964f2a9dc ) require ( diff --git a/go.sum b/go.sum index 7746bf7eb1..59d33c94b9 100644 --- a/go.sum +++ b/go.sum @@ -105,5 +105,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -tinygo.org/x/go-llvm v0.0.0-20240106122909-c2c543540318 h1:4KjZvPtcN1UwobevcGbdzeinx0L1i8HDdJu84bu7NI8= -tinygo.org/x/go-llvm v0.0.0-20240106122909-c2c543540318/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0= +tinygo.org/x/go-llvm v0.0.0-20240518103902-697964f2a9dc h1:TCzibFa4oLu+njEP3fnRUmZ+QQeb8BjtOwctgcjzL0k= +tinygo.org/x/go-llvm v0.0.0-20240518103902-697964f2a9dc/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0= diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go index ac7cd25c93..a93260ba85 100644 --- a/src/runtime/runtime.go +++ b/src/runtime/runtime.go @@ -41,11 +41,9 @@ func memmove(dst, src unsafe.Pointer, size uintptr) // like llvm.memset.p0.i32(ptr, 0, size, false). func memzero(ptr unsafe.Pointer, size uintptr) -// This intrinsic returns the current stack pointer. -// It is normally used together with llvm.stackrestore but also works to get the -// current stack pointer in a platform-independent way. -// -//export llvm.stacksave +// Return the current stack pointer using the llvm.stacksave.p0 intrinsic. +// It is normally used together with llvm.stackrestore.p0 but also works to get +// the current stack pointer in a platform-independent way. func stacksave() unsafe.Pointer //export strlen diff --git a/targets/esp32c3.json b/targets/esp32c3.json index 5a1e70626d..9d1c5cff77 100644 --- a/targets/esp32c3.json +++ b/targets/esp32c3.json @@ -1,6 +1,6 @@ { "inherits": ["riscv32"], - "features": "+32bit,+c,+m,-a,-d,-e,-experimental-smaia,-experimental-ssaia,-experimental-zacas,-experimental-zfa,-experimental-zfbfmin,-experimental-zicond,-experimental-zihintntl,-experimental-ztso,-experimental-zvbb,-experimental-zvbc,-experimental-zvfbfmin,-experimental-zvfbfwma,-experimental-zvkg,-experimental-zvkn,-experimental-zvknc,-experimental-zvkned,-experimental-zvkng,-experimental-zvknha,-experimental-zvknhb,-experimental-zvks,-experimental-zvksc,-experimental-zvksed,-experimental-zvksg,-experimental-zvksh,-experimental-zvkt,-f,-h,-relax,-save-restore,-svinval,-svnapot,-svpbmt,-v,-xcvbitmanip,-xcvmac,-xsfcie,-xsfvcp,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmp,-zcmt,-zdinx,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zicbom,-zicbop,-zicboz,-zicntr,-zicsr,-zifencei,-zihintpause,-zihpm,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-zmmul,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfh,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", + "features": "+32bit,+c,+m,-a,-d,-e,-experimental-zacas,-experimental-zcmop,-experimental-zfbfmin,-experimental-zicfilp,-experimental-zicfiss,-experimental-zimop,-experimental-ztso,-experimental-zvfbfmin,-experimental-zvfbfwma,-f,-h,-relax,-smaia,-smepmp,-ssaia,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-za128rs,-za64rs,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmp,-zcmt,-zdinx,-zfa,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-zmmul,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-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"], "serial": "usb", "rtlib": "compiler-rt", diff --git a/targets/fe310.json b/targets/fe310.json index a51488358d..b96ae6d684 100644 --- a/targets/fe310.json +++ b/targets/fe310.json @@ -1,6 +1,6 @@ { "inherits": ["riscv32"], "cpu": "sifive-e31", - "features": "+32bit,+a,+c,+m,-d,-e,-experimental-smaia,-experimental-ssaia,-experimental-zacas,-experimental-zfa,-experimental-zfbfmin,-experimental-zicond,-experimental-zihintntl,-experimental-ztso,-experimental-zvbb,-experimental-zvbc,-experimental-zvfbfmin,-experimental-zvfbfwma,-experimental-zvkg,-experimental-zvkn,-experimental-zvknc,-experimental-zvkned,-experimental-zvkng,-experimental-zvknha,-experimental-zvknhb,-experimental-zvks,-experimental-zvksc,-experimental-zvksed,-experimental-zvksg,-experimental-zvksh,-experimental-zvkt,-f,-h,-relax,-save-restore,-svinval,-svnapot,-svpbmt,-v,-xcvbitmanip,-xcvmac,-xsfcie,-xsfvcp,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmp,-zcmt,-zdinx,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zicbom,-zicbop,-zicboz,-zicntr,-zicsr,-zifencei,-zihintpause,-zihpm,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-zmmul,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfh,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", + "features": "+32bit,+a,+c,+m,-d,-e,-experimental-zacas,-experimental-zcmop,-experimental-zfbfmin,-experimental-zicfilp,-experimental-zicfiss,-experimental-zimop,-experimental-ztso,-experimental-zvfbfmin,-experimental-zvfbfwma,-f,-h,-relax,-smaia,-smepmp,-ssaia,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-za128rs,-za64rs,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmp,-zcmt,-zdinx,-zfa,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-zmmul,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-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 778e403c67..d95a6f5113 100644 --- a/targets/k210.json +++ b/targets/k210.json @@ -1,6 +1,6 @@ { "inherits": ["riscv64"], - "features": "+64bit,+a,+c,+d,+f,+m,+zicsr,+zifencei,-e,-experimental-smaia,-experimental-ssaia,-experimental-zacas,-experimental-zfa,-experimental-zfbfmin,-experimental-zicond,-experimental-zihintntl,-experimental-ztso,-experimental-zvbb,-experimental-zvbc,-experimental-zvfbfmin,-experimental-zvfbfwma,-experimental-zvkg,-experimental-zvkn,-experimental-zvknc,-experimental-zvkned,-experimental-zvkng,-experimental-zvknha,-experimental-zvknhb,-experimental-zvks,-experimental-zvksc,-experimental-zvksed,-experimental-zvksg,-experimental-zvksh,-experimental-zvkt,-h,-relax,-save-restore,-svinval,-svnapot,-svpbmt,-v,-xcvbitmanip,-xcvmac,-xsfcie,-xsfvcp,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmp,-zcmt,-zdinx,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zicbom,-zicbop,-zicboz,-zicntr,-zihintpause,-zihpm,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-zmmul,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfh,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", + "features": "+64bit,+a,+c,+d,+f,+m,+zicsr,+zifencei,-e,-experimental-zacas,-experimental-zcmop,-experimental-zfbfmin,-experimental-zicfilp,-experimental-zicfiss,-experimental-zimop,-experimental-ztso,-experimental-zvfbfmin,-experimental-zvfbfwma,-h,-relax,-smaia,-smepmp,-ssaia,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-za128rs,-za64rs,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmp,-zcmt,-zdinx,-zfa,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zihintntl,-zihintpause,-zihpm,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-zmmul,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-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 7f9c5e395d..cbe7d3c045 100644 --- a/targets/riscv-qemu.json +++ b/targets/riscv-qemu.json @@ -1,6 +1,6 @@ { "inherits": ["riscv32"], - "features": "+32bit,+a,+c,+m,-d,-e,-experimental-smaia,-experimental-ssaia,-experimental-zacas,-experimental-zfa,-experimental-zfbfmin,-experimental-zicond,-experimental-zihintntl,-experimental-ztso,-experimental-zvbb,-experimental-zvbc,-experimental-zvfbfmin,-experimental-zvfbfwma,-experimental-zvkg,-experimental-zvkn,-experimental-zvknc,-experimental-zvkned,-experimental-zvkng,-experimental-zvknha,-experimental-zvknhb,-experimental-zvks,-experimental-zvksc,-experimental-zvksed,-experimental-zvksg,-experimental-zvksh,-experimental-zvkt,-f,-h,-relax,-save-restore,-svinval,-svnapot,-svpbmt,-v,-xcvbitmanip,-xcvmac,-xsfcie,-xsfvcp,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmp,-zcmt,-zdinx,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zicbom,-zicbop,-zicboz,-zicntr,-zicsr,-zifencei,-zihintpause,-zihpm,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-zmmul,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfh,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", + "features": "+32bit,+a,+c,+m,-d,-e,-experimental-zacas,-experimental-zcmop,-experimental-zfbfmin,-experimental-zicfilp,-experimental-zicfiss,-experimental-zimop,-experimental-ztso,-experimental-zvfbfmin,-experimental-zvfbfwma,-f,-h,-relax,-smaia,-smepmp,-ssaia,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-za128rs,-za64rs,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmp,-zcmt,-zdinx,-zfa,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-zmmul,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-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"], "default-stack-size": 4096, "linkerscript": "targets/riscv-qemu.ld", diff --git a/transform/optimizer.go b/transform/optimizer.go index 4c0ccfc2ea..05533b6a4a 100644 --- a/transform/optimizer.go +++ b/transform/optimizer.go @@ -8,6 +8,7 @@ import ( "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compiler/ircheck" + "github.com/tinygo-org/tinygo/compiler/llvmutil" "tinygo.org/x/go-llvm" ) @@ -52,7 +53,12 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error { // Run some preparatory passes for the Go optimizer. po := llvm.NewPassBuilderOptions() defer po.Dispose() - err := mod.RunPasses("globaldce,globalopt,ipsccp,instcombine,adce,function-attrs", llvm.TargetMachine{}, po) + optPasses := "globaldce,globalopt,ipsccp,instcombine,adce,function-attrs" + if llvmutil.Version() < 18 { + // LLVM 17 doesn't have the no-verify-fixpoint flag. + optPasses = "globaldce,globalopt,ipsccp,instcombine,adce,function-attrs" + } + err := mod.RunPasses(optPasses, llvm.TargetMachine{}, po) if err != nil { return []error{fmt.Errorf("could not build pass pipeline: %w", err)} } @@ -75,7 +81,7 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error { // After interfaces are lowered, there are many more opportunities for // interprocedural optimizations. To get them to work, function // attributes have to be updated first. - err = mod.RunPasses("globaldce,globalopt,ipsccp,instcombine,adce,function-attrs", llvm.TargetMachine{}, po) + err = mod.RunPasses(optPasses, llvm.TargetMachine{}, po) if err != nil { return []error{fmt.Errorf("could not build pass pipeline: %w", err)} } From f7c0466f78fe97c578f298de639fd5248cc91ee4 Mon Sep 17 00:00:00 2001 From: frenkel26 <96975048+frenkel26@users.noreply.github.com> Date: Tue, 28 May 2024 15:09:59 +0300 Subject: [PATCH 055/444] compiler,reflect: fix NumMethods for Interface type --- compiler/interface.go | 15 +++++++++------ compiler/testdata/interface.ll | 2 +- testdata/reflect.go | 1 + testdata/reflect.txt | 5 +++++ 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/compiler/interface.go b/compiler/interface.go index 564e3a4146..b6dffd6436 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -124,16 +124,19 @@ func (c *compilerContext) pkgPathPtr(pkgpath string) llvm.Value { func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { ms := c.program.MethodSets.MethodSet(typ) hasMethodSet := ms.Len() != 0 - if _, ok := typ.Underlying().(*types.Interface); ok { + _, isInterface := typ.Underlying().(*types.Interface) + if isInterface { hasMethodSet = false } + // As defined in https://pkg.go.dev/reflect#Type: + // NumMethod returns the number of methods accessible using Method. + // For a non-interface type, it returns the number of exported methods. + // For an interface type, it returns the number of exported and unexported methods. var numMethods int - if hasMethodSet { - for i := 0; i < ms.Len(); i++ { - if ms.At(i).Obj().Exported() { - numMethods++ - } + for i := 0; i < ms.Len(); i++ { + if isInterface || ms.At(i).Obj().Exported() { + numMethods++ } } diff --git a/compiler/testdata/interface.ll b/compiler/testdata/interface.ll index ff3a04d912..801f370d58 100644 --- a/compiler/testdata/interface.ll +++ b/compiler/testdata/interface.ll @@ -9,7 +9,7 @@ target triple = "wasm32-unknown-wasi" @"reflect/types.type:basic:int" = linkonce_odr constant { i8, ptr } { i8 -62, ptr @"reflect/types.type:pointer:basic:int" }, align 4 @"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:int" }, align 4 @"reflect/types.type:pointer:named:error" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:named:error" }, align 4 -@"reflect/types.type:named:error" = linkonce_odr constant { i8, i16, ptr, ptr, ptr, [7 x i8] } { i8 116, i16 0, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}", ptr @"reflect/types.type.pkgpath.empty", [7 x i8] c".error\00" }, align 4 +@"reflect/types.type:named:error" = linkonce_odr constant { i8, i16, ptr, ptr, ptr, [7 x i8] } { i8 116, i16 1, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}", ptr @"reflect/types.type.pkgpath.empty", [7 x i8] c".error\00" }, align 4 @"reflect/types.type.pkgpath.empty" = linkonce_odr unnamed_addr constant [1 x i8] zeroinitializer, align 1 @"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr } { i8 84, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }, align 4 @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4 diff --git a/testdata/reflect.go b/testdata/reflect.go index 1a92e47ab7..595aaa8cfa 100644 --- a/testdata/reflect.go +++ b/testdata/reflect.go @@ -456,6 +456,7 @@ func showValue(rv reflect.Value, indent string) { case reflect.Interface: println(indent + " interface") println(indent+" nil:", rv.IsNil()) + println(indent+" NumMethod:", rv.NumMethod()) if !rv.IsNil() { showValue(rv.Elem(), indent+" ") } diff --git a/testdata/reflect.txt b/testdata/reflect.txt index e4a92a5e1c..3f1b5a17bf 100644 --- a/testdata/reflect.txt +++ b/testdata/reflect.txt @@ -80,6 +80,7 @@ reflect type: ptr reflect type: interface settable=true addrable=true interface nil: true + NumMethod: 1 reflect type: ptr pointer: true int nil: false @@ -240,6 +241,7 @@ reflect type: struct reflect type: interface caninterface=false interface nil: true + NumMethod: 1 reflect type: struct struct: 3 field: 0 a @@ -371,12 +373,14 @@ reflect type: slice comparable=false reflect type: interface settable=true addrable=true interface nil: false + NumMethod: 0 reflect type: int int: 3 indexing: 1 reflect type: interface settable=true addrable=true interface nil: false + NumMethod: 0 reflect type: string string: str 3 reflect type: uint8 @@ -389,6 +393,7 @@ reflect type: slice comparable=false reflect type: interface settable=true addrable=true interface nil: false + NumMethod: 0 reflect type: complex128 complex: (-4.000000e+000+2.500000e+000i) reflect type: ptr From 7b7601d77c681a3c2ce53ce3f89dbdfc35eec492 Mon Sep 17 00:00:00 2001 From: diamondburned Date: Tue, 28 May 2024 13:53:45 -0700 Subject: [PATCH 056/444] os/user: add stubs for `Lookup{,Group}` and `Group` --- src/os/user/user.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/os/user/user.go b/src/os/user/user.go index 7939380fb9..ee63625f2b 100644 --- a/src/os/user/user.go +++ b/src/os/user/user.go @@ -39,3 +39,21 @@ type User struct { func Current() (*User, error) { return nil, errors.New("user: Current not implemented") } + +// Lookup always returns an error. +func Lookup(username string) (*User, error) { + return nil, errors.New("user: Lookup not implemented") +} + +// Group represents a grouping of users. +// +// On POSIX systems Gid contains a decimal number representing the group ID. +type Group struct { + Gid string // group ID + Name string // group name +} + +// LookupGroup always returns an error. +func LookupGroup(name string) (*Group, error) { + return nil, errors.New("user: LookupGroup not implemented") +} From c383a407e337f08831efd196bae708977a393028 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 3 Mar 2024 20:53:45 +0100 Subject: [PATCH 057/444] cgo: do a basic test that math functions work They should, but we weren't testing this. I discovered this while working on https://github.com/tinygo-org/macos-minimal-sdk/pull/4 which will likely make math.h not work anymore. So I wanted to make sure we have a test in place before we update that dependency. --- testdata/cgo/main.c | 5 +++++ testdata/cgo/main.go | 5 +++++ testdata/cgo/main.h | 2 ++ testdata/cgo/out.txt | 2 ++ 4 files changed, 14 insertions(+) diff --git a/testdata/cgo/main.c b/testdata/cgo/main.c index 31b60704f5..7fb702ed67 100644 --- a/testdata/cgo/main.c +++ b/testdata/cgo/main.c @@ -1,3 +1,4 @@ +#include #include "main.h" int global = 3; @@ -67,3 +68,7 @@ void unionSetData(short f0, short f1, short f2) { void arraydecay(int buf1[5], int buf2[3][8], int buf3[4][7][2]) { // Do nothing. } + +double doSqrt(double x) { + return sqrt(x); +} diff --git a/testdata/cgo/main.go b/testdata/cgo/main.go index aac5221f42..fa3380bcea 100644 --- a/testdata/cgo/main.go +++ b/testdata/cgo/main.go @@ -2,6 +2,7 @@ package main /* #include +#include int fortytwo(void); #include "main.h" #include "test.h" @@ -171,6 +172,10 @@ func main() { C.strcpy((*C.char)(unsafe.Pointer(&buf2[0])), (*C.char)(unsafe.Pointer(&buf1[0]))) println("copied string:", string(buf2[:C.strlen((*C.char)(unsafe.Pointer(&buf2[0])))])) + // libc: test libm functions (normally bundled in libc) + println("CGo sqrt(3):", C.sqrt(3)) + println("C sqrt(3):", C.doSqrt(3)) + // libc: test basic stdio functionality putsBuf := []byte("line written using C puts\x00") C.puts((*C.char)(unsafe.Pointer(&putsBuf[0]))) diff --git a/testdata/cgo/main.h b/testdata/cgo/main.h index 702dab0c00..f5405ade62 100644 --- a/testdata/cgo/main.h +++ b/testdata/cgo/main.h @@ -150,3 +150,5 @@ extern int global; // Test array decaying into a pointer. typedef int arraydecay_buf3[4][7][2]; void arraydecay(int buf1[5], int buf2[3][8], arraydecay_buf3 buf3); + +double doSqrt(double); diff --git a/testdata/cgo/out.txt b/testdata/cgo/out.txt index 781187b50c..ae92c87a7c 100644 --- a/testdata/cgo/out.txt +++ b/testdata/cgo/out.txt @@ -74,4 +74,6 @@ C.GoString(nil): len(C.GoStringN(nil, 0)): 0 len(C.GoBytes(nil, 0)): 0 copied string: foobar +CGo sqrt(3): +1.732051e+000 +C sqrt(3): +1.732051e+000 line written using C puts From b6fd0c818e1fa0393cd667183f453a638ea6fdf6 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Tue, 28 May 2024 10:40:42 -0700 Subject: [PATCH 058/444] src/reflect: uncomment more tests that pass --- src/reflect/all_test.go | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index ef8c7da433..4c2fcc48be 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -698,8 +698,6 @@ func TestMapSetNil(t *testing.T) { } } -/* - func TestAll(t *testing.T) { testType(t, 1, TypeOf((int8)(0)), "int8") testType(t, 2, TypeOf((*int8)(nil)).Elem(), "int8") @@ -747,8 +745,6 @@ func TestAll(t *testing.T) { testType(t, 14, typ, "[]uint32") } -*/ - func TestInterfaceGet(t *testing.T) { var inter struct { E any @@ -1272,8 +1268,6 @@ func TestDeepEqualUnexportedMap(t *testing.T) { } } -/* - var deepEqualPerfTests = []struct { x, y any }{ @@ -1339,8 +1333,6 @@ func TestDeepEqualAllocs(t *testing.T) { } } -*/ - func check2ndField(x any, offs uintptr, t *testing.T) { s := ValueOf(x) f := s.Type().Field(1) @@ -1600,7 +1592,8 @@ func TestIsZero(t *testing.T) { */ } -/* +// extra comment for gofmt + func TestInterfaceExtraction(t *testing.T) { var s struct { W io.Writer @@ -1612,9 +1605,6 @@ func TestInterfaceExtraction(t *testing.T) { t.Error("Interface() on interface: ", v, s.W) } } - -*/ - func TestNilPtrValueSub(t *testing.T) { var pi *int if pv := ValueOf(pi); pv.Elem().IsValid() { @@ -3368,6 +3358,8 @@ func TestNestedMethods(t *testing.T) { } } +*/ + type unexp struct{} func (*unexp) f() (int32, int8) { return 7, 7 } @@ -3379,8 +3371,6 @@ type unexpI interface { var unexpi unexpI = new(unexp) -/* - func TestUnexportedMethods(t *testing.T) { typ := TypeOf(unexpi) @@ -3389,8 +3379,6 @@ func TestUnexportedMethods(t *testing.T) { } } -*/ - type InnerInt struct { X int } @@ -3432,6 +3420,8 @@ func TestEmbeddedMethods(t *testing.T) { } } +*/ + type FuncDDD func(...any) error func (f FuncDDD) M() {} @@ -3443,6 +3433,7 @@ func TestNumMethodOnDDD(t *testing.T) { } } +/* func TestPtrTo(t *testing.T) { // This block of code means that the ptrToThis field of the // reflect data for *unsafe.Pointer is non zero, see @@ -3489,6 +3480,8 @@ func TestPtrToGC(t *testing.T) { } } +*/ + func TestAddr(t *testing.T) { var p struct { X, Y int @@ -3591,8 +3584,6 @@ func TestAllocations(t *testing.T) { }) } -*/ - func TestSmallNegativeInt(t *testing.T) { i := int16(-1) v := ValueOf(i) From 272fea13e71b677c4fa40a0315c89cf97cbd84d7 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Thu, 30 May 2024 21:01:50 -0700 Subject: [PATCH 059/444] builder: keep un-wasm-opt'd .wasm if -work was passed --- builder/build.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/builder/build.go b/builder/build.go index 940439667b..e415781fef 100644 --- a/builder/build.go +++ b/builder/build.go @@ -825,10 +825,18 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe args = append(args, "--asyncify") } + exeunopt := result.Executable + + if config.Options.Work { + // Keep the work direction around => don't overwrite the .wasm binary with the optimized version + exeunopt += ".pre-wasm-opt" + os.Rename(result.Executable, exeunopt) + } + args = append(args, opt, "-g", - result.Executable, + exeunopt, "--output", result.Executable, ) From bfccf3592a13a6df6c22000289f3cd56af36ec25 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Thu, 30 May 2024 21:02:18 -0700 Subject: [PATCH 060/444] builder: make sure wasm-opt command line is printed if asked --- builder/build.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/builder/build.go b/builder/build.go index e415781fef..c569bb1d21 100644 --- a/builder/build.go +++ b/builder/build.go @@ -840,6 +840,10 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe "--output", result.Executable, ) + if config.Options.PrintCommands != nil { + config.Options.PrintCommands(goenv.Get("WASMOPT"), args...) + } + cmd := exec.Command(goenv.Get("WASMOPT"), args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr From 20b58a0128c685f2a2b3ac4f157a670f70eb5f44 Mon Sep 17 00:00:00 2001 From: leongross Date: Sun, 2 Jun 2024 09:52:31 +0200 Subject: [PATCH 061/444] Add signal stubs (#4270) os: init signal ignore stub and add other stubs Signed-off-by: leongross --- src/os/exec.go | 5 +++++ src/os/signal/signal.go | 14 ++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 src/os/signal/signal.go diff --git a/src/os/exec.go b/src/os/exec.go index a80c431696..4ae0a9b98b 100644 --- a/src/os/exec.go +++ b/src/os/exec.go @@ -72,3 +72,8 @@ func (p *Process) Kill() error { func (p *Process) Signal(sig Signal) error { return ErrNotImplemented } + +func Ignore(sig ...Signal) { + // leave all the signals unaltered + return +} diff --git a/src/os/signal/signal.go b/src/os/signal/signal.go new file mode 100644 index 0000000000..41ceaf4853 --- /dev/null +++ b/src/os/signal/signal.go @@ -0,0 +1,14 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package signal + +import ( + "os" +) + +// Just stubbing the functions for now since signal handling is not yet implemented in tinygo +func Reset(sig ...os.Signal) {} +func Ignore(sig ...os.Signal) {} +func Notify(c chan<- os.Signal, sig ...os.Signal) {} From 3a8ef33c7201ae7cd85c3ce1d012ecb707e32f67 Mon Sep 17 00:00:00 2001 From: leongross Date: Thu, 4 Apr 2024 11:52:53 +0200 Subject: [PATCH 062/444] add FindProcess for posix Signed-off-by: leongross --- src/os/exec.go | 7 ++++++- src/os/exec_posix.go | 5 +++++ src/os/exec_test.go | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 src/os/exec_test.go diff --git a/src/os/exec.go b/src/os/exec.go index 4ae0a9b98b..2adcf3c1b8 100644 --- a/src/os/exec.go +++ b/src/os/exec.go @@ -58,7 +58,7 @@ type Process struct { } func StartProcess(name string, argv []string, attr *ProcAttr) (*Process, error) { - return nil, &PathError{"fork/exec", name, ErrNotImplemented} + return nil, &PathError{Op: "fork/exec", Path: name, Err: ErrNotImplemented} } func (p *Process) Wait() (*ProcessState, error) { @@ -77,3 +77,8 @@ func Ignore(sig ...Signal) { // leave all the signals unaltered return } + +// Keep compatibility with golang and always succeed and return new proc with pid on Linux. +func FindProcess(pid int) (*Process, error) { + return findProcess(pid) +} diff --git a/src/os/exec_posix.go b/src/os/exec_posix.go index 3ccb6963bb..84843a2813 100644 --- a/src/os/exec_posix.go +++ b/src/os/exec_posix.go @@ -19,3 +19,8 @@ var ( Interrupt Signal = syscall.SIGINT Kill Signal = syscall.SIGKILL ) + +// Keep compatible with golang and always succeed and return new proc with pid on Linux. +func findProcess(pid int) (*Process, error) { + return &Process{Pid: pid}, nil +} diff --git a/src/os/exec_test.go b/src/os/exec_test.go new file mode 100644 index 0000000000..032763abfe --- /dev/null +++ b/src/os/exec_test.go @@ -0,0 +1,32 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package os_test + +import ( + . "os" + "runtime" + "testing" +) + +func TestFindProcess(t *testing.T) { + // NOTE: For now, we only test the Linux case since only exec_posix.go is currently the only implementation. + if runtime.GOOS == "linux" { + // Linux guarantees that there is pid 0 + proc, err := FindProcess(0) + if err != nil { + t.Error("FindProcess(0): wanted err == nil, got %v:", err) + } + + if proc.Pid != 0 { + t.Error("Expected pid 0, got: ", proc.Pid) + } + + pid0 := Process{Pid: 0} + if *proc != pid0 { + t.Error("Expected &Process{Pid: 0}, got", *proc) + } + } + +} From 3023ba584bb284fef263c4f4280a7f0e38929f1f Mon Sep 17 00:00:00 2001 From: leongross Date: Tue, 7 May 2024 17:06:19 +0200 Subject: [PATCH 063/444] Add process.Release for unix Signed-off-by: leongross --- src/os/exec.go | 10 ++++++++++ src/os/exec_posix.go | 9 +++++++++ src/os/exec_test.go | 25 +++++++++++-------------- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/os/exec.go b/src/os/exec.go index 2adcf3c1b8..cf295e6fb7 100644 --- a/src/os/exec.go +++ b/src/os/exec.go @@ -62,6 +62,9 @@ func StartProcess(name string, argv []string, attr *ProcAttr) (*Process, error) } func (p *Process) Wait() (*ProcessState, error) { + if p.Pid == -1 { + return nil, syscall.EINVAL + } return nil, ErrNotImplemented } @@ -78,6 +81,13 @@ func Ignore(sig ...Signal) { return } +// Release releases any resources associated with the Process p, +// rendering it unusable in the future. +// Release only needs to be called if Wait is not. +func (p *Process) Release() error { + return p.release() +} + // Keep compatibility with golang and always succeed and return new proc with pid on Linux. func FindProcess(pid int) (*Process, error) { return findProcess(pid) diff --git a/src/os/exec_posix.go b/src/os/exec_posix.go index 84843a2813..0a968d9a10 100644 --- a/src/os/exec_posix.go +++ b/src/os/exec_posix.go @@ -7,6 +7,7 @@ package os import ( + "runtime" "syscall" ) @@ -24,3 +25,11 @@ var ( func findProcess(pid int) (*Process, error) { return &Process{Pid: pid}, nil } + +func (p *Process) release() error { + // NOOP for unix. + p.Pid = -1 + // no need for a finalizer anymore + runtime.SetFinalizer(p, nil) + return nil +} diff --git a/src/os/exec_test.go b/src/os/exec_test.go index 032763abfe..e960cea023 100644 --- a/src/os/exec_test.go +++ b/src/os/exec_test.go @@ -12,21 +12,18 @@ import ( func TestFindProcess(t *testing.T) { // NOTE: For now, we only test the Linux case since only exec_posix.go is currently the only implementation. - if runtime.GOOS == "linux" { - // Linux guarantees that there is pid 0 - proc, err := FindProcess(0) - if err != nil { - t.Error("FindProcess(0): wanted err == nil, got %v:", err) - } - - if proc.Pid != 0 { - t.Error("Expected pid 0, got: ", proc.Pid) - } + // Linux guarantees that there is pid 0 + proc, err := FindProcess(0) + if err != nil { + t.Error("FindProcess(0): wanted err == nil, got %v:", err) + } - pid0 := Process{Pid: 0} - if *proc != pid0 { - t.Error("Expected &Process{Pid: 0}, got", *proc) - } + if proc.Pid != 0 { + t.Error("Expected pid 0, got: ", proc.Pid) } + pid0 := Process{Pid: 0} + if *proc != pid0 { + t.Error("Expected &Process{Pid: 0}, got", *proc) + } } From e731a31eb93c14b82396b5d8756b2e5042092093 Mon Sep 17 00:00:00 2001 From: leongross Date: Thu, 16 May 2024 15:01:45 +0200 Subject: [PATCH 064/444] update fpm ci, fixup import Signed-off-by: leongross --- src/os/exec_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/src/os/exec_test.go b/src/os/exec_test.go index e960cea023..b8adfb5487 100644 --- a/src/os/exec_test.go +++ b/src/os/exec_test.go @@ -6,7 +6,6 @@ package os_test import ( . "os" - "runtime" "testing" ) From ae6220262acac838dc092f915fd41f3f74aa8391 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 6 Jun 2024 19:36:23 +0200 Subject: [PATCH 065/444] cgo: implement shift operations in preprocessor macros --- cgo/const.go | 14 ++++++++++++-- cgo/const_test.go | 4 ++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/cgo/const.go b/cgo/const.go index 6420af4b92..2d0e29e10d 100644 --- a/cgo/const.go +++ b/cgo/const.go @@ -17,6 +17,8 @@ var ( token.OR: precedenceOr, token.XOR: precedenceXor, token.AND: precedenceAnd, + token.SHL: precedenceShift, + token.SHR: precedenceShift, token.ADD: precedenceAdd, token.SUB: precedenceAdd, token.MUL: precedenceMul, @@ -25,11 +27,13 @@ var ( } ) +// See: https://en.cppreference.com/w/c/language/operator_precedence const ( precedenceLowest = iota + 1 precedenceOr precedenceXor precedenceAnd + precedenceShift precedenceAdd precedenceMul precedencePrefix @@ -82,7 +86,7 @@ func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) { for t.peekToken != token.EOF && precedence < precedences[t.peekToken] { switch t.peekToken { - case token.OR, token.XOR, token.AND, token.ADD, token.SUB, token.MUL, token.QUO, token.REM: + case token.OR, token.XOR, token.AND, token.SHL, token.SHR, token.ADD, token.SUB, token.MUL, token.QUO, token.REM: t.Next() leftExpr, err = parseBinaryExpr(t, leftExpr) } @@ -205,13 +209,19 @@ func (t *tokenizer) Next() { // https://en.cppreference.com/w/cpp/string/byte/isspace t.peekPos++ t.buf = t.buf[1:] - case len(t.buf) >= 2 && (string(t.buf[:2]) == "||" || string(t.buf[:2]) == "&&"): + case len(t.buf) >= 2 && (string(t.buf[:2]) == "||" || string(t.buf[:2]) == "&&" || string(t.buf[:2]) == "<<" || string(t.buf[:2]) == ">>"): // Two-character tokens. switch c { case '&': t.peekToken = token.LAND case '|': t.peekToken = token.LOR + case '<': + t.peekToken = token.SHL + case '>': + t.peekToken = token.SHR + default: + panic("unreachable") } t.peekValue = t.buf[:2] t.buf = t.buf[2:] diff --git a/cgo/const_test.go b/cgo/const_test.go index 305416e84d..d150e751f4 100644 --- a/cgo/const_test.go +++ b/cgo/const_test.go @@ -40,6 +40,10 @@ func TestParseConst(t *testing.T) { {`5&5`, `5 & 5`}, {`5|5`, `5 | 5`}, {`5^5`, `5 ^ 5`}, + {`5<<5`, `5 << 5`}, + {`5>>5`, `5 >> 5`}, + {`5>>5 + 3`, `5 >> (5 + 3)`}, + {`5>>5 ^ 3`, `5>>5 ^ 3`}, {`5||5`, `error: 1:2: unexpected token ||, expected end of expression`}, // logical binops aren't supported yet {`(5/5)`, `(5 / 5)`}, {`1 - 2`, `1 - 2`}, From ad6c89bf64cef2e616b5e73554db7f2d7e915477 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Fri, 7 Jun 2024 21:44:58 -0700 Subject: [PATCH 066/444] transform/rtcalls: Bail fast if can't convert pointer There's no need to keep looping if one of the uses makes it impossible to convert a call to `runtime.stringToBytes()` with a raw pointer. Signed-off-by: L. Pereira --- transform/rtcalls.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transform/rtcalls.go b/transform/rtcalls.go index 0b6feff21f..8310fc9f19 100644 --- a/transform/rtcalls.go +++ b/transform/rtcalls.go @@ -34,7 +34,7 @@ func OptimizeStringToBytes(mod llvm.Module) { if use.IsAExtractValueInst().IsNil() { // Expected an extractvalue, but this is something else. canConvertPointer = false - continue + break } switch use.Type().TypeKind() { case llvm.IntegerTypeKind: @@ -49,7 +49,7 @@ func OptimizeStringToBytes(mod llvm.Module) { // There is a store to the byte slice. This means that none // of the pointer uses can't be propagated. canConvertPointer = false - continue + break } // It may be that the pointer value can be propagated, if all of // the pointer uses are readonly. From 880e9404177bb2dae45a4fead141e96cd4b2024a Mon Sep 17 00:00:00 2001 From: deadprogram Date: Mon, 10 Jun 2024 19:43:15 +0200 Subject: [PATCH 067/444] targets: add cyw43439 tag to badger2040-w and also add new pico-w target for wireless support Signed-off-by: deadprogram --- targets/badger2040-w.json | 2 +- targets/pico-w.json | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 targets/pico-w.json diff --git a/targets/badger2040-w.json b/targets/badger2040-w.json index 4b29dc85b9..7b7e729d17 100644 --- a/targets/badger2040-w.json +++ b/targets/badger2040-w.json @@ -3,7 +3,7 @@ "rp2040" ], "serial-port": ["2e8a:0003"], - "build-tags": ["badger2040_w"], + "build-tags": ["badger2040_w", "cyw43439"], "ldflags": [ "--defsym=__flash_size=1020K" ], diff --git a/targets/pico-w.json b/targets/pico-w.json new file mode 100644 index 0000000000..0eff1afca5 --- /dev/null +++ b/targets/pico-w.json @@ -0,0 +1,4 @@ +{ + "inherits": ["pico"], + "build-tags": ["pico-w", "cyw43439"] +} From 077b35e9ad030d7dcf1c35249378c5fa1608c2df Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 12 Jun 2024 13:28:30 +0200 Subject: [PATCH 068/444] all: drop support for Go 1.18 Go 1.18 has been unsupported for quite a while now (the oldest supported version is Go 1.21). But more importantly, the golang.org/x/tools module now requires Go 1.19 or later. So we'll drop this older version. --- .circleci/config.yml | 6 +++--- BUILDING.md | 2 +- builder/config.go | 4 ++-- compiler/interface.go | 3 +-- go.mod | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a940406065..8d104de04e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -98,9 +98,9 @@ commands: - /go/pkg/mod jobs: - test-llvm15-go118: + test-llvm15-go119: docker: - - image: golang:1.18-bullseye + - image: golang:1.19-bullseye steps: - test-linux: llvm: "15" @@ -118,6 +118,6 @@ workflows: jobs: # This tests our lowest supported versions of Go and LLVM, to make sure at # least the smoke tests still pass. - - test-llvm15-go118 + - test-llvm15-go119 # This tests LLVM 18 support when linking against system libraries. - test-llvm18-go122 diff --git a/BUILDING.md b/BUILDING.md index 52e411ec53..dfc069c0fd 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -18,7 +18,7 @@ tarball. If you want to help with development of TinyGo itself, you should follo LLVM, Clang and LLD are quite light on dependencies, requiring only standard build tools to be built. Go is of course necessary to build TinyGo itself. - * Go (1.18+) + * Go (1.19+) * GNU Make * Standard build tools (gcc/clang) * git diff --git a/builder/config.go b/builder/config.go index d6d1a38387..a82450a638 100644 --- a/builder/config.go +++ b/builder/config.go @@ -27,10 +27,10 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) { if err != nil { return nil, err } - if major != 1 || minor < 18 || minor > 22 { + if major != 1 || minor < 19 || minor > 22 { // 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.22, got go%d.%d", major, minor) + return nil, fmt.Errorf("requires go version 1.19 through 1.22, got go%d.%d", major, minor) } return &compileopts.Config{ diff --git a/compiler/interface.go b/compiler/interface.go index b6dffd6436..fc698c7a97 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -514,8 +514,7 @@ var basicTypeNames = [...]string{ func getTypeCodeName(t types.Type) (string, bool) { switch t := t.(type) { case *types.Named: - // Note: check for `t.Obj().Pkg() != nil` for Go 1.18 only. - if t.Obj().Pkg() != nil && t.Obj().Parent() != t.Obj().Pkg().Scope() { + if t.Obj().Parent() != t.Obj().Pkg().Scope() { return "named:" + t.String() + "$local", true } return "named:" + t.String(), false diff --git a/go.mod b/go.mod index 6d40cd6498..c034fa558f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/tinygo-org/tinygo -go 1.18 +go 1.19 require ( github.com/aykevl/go-wasm v0.0.2-0.20240312204833-50275154210c From 385a7920e60d5272a841c3351552876ea6cfe5f7 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 12 Jun 2024 12:41:42 +0200 Subject: [PATCH 069/444] compiler: fix race condition by applying a proposed patch This commit switches to v0.22.0 of golang.org/x/tools and then applies https://go-review.googlesource.com/c/tools/+/590815 to fix the race condition. In my testing, it seems to fix these issues. --- go.mod | 10 ++++++---- go.sum | 19 ++++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index c034fa558f..91d73591c2 100644 --- a/go.mod +++ b/go.mod @@ -17,9 +17,9 @@ require ( github.com/mgechev/revive v1.3.7 github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 go.bug.st/serial v1.6.0 - golang.org/x/net v0.20.0 - golang.org/x/sys v0.16.0 - golang.org/x/tools v0.17.0 + golang.org/x/net v0.26.0 + golang.org/x/sys v0.21.0 + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d gopkg.in/yaml.v2 v2.4.0 tinygo.org/x/go-llvm v0.0.0-20240518103902-697964f2a9dc ) @@ -43,5 +43,7 @@ require ( github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/spf13/afero v1.11.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/text v0.16.0 // indirect ) + +replace golang.org/x/tools => github.com/tinygo-org/tools v0.0.0-20240612102102-36af80766fc9 diff --git a/go.sum b/go.sum index 59d33c94b9..2a94d563b8 100644 --- a/go.sum +++ b/go.sum @@ -80,11 +80,14 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 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/tinygo-org/tools v0.0.0-20240612102102-36af80766fc9 h1:t8VQNFa68kSA8ze7gi8kSxF22a7/vmr1OtOPLvgO3+8= +github.com/tinygo-org/tools v0.0.0-20240612102102-36af80766fc9/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= go.bug.st/serial v1.6.0 h1:mAbRGN4cKE2J5gMwsMHC2KQisdLRQssO9WSM+rbZJ8A= go.bug.st/serial v1.6.0/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 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= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -92,12 +95,10 @@ golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= From e612f7cf3f9cea35acb2257c045a5513375b8c74 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 14 Jun 2024 12:22:51 +0200 Subject: [PATCH 070/444] Add smoke tests for machine package The machine package wasn't tested for every board. Therefore, add a new serial-like test that also tries to import the machine package. This should highlight potential issues in the future. --- GNUmakefile | 26 ++++++++++++------------- src/examples/machinetest/machinetest.go | 17 ++++++++++++++++ 2 files changed, 30 insertions(+), 13 deletions(-) create mode 100644 src/examples/machinetest/machinetest.go diff --git a/GNUmakefile b/GNUmakefile index ce833c656e..2cc70df7fa 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -491,7 +491,7 @@ smoketest: @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/rtcinterrupt @$(MD5SUM) test.hex - $(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial + $(TINYGO) build -size short -o test.hex -target=pca10040 examples/machinetest @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/systick @$(MD5SUM) test.hex @@ -523,7 +523,7 @@ ifneq ($(WASM), 0) @$(MD5SUM) test.wasm $(TINYGO) build -size short -o test.wasm -tags=circuitplay_bluefruit examples/blinky1 @$(MD5SUM) test.wasm - $(TINYGO) build -size short -o test.wasm -tags=mch2022 examples/serial + $(TINYGO) build -size short -o test.wasm -tags=mch2022 examples/machinetest @$(MD5SUM) test.wasm $(TINYGO) build -size short -o test.wasm -tags=gopher_badge examples/blinky1 @$(MD5SUM) test.wasm @@ -621,7 +621,7 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=itsybitsy-nrf52840 examples/blinky1 @$(MD5SUM) test.hex - $(TINYGO) build -size short -o test.hex -target=qtpy examples/serial + $(TINYGO) build -size short -o test.hex -target=qtpy examples/machinetest @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=teensy41 examples/blinky1 @$(MD5SUM) test.hex @@ -693,7 +693,7 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/usb-midi @$(MD5SUM) test.hex - $(TINYGO) build -size short -o test.hex -target=nrf52840-s140v6-uf2-generic examples/serial + $(TINYGO) build -size short -o test.hex -target=nrf52840-s140v6-uf2-generic examples/machinetest @$(MD5SUM) test.hex ifneq ($(STM32), 0) $(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1 @@ -735,7 +735,7 @@ ifneq ($(STM32), 0) endif $(TINYGO) build -size short -o test.hex -target=atmega328pb examples/blinkm @$(MD5SUM) test.hex - $(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial + $(TINYGO) build -size short -o test.hex -target=atmega1284p examples/machinetest @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1 @$(MD5SUM) test.hex @@ -762,22 +762,22 @@ ifneq ($(XTENSA), 0) @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1 @$(MD5SUM) test.bin - $(TINYGO) build -size short -o test.bin -target m5stack-core2 examples/serial + $(TINYGO) build -size short -o test.bin -target m5stack-core2 examples/machinetest @$(MD5SUM) test.bin - $(TINYGO) build -size short -o test.bin -target m5stack examples/serial + $(TINYGO) build -size short -o test.bin -target m5stack examples/machinetest @$(MD5SUM) test.bin - $(TINYGO) build -size short -o test.bin -target m5stick-c examples/serial + $(TINYGO) build -size short -o test.bin -target m5stick-c examples/machinetest @$(MD5SUM) test.bin - $(TINYGO) build -size short -o test.bin -target m5paper examples/serial + $(TINYGO) build -size short -o test.bin -target m5paper examples/machinetest @$(MD5SUM) test.bin - $(TINYGO) build -size short -o test.bin -target mch2022 examples/serial + $(TINYGO) build -size short -o test.bin -target mch2022 examples/machinetest @$(MD5SUM) test.bin endif - $(TINYGO) build -size short -o test.bin -target=qtpy-esp32c3 examples/serial + $(TINYGO) build -size short -o test.bin -target=qtpy-esp32c3 examples/machinetest @$(MD5SUM) test.bin - $(TINYGO) build -size short -o test.bin -target=m5stamp-c3 examples/serial + $(TINYGO) build -size short -o test.bin -target=m5stamp-c3 examples/machinetest @$(MD5SUM) test.bin - $(TINYGO) build -size short -o test.bin -target=xiao-esp32c3 examples/serial + $(TINYGO) build -size short -o test.bin -target=xiao-esp32c3 examples/machinetest @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1 @$(MD5SUM) test.hex diff --git a/src/examples/machinetest/machinetest.go b/src/examples/machinetest/machinetest.go new file mode 100644 index 0000000000..e79e38b18d --- /dev/null +++ b/src/examples/machinetest/machinetest.go @@ -0,0 +1,17 @@ +package main + +// This is the same as examples/serial, but it also imports the machine package. +// It is used as a smoke test for the machine package (for boards that don't +// have an on-board LED and therefore can't use examples/blinky1). + +import ( + _ "machine" // smoke test for the machine package + "time" +) + +func main() { + for { + println("hello world!") + time.Sleep(time.Second) + } +} From d513cae5d236cb367632b9cfa81a409b20eee065 Mon Sep 17 00:00:00 2001 From: leongross Date: Fri, 14 Jun 2024 14:31:50 +0200 Subject: [PATCH 071/444] add SetReadDeadline stub --- src/os/file.go | 7 +++++++ src/os/file_posix.go | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/src/os/file.go b/src/os/file.go index e003d47dab..acf33f850d 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -23,6 +23,7 @@ import ( "io/fs" "runtime" "syscall" + "time" ) // Seek whence values. @@ -256,6 +257,12 @@ func (f *File) SyscallConn() (conn syscall.RawConn, err error) { return } +// SetReadDeadline sets the deadline for future Read calls and any +// currently-blocked Read call. +func (f *File) SetReadDeadline(t time.Time) error { + return f.setReadDeadline(t) +} + // fd is an internal interface that is used to try a type assertion in order to // call the Fd() method of the underlying file handle if it is implemented. type fd interface { diff --git a/src/os/file_posix.go b/src/os/file_posix.go index b31453d645..e36a6b1cd7 100644 --- a/src/os/file_posix.go +++ b/src/os/file_posix.go @@ -8,3 +8,8 @@ import ( func Chtimes(name string, atime time.Time, mtime time.Time) error { return ErrNotImplemented } + +// setReadDeadline sets the read deadline, not yet implemented +func (f *File) setReadDeadline(_ time.Time) error { + return ErrNotImplemented +} From dd6fa89aa66a5113baa8883d4180ee090f35f784 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 14 Jun 2024 12:02:59 +0200 Subject: [PATCH 072/444] Release 0.32.0 --- CHANGELOG.md | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ goenv/version.go | 2 +- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 704e1dee9e..bb2de66477 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,61 @@ +0.32.0 +--- + +* **general** + - fix wasi-libc include headers on Nix + - apply OpenOCD commands after target configuration + - fix a minor race condition when determining the build tags + - support UF2 drives with a space in their name on Linux + - add LLVM 18 support + - drop support for Go 1.18 to be able to stay up to date + +* **compiler** + - move `-panic=trap` support to the compiler/runtime + - fix symbol table index for WebAssembly archives + - fix ed25519 build errors by adjusting the alias names + - add aliases to generic AES functions + - fix race condition by temporarily applying a proposed patch + - `builder`: keep un-wasm-opt'd .wasm if -work was passed + - `builder`: make sure wasm-opt command line is printed if asked + - `cgo`: implement shift operations in preprocessor macros + - `interp`: checking for methodset existance + +* **standard library** + - `machine`: add `__tinygo_spi_tx` function to simulator + - `machine`: fix simulator I2C support + - `machine`: add GetRNG support to simulator + - `machine`: add `TxFifoFreeLevel` for CAN + - `os`: add `Link` + - `os`: add `FindProcess` for posix + - `os`: add `Process.Release` for unix + - `os`: add `SetReadDeadline` stub + - `os`, `os/signal`: add signal stubs + - `os/user`: add stubs for `Lookup{,Group}` and `Group` + - `reflect`: use int in `StringHeader` and `SliceHeader` on non-AVR platforms + - `reflect`: fix `NumMethods` for Interface type + - `runtime`: skip negative sleep durations in sleepTicks + +* **targets** + - `esp32`: add I2C support + - `rp2040`: move UART0 and UART1 to common file + - `rp2040`: make all RP2040 boards available for simulation + - `rp2040`: fix timeUnit type + - `stm32`: add i2c `Frequency` and `SetBaudRate` function for chips that were missing implementation + - `wasm-unknown`: add math and memory builtins that LLVM needs + - `wasip1`: replace existing `-target=wasi` support with wasip1 as supported in Go 1.21+ + +* **boards** + - `adafruit-esp32-feather-v2`: add the Adafruit ESP32 Feather V2 + - `badger2040-w`: add support for the Badger2040 W + - `feather-nrf52840-sense`: fix lack of LXFO + - `m5paper`: add support for the M5 Paper + - `mksnanov3`: limit programming speed to 1800 kHz + - `nucleol476rg`: add stm32 nucleol476rg support + - `pico-w`: add the Pico W (which is near-idential to the pico target) + - `thingplus-rp2040`, `waveshare-rp2040-zero`: add WS2812 definition + - `pca10059-s140v7`: add this variant to the PCA10059 board + + 0.31.2 --- diff --git a/goenv/version.go b/goenv/version.go index f00b4b95d2..85ca472a59 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -9,7 +9,7 @@ import ( // Version of TinyGo. // Update this value before release of new version of software. -const version = "0.32.0-dev" +const version = "0.32.0" var ( // This variable is set at build time using -ldflags parameters. From d33ace7b59b41a358bb40366f086e135db56aaac Mon Sep 17 00:00:00 2001 From: leongross Date: Fri, 21 Jun 2024 13:40:02 +0200 Subject: [PATCH 073/444] remove unused registers for x86_64 linux syscalls Signed-off-by: leongross --- compiler/syscall.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/compiler/syscall.go b/compiler/syscall.go index 2623ea94cf..bf3955df2d 100644 --- a/compiler/syscall.go +++ b/compiler/syscall.go @@ -33,18 +33,17 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) { "{r10}", "{r8}", "{r9}", - "{r11}", - "{r12}", - "{r13}", }[i] llvmValue := b.getValue(arg, getPos(call)) args = append(args, llvmValue) argTypes = append(argTypes, llvmValue.Type()) } + // rcx and r11 are clobbered by the syscall, so make sure they are not used constraints += ",~{rcx},~{r11}" fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel, false) return b.CreateCall(fnType, target, args, ""), nil + case b.GOARCH == "386" && b.GOOS == "linux": // Sources: // syscall(2) man page @@ -71,6 +70,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) { fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel, false) return b.CreateCall(fnType, target, args, ""), nil + case b.GOARCH == "arm" && b.GOOS == "linux": // Implement the EABI system call convention for Linux. // Source: syscall(2) man page. @@ -103,6 +103,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) { fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false) return b.CreateCall(fnType, target, args, ""), nil + case b.GOARCH == "arm64" && b.GOOS == "linux": // Source: syscall(2) man page. args := []llvm.Value{} @@ -135,6 +136,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) { fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false) return b.CreateCall(fnType, target, args, ""), nil + default: return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH) } @@ -217,6 +219,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) { retval = b.CreateInsertValue(retval, syscallResult, 0, "") retval = b.CreateInsertValue(retval, errResult, 2, "") return retval, nil + default: return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH) } From 868262f4a7f24753e5b8dab28cae59330506bedf Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 21 Jun 2024 19:25:17 +0200 Subject: [PATCH 074/444] all: use latest version of x/tools We previously picked a work-in-progress patch, but this is the proper fix for this race condition. I think we should use that instead of relying on the previous work-in-progress patch. --- go.mod | 4 +--- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 91d73591c2..d17f62e3f7 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( go.bug.st/serial v1.6.0 golang.org/x/net v0.26.0 golang.org/x/sys v0.21.0 - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d + golang.org/x/tools v0.22.1-0.20240621165957-db513b091504 gopkg.in/yaml.v2 v2.4.0 tinygo.org/x/go-llvm v0.0.0-20240518103902-697964f2a9dc ) @@ -45,5 +45,3 @@ require ( github.com/spf13/afero v1.11.0 // indirect golang.org/x/text v0.16.0 // indirect ) - -replace golang.org/x/tools => github.com/tinygo-org/tools v0.0.0-20240612102102-36af80766fc9 diff --git a/go.sum b/go.sum index 2a94d563b8..c093ee07ca 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 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/tinygo-org/tools v0.0.0-20240612102102-36af80766fc9 h1:t8VQNFa68kSA8ze7gi8kSxF22a7/vmr1OtOPLvgO3+8= -github.com/tinygo-org/tools v0.0.0-20240612102102-36af80766fc9/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= go.bug.st/serial v1.6.0 h1:mAbRGN4cKE2J5gMwsMHC2KQisdLRQssO9WSM+rbZJ8A= go.bug.st/serial v1.6.0/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= @@ -99,6 +97,8 @@ golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/tools v0.22.1-0.20240621165957-db513b091504 h1:MMsD8mMfluf/578+3wrTn22pjI/Xkzm+gPW47SYfspY= +golang.org/x/tools v0.22.1-0.20240621165957-db513b091504/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= From 1270a501044c97a4a2aa057f4fa87ef679478953 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 24 Jun 2024 17:26:58 +0200 Subject: [PATCH 075/444] machine: use new internal/binary package The encoding/binary package in Go 1.23 imports the slices package, which results in circular import when imported from the machine package. Therefore, encoding/binary cannot be used in the machine package. This commit fixes that by introducing a new internal/binary package that is just plain Go code without dependencies. It can be safely used anywhere (including the runtime if needed). --- loader/goroot.go | 1 + src/internal/binary/binary.go | 32 +++++++++++++++++++++ src/machine/machine_atsamd21.go | 2 +- src/machine/machine_atsamd51.go | 2 +- src/machine/machine_nrf.go | 2 +- src/machine/machine_stm32f4.go | 2 +- src/machine/machine_stm32l4.go | 2 +- src/machine/machine_stm32wlx.go | 2 +- src/machine/usb/descriptor/configuration.go | 2 +- src/machine/usb/descriptor/device.go | 2 +- src/machine/usb/descriptor/endpoint.go | 2 +- src/machine/usb/descriptor/hid.go | 2 +- 12 files changed, 43 insertions(+), 10 deletions(-) create mode 100644 src/internal/binary/binary.go diff --git a/loader/goroot.go b/loader/goroot.go index c7341fc7fd..7c8d97fa46 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -236,6 +236,7 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { "device/": false, "examples/": false, "internal/": true, + "internal/binary/": false, "internal/bytealg/": false, "internal/fuzz/": false, "internal/reflectlite/": false, diff --git a/src/internal/binary/binary.go b/src/internal/binary/binary.go new file mode 100644 index 0000000000..f3dbf6e9b5 --- /dev/null +++ b/src/internal/binary/binary.go @@ -0,0 +1,32 @@ +// Package binary is a lightweight replacement package for encoding/binary. +package binary + +// This file contains small helper functions for working with binary data. + +var LittleEndian = littleEndian{} + +type littleEndian struct{} + +// Encode data like encoding/binary.LittleEndian.Uint16. +func (littleEndian) Uint16(b []byte) uint16 { + return uint16(b[0]) | uint16(b[1])<<8 +} + +// Store data like binary.LittleEndian.PutUint16. +func (littleEndian) PutUint16(b []byte, v uint16) { + b[0] = byte(v) + b[1] = byte(v >> 8) +} + +// Append data like binary.LittleEndian.AppendUint16. +func (littleEndian) AppendUint16(b []byte, v uint16) []byte { + return append(b, + byte(v), + byte(v>>8), + ) +} + +// Encode data like encoding/binary.LittleEndian.Uint32. +func (littleEndian) Uint32(b []byte) uint32 { + return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 +} diff --git a/src/machine/machine_atsamd21.go b/src/machine/machine_atsamd21.go index 755bedf9a2..fe67f45a32 100644 --- a/src/machine/machine_atsamd21.go +++ b/src/machine/machine_atsamd21.go @@ -10,8 +10,8 @@ import ( "bytes" "device/arm" "device/sam" - "encoding/binary" "errors" + "internal/binary" "runtime/interrupt" "unsafe" ) diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go index fdc368a63d..bcaaec7212 100644 --- a/src/machine/machine_atsamd51.go +++ b/src/machine/machine_atsamd51.go @@ -10,8 +10,8 @@ import ( "bytes" "device/arm" "device/sam" - "encoding/binary" "errors" + "internal/binary" "runtime/interrupt" "unsafe" ) diff --git a/src/machine/machine_nrf.go b/src/machine/machine_nrf.go index 99c49d3cab..9457007115 100644 --- a/src/machine/machine_nrf.go +++ b/src/machine/machine_nrf.go @@ -5,7 +5,7 @@ package machine import ( "bytes" "device/nrf" - "encoding/binary" + "internal/binary" "runtime/interrupt" "unsafe" ) diff --git a/src/machine/machine_stm32f4.go b/src/machine/machine_stm32f4.go index 42193a7397..41e1b2204f 100644 --- a/src/machine/machine_stm32f4.go +++ b/src/machine/machine_stm32f4.go @@ -6,8 +6,8 @@ package machine import ( "device/stm32" - "encoding/binary" "errors" + "internal/binary" "math/bits" "runtime/interrupt" "runtime/volatile" diff --git a/src/machine/machine_stm32l4.go b/src/machine/machine_stm32l4.go index 856320911b..4eb3588f7b 100644 --- a/src/machine/machine_stm32l4.go +++ b/src/machine/machine_stm32l4.go @@ -4,8 +4,8 @@ package machine import ( "device/stm32" - "encoding/binary" "errors" + "internal/binary" "runtime/interrupt" "runtime/volatile" "unsafe" diff --git a/src/machine/machine_stm32wlx.go b/src/machine/machine_stm32wlx.go index b27c779feb..84e302a101 100644 --- a/src/machine/machine_stm32wlx.go +++ b/src/machine/machine_stm32wlx.go @@ -6,8 +6,8 @@ package machine import ( "device/stm32" - "encoding/binary" "errors" + "internal/binary" "math/bits" "runtime/interrupt" "runtime/volatile" diff --git a/src/machine/usb/descriptor/configuration.go b/src/machine/usb/descriptor/configuration.go index d9446e6738..efb9ab1d2c 100644 --- a/src/machine/usb/descriptor/configuration.go +++ b/src/machine/usb/descriptor/configuration.go @@ -1,7 +1,7 @@ package descriptor import ( - "encoding/binary" + "internal/binary" ) const ( diff --git a/src/machine/usb/descriptor/device.go b/src/machine/usb/descriptor/device.go index 48229fbfd3..0c3ee92f9a 100644 --- a/src/machine/usb/descriptor/device.go +++ b/src/machine/usb/descriptor/device.go @@ -1,7 +1,7 @@ package descriptor import ( - "encoding/binary" + "internal/binary" ) const ( diff --git a/src/machine/usb/descriptor/endpoint.go b/src/machine/usb/descriptor/endpoint.go index affaffa0ad..c7fa011fad 100644 --- a/src/machine/usb/descriptor/endpoint.go +++ b/src/machine/usb/descriptor/endpoint.go @@ -1,7 +1,7 @@ package descriptor import ( - "encoding/binary" + "internal/binary" ) var endpointEP1IN = [endpointTypeLen]byte{ diff --git a/src/machine/usb/descriptor/hid.go b/src/machine/usb/descriptor/hid.go index cdd4fc7e57..d8f86a41e7 100644 --- a/src/machine/usb/descriptor/hid.go +++ b/src/machine/usb/descriptor/hid.go @@ -2,8 +2,8 @@ package descriptor import ( "bytes" - "encoding/binary" "errors" + "internal/binary" ) var configurationCDCHID = [configurationTypeLen]byte{ From 6abaee464031bfe41a9421c6e5f6563903abbafb Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 22 Jun 2024 17:13:04 +0200 Subject: [PATCH 076/444] compiler: remove old atomics workaround for AVR The bug should have been fixed in any LLVM version that we currently support. --- compiler/atomic.go | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/compiler/atomic.go b/compiler/atomic.go index 006da5ef8b..4ba69c3960 100644 --- a/compiler/atomic.go +++ b/compiler/atomic.go @@ -1,9 +1,6 @@ package compiler import ( - "fmt" - "strings" - "tinygo.org/x/go-llvm" ) @@ -15,20 +12,6 @@ func (b *builder) createAtomicOp(name string) llvm.Value { case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr": ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) val := b.getValue(b.fn.Params[1], getPos(b.fn)) - if strings.HasPrefix(b.Triple, "avr") { - // AtomicRMW does not work on AVR as intended: - // - There are some register allocation issues (fixed by https://reviews.llvm.org/D97127 which is not yet in a usable LLVM release) - // - The result is the new value instead of the old value - vType := val.Type() - name := fmt.Sprintf("__sync_fetch_and_add_%d", vType.IntTypeWidth()/8) - fn := b.mod.NamedFunction(name) - if fn.IsNil() { - fn = llvm.AddFunction(b.mod, name, llvm.FunctionType(vType, []llvm.Type{ptr.Type(), vType}, false)) - } - oldVal := b.createCall(fn.GlobalValueType(), fn, []llvm.Value{ptr, val}, "") - // Return the new value, not the original value returned. - return b.CreateAdd(oldVal, val, "") - } oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpAdd, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true) // Return the new value, not the original value returned by atomicrmw. return b.CreateAdd(oldVal, val, "") From 4517a0c17b207957f8c5506032c396f5f34950ba Mon Sep 17 00:00:00 2001 From: deadprogram Date: Mon, 24 Jun 2024 21:31:31 +0200 Subject: [PATCH 077/444] version: update to 0.33.0-dev Signed-off-by: deadprogram --- goenv/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/goenv/version.go b/goenv/version.go index 85ca472a59..bed4e26f34 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -9,7 +9,7 @@ import ( // Version of TinyGo. // Update this value before release of new version of software. -const version = "0.32.0" +const version = "0.33.0-dev" var ( // This variable is set at build time using -ldflags parameters. From fae0402fdeba8a7d3a2e9b97019aeef7e3fae930 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 27 Jun 2024 18:27:28 +0200 Subject: [PATCH 078/444] wasm-unknown: make sure the os package can be imported See: https://github.com/tinygo-org/tinygo/issues/4314 The os package isn't particularly useful on wasm-unknown, but just like on baremetal systems it's imported by a lot of packages so it should at least be possible to import this package. --- src/examples/hello-wasm-unknown/main.go | 4 ++++ src/os/dir_other.go | 2 +- src/os/dir_unix.go | 2 +- src/os/dirent_linux.go | 2 +- src/os/file_anyos.go | 2 +- src/os/file_other.go | 2 +- src/os/file_unix.go | 2 +- src/os/removeall_noat.go | 2 +- src/os/removeall_other.go | 2 +- src/os/stat_linuxlike.go | 2 +- src/os/stat_other.go | 2 +- src/os/stat_unix.go | 2 +- src/os/types_anyos.go | 2 +- src/os/types_unix.go | 2 +- src/runtime/nonhosted.go | 2 +- src/runtime/runtime_wasm_unknown.go | 2 -- 16 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/examples/hello-wasm-unknown/main.go b/src/examples/hello-wasm-unknown/main.go index 557f4a3c36..ff2ec88f18 100644 --- a/src/examples/hello-wasm-unknown/main.go +++ b/src/examples/hello-wasm-unknown/main.go @@ -3,6 +3,10 @@ // tinygo build -size short -o hello-unknown.wasm -target wasm-unknown -gc=leaking -no-debug ./src/examples/hello-wasm-unknown/ package main +// Smoke test: make sure the fmt package can be imported (even if it isn't +// really useful for wasm-unknown). +import _ "os" + var x int32 //go:wasmimport hosted echo_i32 diff --git a/src/os/dir_other.go b/src/os/dir_other.go index 9a1b394213..60cd9f8e6a 100644 --- a/src/os/dir_other.go +++ b/src/os/dir_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || js || windows +//go:build baremetal || js || windows || wasm_unknown // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/dir_unix.go b/src/os/dir_unix.go index a531e0a639..227dc9188d 100644 --- a/src/os/dir_unix.go +++ b/src/os/dir_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build linux && !baremetal && !wasip1 +//go:build linux && !baremetal && !wasip1 && !wasm_unknown package os diff --git a/src/os/dirent_linux.go b/src/os/dirent_linux.go index 90f7086db8..790c26890f 100644 --- a/src/os/dirent_linux.go +++ b/src/os/dirent_linux.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !js && !wasip1 +//go:build !baremetal && !js && !wasip1 && !wasm_unknown // Copyright 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/file_anyos.go b/src/os/file_anyos.go index 0436d1222e..1483f11c2a 100644 --- a/src/os/file_anyos.go +++ b/src/os/file_anyos.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !js +//go:build !baremetal && !js && !wasm_unknown // Portions copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/file_other.go b/src/os/file_other.go index e7fabddcaf..82552c1a8c 100644 --- a/src/os/file_other.go +++ b/src/os/file_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || (wasm && !wasip1) +//go:build baremetal || (tinygo.wasm && !wasip1) package os diff --git a/src/os/file_unix.go b/src/os/file_unix.go index 25f0266a67..25c2161e3e 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal) || wasip1 +//go:build darwin || (linux && !baremetal && !wasm_unknown) || wasip1 // target wasi sets GOOS=linux and thus the +linux build tag, // even though it doesn't show up in "tinygo info target -wasi" diff --git a/src/os/removeall_noat.go b/src/os/removeall_noat.go index 8b03756e31..d49162768c 100644 --- a/src/os/removeall_noat.go +++ b/src/os/removeall_noat.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !baremetal && !js && !wasip1 +//go:build !baremetal && !js && !wasip1 && !wasm_unknown package os diff --git a/src/os/removeall_other.go b/src/os/removeall_other.go index ec055a9875..75346bc838 100644 --- a/src/os/removeall_other.go +++ b/src/os/removeall_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || js || wasi || wasip1 +//go:build baremetal || js || wasi || wasip1 || wasm_unknown // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/stat_linuxlike.go b/src/os/stat_linuxlike.go index f2ff8a5f61..59ee649707 100644 --- a/src/os/stat_linuxlike.go +++ b/src/os/stat_linuxlike.go @@ -1,4 +1,4 @@ -//go:build (linux && !baremetal) || wasip1 +//go:build (linux && !baremetal && !wasm_unknown) || wasip1 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/stat_other.go b/src/os/stat_other.go index 7340fbb574..d3e0af6ed5 100644 --- a/src/os/stat_other.go +++ b/src/os/stat_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || (wasm && !wasip1) +//go:build baremetal || (tinygo.wasm && !wasip1) // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/stat_unix.go b/src/os/stat_unix.go index 54b2bb4857..a3aa491f36 100644 --- a/src/os/stat_unix.go +++ b/src/os/stat_unix.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal) || wasip1 +//go:build darwin || (linux && !baremetal && !wasm_unknown) || wasip1 // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/types_anyos.go b/src/os/types_anyos.go index cb3a0b9491..939c00967c 100644 --- a/src/os/types_anyos.go +++ b/src/os/types_anyos.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !js +//go:build !baremetal && !js && !wasm_unknown // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/types_unix.go b/src/os/types_unix.go index 943fc00f52..3a552dfd94 100644 --- a/src/os/types_unix.go +++ b/src/os/types_unix.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal) || wasip1 +//go:build darwin || (linux && !baremetal && !wasm_unknown) || wasip1 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/runtime/nonhosted.go b/src/runtime/nonhosted.go index 6b47ba8b0c..ca5ab4c3c8 100644 --- a/src/runtime/nonhosted.go +++ b/src/runtime/nonhosted.go @@ -1,4 +1,4 @@ -//go:build baremetal || js +//go:build baremetal || js || wasm_unknown package runtime diff --git a/src/runtime/runtime_wasm_unknown.go b/src/runtime/runtime_wasm_unknown.go index 111bddf085..d307a4f87e 100644 --- a/src/runtime/runtime_wasm_unknown.go +++ b/src/runtime/runtime_wasm_unknown.go @@ -23,8 +23,6 @@ func init() { __wasm_call_ctors() } -var args []string - func ticksToNanoseconds(ticks timeUnit) int64 { return int64(ticks) } From 36958b2875eb777972e3813b347a9ab308b0fc14 Mon Sep 17 00:00:00 2001 From: leongross Date: Tue, 25 Jun 2024 09:52:05 +0200 Subject: [PATCH 079/444] add support for unix.Syscall* invocations Signed-off-by: leongross --- compiler/compiler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index 0ae32cc94c..966c567591 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1846,7 +1846,7 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) return b.emitSV64Call(instr.Args, getPos(instr)) case strings.HasPrefix(name, "(device/riscv.CSR)."): return b.emitCSROperation(instr) - case strings.HasPrefix(name, "syscall.Syscall") || strings.HasPrefix(name, "syscall.RawSyscall"): + case strings.HasPrefix(name, "syscall.Syscall") || strings.HasPrefix(name, "syscall.RawSyscall") || strings.HasPrefix(name, "Syscall") || strings.HasPrefix(name, "golang.org/x/sys/unix.Syscall"): return b.createSyscall(instr) case strings.HasPrefix(name, "syscall.rawSyscallNoError"): return b.createRawSyscallNoError(instr) From 2d5a8d407bacad4f5c5c67378e83e38df271199f Mon Sep 17 00:00:00 2001 From: leongross Date: Thu, 27 Jun 2024 17:20:54 +0200 Subject: [PATCH 080/444] add support for unix.{RawSyscall,RawSyscallNoError} --- compiler/compiler.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index 966c567591..004dfcaa31 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1846,9 +1846,9 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) return b.emitSV64Call(instr.Args, getPos(instr)) case strings.HasPrefix(name, "(device/riscv.CSR)."): return b.emitCSROperation(instr) - case strings.HasPrefix(name, "syscall.Syscall") || strings.HasPrefix(name, "syscall.RawSyscall") || strings.HasPrefix(name, "Syscall") || strings.HasPrefix(name, "golang.org/x/sys/unix.Syscall"): + case strings.HasPrefix(name, "syscall.Syscall") || strings.HasPrefix(name, "syscall.RawSyscall") || strings.HasPrefix(name, "golang.org/x/sys/unix.Syscall") || strings.HasPrefix(name, "golang.org/x/sys/unix.RawSyscall"): return b.createSyscall(instr) - case strings.HasPrefix(name, "syscall.rawSyscallNoError"): + case strings.HasPrefix(name, "syscall.rawSyscallNoError") || strings.HasPrefix(name, "golang.org/x/sys/unix.RawSyscallNoError"): return b.createRawSyscallNoError(instr) case name == "runtime.supportsRecover": supportsRecover := uint64(0) From f18c6e342f834988caf43fd652b2baae9c3093f0 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 27 Jun 2024 19:31:35 +0200 Subject: [PATCH 081/444] test: support GOOS/GOARCH pairs in the -target flag This means it's possible to test just a particular OS/arch with a command like this: go test -run=Build -target=linux/arm I found it useful while working on MIPS support. --- main_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/main_test.go b/main_test.go index 0f0d52f4d6..1ea16cd143 100644 --- a/main_test.go +++ b/main_test.go @@ -297,6 +297,10 @@ func emuCheck(t *testing.T, options compileopts.Options) { } func optionsFromTarget(target string, sema chan struct{}) compileopts.Options { + separators := strings.Count(target, "/") + if (separators == 1 || separators == 2) && !strings.HasSuffix(target, ".json") { + return optionsFromOSARCH(target, sema) + } return compileopts.Options{ // GOOS/GOARCH are only used if target == "" GOOS: goenv.Get("GOOS"), From 9cb263479c4b98f2d28889ca1acc297454e0d875 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Tue, 2 Jul 2024 07:02:03 -0700 Subject: [PATCH 082/444] wasi preview 2 support (#4027) * all: wasip2 support Co-authored-by: Randy Reddig --- .github/workflows/linux.yml | 24 +- .github/workflows/windows.yml | 4 +- .gitignore | 5 +- .gitmodules | 6 + GNUmakefile | 38 +- builder/build.go | 62 +- builder/builder_test.go | 2 + compileopts/options.go | 2 + compileopts/target.go | 2 + compiler/symbol.go | 48 +- compiler/testdata/errors.go | 60 +- goenv/goenv.go | 5 + lib/wasi-cli | 1 + loader/goroot.go | 3 + main.go | 70 +- main_test.go | 12 + src/crypto/rand/rand_arc4random.go | 2 +- src/crypto/rand/rand_urandom.go | 2 +- .../wasi/cli/v0.2.0/command/command.wit.go | 6 + .../wasi/cli/v0.2.0/environment/empty.s | 3 + .../cli/v0.2.0/environment/environment.wit.go | 66 + src/internal/wasi/cli/v0.2.0/exit/empty.s | 3 + src/internal/wasi/cli/v0.2.0/exit/exit.wit.go | 27 + src/internal/wasi/cli/v0.2.0/run/empty.s | 3 + .../wasi/cli/v0.2.0/run/run.exports.go | 19 + src/internal/wasi/cli/v0.2.0/run/run.wit.go | 18 + src/internal/wasi/cli/v0.2.0/stderr/empty.s | 3 + .../wasi/cli/v0.2.0/stderr/stderr.wit.go | 26 + src/internal/wasi/cli/v0.2.0/stdin/empty.s | 3 + .../wasi/cli/v0.2.0/stdin/stdin.wit.go | 26 + src/internal/wasi/cli/v0.2.0/stdout/empty.s | 3 + .../wasi/cli/v0.2.0/stdout/stdout.wit.go | 26 + .../wasi/cli/v0.2.0/terminal-input/empty.s | 3 + .../terminal-input/terminal-input.wit.go | 38 + .../wasi/cli/v0.2.0/terminal-output/empty.s | 3 + .../terminal-output/terminal-output.wit.go | 38 + .../wasi/cli/v0.2.0/terminal-stderr/empty.s | 3 + .../terminal-stderr/terminal-stderr.wit.go | 31 + .../wasi/cli/v0.2.0/terminal-stdin/empty.s | 3 + .../terminal-stdin/terminal-stdin.wit.go | 31 + .../wasi/cli/v0.2.0/terminal-stdout/empty.s | 3 + .../terminal-stdout/terminal-stdout.wit.go | 31 + .../clocks/v0.2.0/monotonic-clock/empty.s | 3 + .../monotonic-clock/monotonic-clock.wit.go | 115 ++ .../wasi/clocks/v0.2.0/wall-clock/empty.s | 3 + .../v0.2.0/wall-clock/wall-clock.wit.go | 80 + .../wasi/filesystem/v0.2.0/preopens/empty.s | 3 + .../v0.2.0/preopens/preopens.wit.go | 27 + .../wasi/filesystem/v0.2.0/types/abi.go | 27 + .../wasi/filesystem/v0.2.0/types/empty.s | 3 + .../wasi/filesystem/v0.2.0/types/types.wit.go | 1284 ++++++++++++++++ src/internal/wasi/io/v0.2.0/error/empty.s | 3 + .../wasi/io/v0.2.0/error/error.wit.go | 73 + src/internal/wasi/io/v0.2.0/poll/empty.s | 3 + src/internal/wasi/io/v0.2.0/poll/poll.wit.go | 110 ++ src/internal/wasi/io/v0.2.0/streams/empty.s | 3 + .../wasi/io/v0.2.0/streams/streams.wit.go | 521 +++++++ .../wasi/random/v0.2.0/insecure-seed/empty.s | 3 + .../v0.2.0/insecure-seed/insecure-seed.wit.go | 43 + .../wasi/random/v0.2.0/insecure/empty.s | 3 + .../random/v0.2.0/insecure/insecure.wit.go | 59 + .../wasi/random/v0.2.0/random/empty.s | 3 + .../wasi/random/v0.2.0/random/random.wit.go | 63 + .../sockets/v0.2.0/instance-network/empty.s | 3 + .../instance-network/instance-network.wit.go | 30 + .../sockets/v0.2.0/ip-name-lookup/empty.s | 3 + .../ip-name-lookup/ip-name-lookup.wit.go | 123 ++ .../wasi/sockets/v0.2.0/network/empty.s | 3 + .../sockets/v0.2.0/network/network.wit.go | 278 ++++ .../sockets/v0.2.0/tcp-create-socket/empty.s | 3 + .../tcp-create-socket.wit.go | 54 + src/internal/wasi/sockets/v0.2.0/tcp/abi.go | 71 + src/internal/wasi/sockets/v0.2.0/tcp/empty.s | 3 + .../wasi/sockets/v0.2.0/tcp/tcp.wit.go | 839 ++++++++++ .../sockets/v0.2.0/udp-create-socket/empty.s | 3 + .../udp-create-socket.wit.go | 54 + src/internal/wasi/sockets/v0.2.0/udp/abi.go | 92 ++ src/internal/wasi/sockets/v0.2.0/udp/empty.s | 3 + .../wasi/sockets/v0.2.0/udp/udp.wit.go | 643 ++++++++ src/os/dir_test.go | 2 +- src/os/dir_unix.go | 2 +- src/os/{dir_wasip1.go => dir_wasi.go} | 2 +- src/os/dirent_linux.go | 2 +- src/os/env_unix_test.go | 2 +- src/os/exec_posix.go | 2 +- src/os/file_other.go | 2 +- src/os/file_unix.go | 2 +- src/os/getpagesize_test.go | 2 +- src/os/os_anyos_test.go | 6 +- src/os/os_chmod_test.go | 2 +- src/os/os_hardlink_test.go | 2 +- src/os/os_symlink_test.go | 2 +- src/os/pipe_test.go | 2 +- src/os/read_test.go | 2 +- src/os/removeall_noat.go | 2 +- src/os/removeall_other.go | 2 +- src/os/removeall_test.go | 2 +- src/os/seek_unix_bad.go | 2 +- src/os/stat_linuxlike.go | 2 +- src/os/stat_other.go | 2 +- src/os/stat_unix.go | 2 +- src/os/tempfile_test.go | 2 +- src/os/types_unix.go | 2 +- src/runtime/os_linux.go | 2 +- src/runtime/os_other.go | 2 +- src/runtime/os_wasip2.go | 5 + src/runtime/runtime_tinygowasm.go | 2 +- src/runtime/runtime_tinygowasmp2.go | 76 + src/runtime/runtime_unix.go | 2 +- src/runtime/runtime_wasm_js_scheduler.go | 2 +- src/runtime/runtime_wasm_wasip2.go | 61 + src/syscall/env_libc.go | 59 + src/syscall/env_wasip2.go | 11 + src/syscall/errno_other.go | 2 +- src/syscall/errno_wasip1.go | 8 + src/syscall/errno_wasip2.go | 7 + src/syscall/file_emulated.go | 2 +- src/syscall/file_hosted.go | 2 +- src/syscall/libc_wasip2.go | 1356 +++++++++++++++++ src/syscall/syscall_libc.go | 54 +- ...ll_libc_wasip1.go => syscall_libc_wasi.go} | 26 +- src/testing/testing_test.go | 4 +- src/vendor/github.com/ydnar/wasm-tools-go | 1 + targets/wasip2.json | 28 + tools/tgtestjson.sh | 22 + 125 files changed, 7034 insertions(+), 153 deletions(-) create mode 160000 lib/wasi-cli create mode 100644 src/internal/wasi/cli/v0.2.0/command/command.wit.go create mode 100644 src/internal/wasi/cli/v0.2.0/environment/empty.s create mode 100644 src/internal/wasi/cli/v0.2.0/environment/environment.wit.go create mode 100644 src/internal/wasi/cli/v0.2.0/exit/empty.s create mode 100644 src/internal/wasi/cli/v0.2.0/exit/exit.wit.go create mode 100644 src/internal/wasi/cli/v0.2.0/run/empty.s create mode 100644 src/internal/wasi/cli/v0.2.0/run/run.exports.go create mode 100644 src/internal/wasi/cli/v0.2.0/run/run.wit.go create mode 100644 src/internal/wasi/cli/v0.2.0/stderr/empty.s create mode 100644 src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go create mode 100644 src/internal/wasi/cli/v0.2.0/stdin/empty.s create mode 100644 src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go create mode 100644 src/internal/wasi/cli/v0.2.0/stdout/empty.s create mode 100644 src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go create mode 100644 src/internal/wasi/cli/v0.2.0/terminal-input/empty.s create mode 100644 src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go create mode 100644 src/internal/wasi/cli/v0.2.0/terminal-output/empty.s create mode 100644 src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go create mode 100644 src/internal/wasi/cli/v0.2.0/terminal-stderr/empty.s create mode 100644 src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go create mode 100644 src/internal/wasi/cli/v0.2.0/terminal-stdin/empty.s create mode 100644 src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go create mode 100644 src/internal/wasi/cli/v0.2.0/terminal-stdout/empty.s create mode 100644 src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go create mode 100644 src/internal/wasi/clocks/v0.2.0/monotonic-clock/empty.s create mode 100644 src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go create mode 100644 src/internal/wasi/clocks/v0.2.0/wall-clock/empty.s create mode 100644 src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go create mode 100644 src/internal/wasi/filesystem/v0.2.0/preopens/empty.s create mode 100644 src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go create mode 100644 src/internal/wasi/filesystem/v0.2.0/types/abi.go create mode 100644 src/internal/wasi/filesystem/v0.2.0/types/empty.s create mode 100644 src/internal/wasi/filesystem/v0.2.0/types/types.wit.go create mode 100644 src/internal/wasi/io/v0.2.0/error/empty.s create mode 100644 src/internal/wasi/io/v0.2.0/error/error.wit.go create mode 100644 src/internal/wasi/io/v0.2.0/poll/empty.s create mode 100644 src/internal/wasi/io/v0.2.0/poll/poll.wit.go create mode 100644 src/internal/wasi/io/v0.2.0/streams/empty.s create mode 100644 src/internal/wasi/io/v0.2.0/streams/streams.wit.go create mode 100644 src/internal/wasi/random/v0.2.0/insecure-seed/empty.s create mode 100644 src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go create mode 100644 src/internal/wasi/random/v0.2.0/insecure/empty.s create mode 100644 src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go create mode 100644 src/internal/wasi/random/v0.2.0/random/empty.s create mode 100644 src/internal/wasi/random/v0.2.0/random/random.wit.go create mode 100644 src/internal/wasi/sockets/v0.2.0/instance-network/empty.s create mode 100644 src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go create mode 100644 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/empty.s create mode 100644 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go create mode 100644 src/internal/wasi/sockets/v0.2.0/network/empty.s create mode 100644 src/internal/wasi/sockets/v0.2.0/network/network.wit.go create mode 100644 src/internal/wasi/sockets/v0.2.0/tcp-create-socket/empty.s create mode 100644 src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go create mode 100644 src/internal/wasi/sockets/v0.2.0/tcp/abi.go create mode 100644 src/internal/wasi/sockets/v0.2.0/tcp/empty.s create mode 100644 src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go create mode 100644 src/internal/wasi/sockets/v0.2.0/udp-create-socket/empty.s create mode 100644 src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go create mode 100644 src/internal/wasi/sockets/v0.2.0/udp/abi.go create mode 100644 src/internal/wasi/sockets/v0.2.0/udp/empty.s create mode 100644 src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go rename src/os/{dir_wasip1.go => dir_wasi.go} (98%) create mode 100644 src/runtime/os_wasip2.go create mode 100644 src/runtime/runtime_tinygowasmp2.go create mode 100644 src/runtime/runtime_wasm_wasip2.go create mode 100644 src/syscall/env_libc.go create mode 100644 src/syscall/env_wasip2.go create mode 100644 src/syscall/errno_wasip1.go create mode 100644 src/syscall/errno_wasip2.go create mode 100644 src/syscall/libc_wasip2.go rename src/syscall/{syscall_libc_wasip1.go => syscall_libc_wasi.go} (95%) create mode 160000 src/vendor/github.com/ydnar/wasm-tools-go create mode 100644 targets/wasip2.json create mode 100755 tools/tgtestjson.sh diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 38f0d92f5d..8bb92e0832 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -134,17 +134,19 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + submodules: true - name: Install Go uses: actions/setup-go@v5 with: go-version: '1.22' cache: true - name: Install wasmtime - run: | - mkdir -p $HOME/.wasmtime $HOME/.wasmtime/bin - curl https://github.com/bytecodealliance/wasmtime/releases/download/v14.0.4/wasmtime-v14.0.4-x86_64-linux.tar.xz -o wasmtime-v14.0.4-x86_64-linux.tar.xz -SfL - tar -C $HOME/.wasmtime/bin --wildcards -xf wasmtime-v14.0.4-x86_64-linux.tar.xz --strip-components=1 wasmtime-v14.0.4-x86_64-linux/* - echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH + uses: bytecodealliance/actions/wasmtime/setup@v1 + with: + version: "19.0.1" + - name: Install wasm-tools + uses: bytecodealliance/actions/wasm-tools/setup@v1 - name: Download release artifact uses: actions/download-artifact@v4 with: @@ -154,8 +156,8 @@ jobs: mkdir -p ~/lib tar -C ~/lib -xf tinygo.linux-amd64.tar.gz ln -s ~/lib/tinygo/bin/tinygo ~/go/bin/tinygo - - run: make tinygo-test-wasi-fast - run: make tinygo-test-wasip1-fast + - run: make tinygo-test-wasip2-fast - run: make smoketest assert-test-linux: # Run all tests that can run on Linux, with LLVM assertions enabled to catch @@ -187,11 +189,11 @@ jobs: with: node-version: '18' - name: Install wasmtime - run: | - mkdir -p $HOME/.wasmtime $HOME/.wasmtime/bin - curl -L https://github.com/bytecodealliance/wasmtime/releases/download/v14.0.4/wasmtime-v14.0.4-x86_64-linux.tar.xz -o wasmtime-v14.0.4-x86_64-linux.tar.xz -SfL - tar -C $HOME/.wasmtime/bin --wildcards -xf wasmtime-v14.0.4-x86_64-linux.tar.xz --strip-components=1 wasmtime-v14.0.4-x86_64-linux/* - echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH + uses: bytecodealliance/actions/wasmtime/setup@v1 + with: + version: "19.0.1" + - name: Setup `wasm-tools` + uses: bytecodealliance/actions/wasm-tools/setup@v1 - name: Restore LLVM source cache uses: actions/cache/restore@v4 id: cache-llvm-source diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 5cfba9217d..2b26a9b1c6 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -220,5 +220,5 @@ jobs: shell: bash working-directory: build run: 7z x release.zip -r - - name: Test stdlib packages on wasi - run: make tinygo-test-wasi-fast TINYGO=$(PWD)/build/tinygo/bin/tinygo + - name: Test stdlib packages on wasip1 + run: make tinygo-test-wasip1-fast TINYGO=$(PWD)/build/tinygo/bin/tinygo diff --git a/.gitignore b/.gitignore index 9d4f702f8c..9994ee3dca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +go.work +go.work.sum + docs/_build src/device/avr/*.go src/device/avr/*.ld @@ -17,7 +20,7 @@ src/device/kendryte/*.go src/device/kendryte/*.s src/device/rp/*.go src/device/rp/*.s -vendor +./vendor llvm-build llvm-project build/* diff --git a/.gitmodules b/.gitmodules index c3e7e47bb8..edd7b215c1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -39,3 +39,9 @@ path = src/net url = https://github.com/tinygo-org/net.git branch = dev +[submodule "lib/wasi-cli"] + path = lib/wasi-cli + url = https://github.com/WebAssembly/wasi-cli +[submodule "src/vendor/github.com/ydnar/wasm-tools-go"] + path = src/vendor/github.com/ydnar/wasm-tools-go + url = https://github.com/ydnar/wasm-tools-go.git diff --git a/GNUmakefile b/GNUmakefile index 2cc70df7fa..ca8718ca54 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -268,6 +268,11 @@ lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a: @if [ ! -e lib/wasi-libc/Makefile ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi cd lib/wasi-libc && $(MAKE) -j4 EXTRA_CFLAGS="-O2 -g -DNDEBUG -mnontrapping-fptoint -msign-ext" MALLOC_IMPL=none CC="$(CLANG)" AR=$(LLVM_AR) NM=$(LLVM_NM) +# Generate WASI syscall bindings +.PHONY: wasi-syscall +wasi-syscall: + wit-bindgen-go generate -o ./src/internal -p internal --versioned ./lib/wasi-cli/wit + # Check for Node.js used during WASM tests. NODEJS_VERSION := $(word 1,$(subst ., ,$(shell node -v | cut -c 2-))) MIN_NODEJS_VERSION=18 @@ -430,15 +435,36 @@ tinygo-test-wasi: $(TINYGO) test -target wasip1 $(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 -tinygo-test-wasi-fast: - $(TINYGO) test -target wasip1 $(TEST_PACKAGES_FAST) ./tests/runtime_wasi tinygo-test-wasip1-fast: - GOOS=wasip1 GOARCH=wasm $(TINYGO) test $(TEST_PACKAGES_FAST) ./tests/runtime_wasi -tinygo-bench-wasi: + $(TINYGO) test -target=wasip1 $(TEST_PACKAGES_FAST) ./tests/runtime_wasi + +tinygo-test-wasip2-slow: + $(TINYGO) test -target=wasip2 $(TEST_PACKAGES_SLOW) +tinygo-test-wasip2-fast: + $(TINYGO) test -target=wasip2 $(TEST_PACKAGES_FAST) ./tests/runtime_wasi + +tinygo-test-wasip2-sum-slow: + TINYGO=$(TINYGO) \ + TARGET=wasip2 \ + TESTOPTS="-x -work" \ + PACKAGES="$(TEST_PACKAGES_SLOW)" \ + gotestsum --raw-command -- ./tools/tgtestjson.sh +tinygo-test-wasip2-sum-fast: + TINYGO=$(TINYGO) \ + TARGET=wasip2 \ + TESTOPTS="-x -work" \ + PACKAGES="$(TEST_PACKAGES_FAST)" \ + gotestsum --raw-command -- ./tools/tgtestjson.sh +tinygo-bench-wasip1: $(TINYGO) test -target wasip1 -bench . $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) -tinygo-bench-wasi-fast: +tinygo-bench-wasip1-fast: $(TINYGO) test -target wasip1 -bench . $(TEST_PACKAGES_FAST) +tinygo-bench-wasip2: + $(TINYGO) test -target wasip2 -bench . $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) +tinygo-bench-wasip2-fast: + $(TINYGO) test -target wasip2 -bench . $(TEST_PACKAGES_FAST) + # Test external packages in a large corpus. test-corpus: CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml @@ -832,6 +858,7 @@ build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN @mkdir -p build/release/tinygo/lib/wasi-libc/libc-bottom-half/headers @mkdir -p build/release/tinygo/lib/wasi-libc/libc-top-half/musl/arch @mkdir -p build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src + @mkdir -p build/release/tinygo/lib/wasi-cli/ @mkdir -p build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0 @mkdir -p build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus @mkdir -p build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4 @@ -891,6 +918,7 @@ endif @cp -rp lib/wasi-libc/libc-top-half/musl/src/string build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/include build/release/tinygo/lib/wasi-libc/libc-top-half/musl @cp -rp lib/wasi-libc/sysroot build/release/tinygo/lib/wasi-libc/sysroot + @cp -rp lib/wasi-cli/wit build/release/tinygo/lib/wasi-cli/wit @cp -rp llvm-project/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt-builtins @cp -rp llvm-project/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt-builtins @cp -rp src build/release/tinygo/src diff --git a/builder/build.go b/builder/build.go index c569bb1d21..d99d640af6 100644 --- a/builder/build.go +++ b/builder/build.go @@ -840,11 +840,11 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe "--output", result.Executable, ) + wasmopt := goenv.Get("WASMOPT") if config.Options.PrintCommands != nil { - config.Options.PrintCommands(goenv.Get("WASMOPT"), args...) + config.Options.PrintCommands(wasmopt, args...) } - - cmd := exec.Command(goenv.Get("WASMOPT"), args...) + cmd := exec.Command(wasmopt, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -854,6 +854,62 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } } + // Run wasm-tools for component-model binaries + witPackage := strings.ReplaceAll(config.Target.WITPackage, "{root}", goenv.Get("TINYGOROOT")) + if config.Options.WITPackage != "" { + witPackage = config.Options.WITPackage + } + witWorld := config.Target.WITWorld + if config.Options.WITWorld != "" { + witWorld = config.Options.WITWorld + } + if witPackage != "" && witWorld != "" { + + // wasm-tools component embed -w wasi:cli/command + // $$(tinygo env TINYGOROOT)/lib/wasi-cli/wit/ main.wasm -o embedded.wasm + args := []string{ + "component", + "embed", + "-w", witWorld, + witPackage, + result.Executable, + "-o", result.Executable, + } + + wasmtools := goenv.Get("WASMTOOLS") + if config.Options.PrintCommands != nil { + config.Options.PrintCommands(wasmtools, args...) + } + cmd := exec.Command(wasmtools, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err := cmd.Run() + if err != nil { + return fmt.Errorf("wasm-tools failed: %w", err) + } + + // wasm-tools component new embedded.wasm -o component.wasm + args = []string{ + "component", + "new", + result.Executable, + "-o", result.Executable, + } + + if config.Options.PrintCommands != nil { + config.Options.PrintCommands(wasmtools, args...) + } + cmd = exec.Command(wasmtools, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err = cmd.Run() + if err != nil { + return fmt.Errorf("wasm-tools failed: %w", err) + } + } + // Print code size if requested. if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" { packagePathMap := make(map[string]string, len(lprogram.Packages)) diff --git a/builder/builder_test.go b/builder/builder_test.go index 3bb3e98411..c7b8638730 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -34,6 +34,7 @@ func TestClangAttributes(t *testing.T) { "nintendoswitch", "riscv-qemu", "wasip1", + "wasip2", "wasm", "wasm-unknown", } @@ -61,6 +62,7 @@ func TestClangAttributes(t *testing.T) { {GOOS: "windows", GOARCH: "amd64"}, {GOOS: "windows", GOARCH: "arm64"}, {GOOS: "wasip1", GOARCH: "wasm"}, + {GOOS: "wasip2", GOARCH: "wasm"}, } { name := "GOOS=" + options.GOOS + ",GOARCH=" + options.GOARCH if options.GOARCH == "arm" { diff --git a/compileopts/options.go b/compileopts/options.go index debdaf08cd..8b0e2266de 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -53,6 +53,8 @@ type Options struct { Monitor bool BaudRate int Timeout time.Duration + WITPackage string // pass through to wasm-tools component embed invocation + WITWorld string // pass through to wasm-tools component embed -w option } // Verify performs a validation on the given options, raising an error if options are not valid. diff --git a/compileopts/target.go b/compileopts/target.go index 9a7d550fab..646029e447 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -62,6 +62,8 @@ type TargetSpec struct { JLinkDevice string `json:"jlink-device,omitempty"` CodeModel string `json:"code-model,omitempty"` RelocationModel string `json:"relocation-model,omitempty"` + WITPackage string `json:"wit-package,omitempty"` + WITWorld string `json:"wit-world,omitempty"` } // overrideProperties overrides all properties that are set in child into itself using reflection. diff --git a/compiler/symbol.go b/compiler/symbol.go index bf5ac5f1b7..5ebdee1471 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -346,7 +346,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" || c.pkg.Path() == "syscall/js" { + if c.pkg.Path() == "runtime" || c.pkg.Path() == "syscall/js" || c.pkg.Path() == "syscall" { // The runtime is a special case. Allow all kinds of parameters // (importantly, including pointers). return @@ -360,38 +360,68 @@ func (c *compilerContext) checkWasmImport(f *ssa.Function, pragma string) { c.addError(f.Signature.Results().At(1).Pos(), fmt.Sprintf("%s: too many return values", pragma)) } else if f.Signature.Results().Len() == 1 { result := f.Signature.Results().At(0) - if !isValidWasmType(result.Type(), true) { + if !isValidWasmType(result.Type(), siteResult) { c.addError(result.Pos(), fmt.Sprintf("%s: unsupported result type %s", pragma, result.Type().String())) } } for _, param := range f.Params { // Check whether the type is allowed. // Only a very limited number of types can be mapped to WebAssembly. - if !isValidWasmType(param.Type(), false) { + if !isValidWasmType(param.Type(), siteParam) { c.addError(param.Pos(), fmt.Sprintf("%s: unsupported parameter type %s", pragma, param.Type().String())) } } } -// Check whether the type maps directly to a WebAssembly type, according to: +// Check whether the type maps directly to a WebAssembly type. +// +// This reflects the relaxed type restrictions proposed here (except for structs.HostLayout): +// https://github.com/golang/go/issues/66984 +// +// This previously reflected the additional restrictions documented here: // https://github.com/golang/go/issues/59149 -func isValidWasmType(typ types.Type, isReturn bool) bool { +func isValidWasmType(typ types.Type, site wasmSite) bool { switch typ := typ.Underlying().(type) { case *types.Basic: switch typ.Kind() { - case types.Int32, types.Uint32, types.Int64, types.Uint64: + case types.Bool: + return true + case types.Int, types.Uint, types.Int8, types.Uint8, types.Int16, types.Uint16, types.Int32, types.Uint32, types.Int64, types.Uint64: return true case types.Float32, types.Float64: return true - case types.UnsafePointer: - if !isReturn { - return true + case types.Uintptr, types.UnsafePointer: + return true + case types.String: + // string flattens to two values, so disallowed as a result + return site == siteParam || site == siteIndirect + } + case *types.Array: + return site == siteIndirect && isValidWasmType(typ.Elem(), siteIndirect) + case *types.Struct: + if site != siteIndirect { + return false + } + for i := 0; i < typ.NumFields(); i++ { + if !isValidWasmType(typ.Field(i).Type(), siteIndirect) { + return false } } + return true + case *types.Pointer: + return isValidWasmType(typ.Elem(), siteIndirect) } return false } +type wasmSite int + +const ( + siteParam wasmSite = iota + siteResult + siteIndirect // pointer or field +) + // getParams returns the function parameters, including the receiver at the // start. This is an alternative to the Params member of *ssa.Function, which is // not yet populated when the package has not yet been built. diff --git a/compiler/testdata/errors.go b/compiler/testdata/errors.go index 5778a931e1..81e8a76a18 100644 --- a/compiler/testdata/errors.go +++ b/compiler/testdata/errors.go @@ -13,31 +13,65 @@ func implementation() { type Uint uint32 +type S struct { + a [4]uint32 + b uintptr + c int + d float32 + e float64 +} + //go:wasmimport modulename validparam -func validparam(a int32, b uint64, c float64, d unsafe.Pointer, e Uint) +func validparam(a int32, b uint64, c float64, d unsafe.Pointer, e Uint, f uintptr, g string, h *int32, i *S) -// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type int -// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type string +// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type [4]uint32 // ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type []byte -// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type *int32 +// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type struct{a int} +// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type chan struct{} +// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type func() // //go:wasmimport modulename invalidparam -func invalidparam(a int, b string, c []byte, d *int32) +func invalidparam(a [4]uint32, b []byte, c struct{ a int }, d chan struct{}, e func()) + +//go:wasmimport modulename validreturn_int32 +func validreturn_int32() int32 + +//go:wasmimport modulename validreturn_int +func validreturn_int() int + +//go:wasmimport modulename validreturn_ptr_int32 +func validreturn_ptr_int32() *int32 + +//go:wasmimport modulename validreturn_ptr_string +func validreturn_ptr_string() *string -//go:wasmimport modulename validreturn -func validreturn() int32 +//go:wasmimport modulename validreturn_ptr_struct +func validreturn_ptr_struct() *S + +//go:wasmimport modulename validreturn_unsafe_pointer +func validreturn_unsafe_pointer() unsafe.Pointer // ERROR: //go:wasmimport modulename manyreturns: too many return values // //go:wasmimport modulename manyreturns func manyreturns() (int32, int32) -// ERROR: //go:wasmimport modulename invalidreturn: unsupported result type int +// ERROR: //go:wasmimport modulename invalidreturn_func: unsupported result type func() +// +//go:wasmimport modulename invalidreturn_func +func invalidreturn_func() func() + +// ERROR: //go:wasmimport modulename invalidreturn_slice_byte: unsupported result type []byte +// +//go:wasmimport modulename invalidreturn_slice_byte +func invalidreturn_slice_byte() []byte + +// ERROR: //go:wasmimport modulename invalidreturn_chan_int: unsupported result type chan int // -//go:wasmimport modulename invalidreturn -func invalidreturn() int +//go:wasmimport modulename invalidreturn_chan_int +func invalidreturn_chan_int() chan int -// ERROR: //go:wasmimport modulename invalidUnsafePointerReturn: unsupported result type unsafe.Pointer +// ERROR: //go:wasmimport modulename invalidreturn_string: unsupported result type string // -//go:wasmimport modulename invalidUnsafePointerReturn -func invalidUnsafePointerReturn() unsafe.Pointer +//go:wasmimport modulename invalidreturn_string +func invalidreturn_string() string diff --git a/goenv/goenv.go b/goenv/goenv.go index be1c631ca9..187bcc6df1 100644 --- a/goenv/goenv.go +++ b/goenv/goenv.go @@ -159,6 +159,11 @@ func Get(name string) string { } return findWasmOpt() + case "WASMTOOLS": + if path := os.Getenv("WASMTOOLS"); path != "" { + return path + } + return "wasm-tools" default: return "" } diff --git a/lib/wasi-cli b/lib/wasi-cli new file mode 160000 index 0000000000..6ae8261709 --- /dev/null +++ b/lib/wasi-cli @@ -0,0 +1 @@ +Subproject commit 6ae82617096e83e6606047736e84ac397b788631 diff --git a/loader/goroot.go b/loader/goroot.go index 7c8d97fa46..72b2f33355 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -241,6 +241,7 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { "internal/fuzz/": false, "internal/reflectlite/": false, "internal/task/": false, + "internal/wasi/": false, "machine/": false, "net/": true, "net/http/": false, @@ -250,6 +251,8 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { "runtime/": false, "sync/": true, "testing/": true, + "vendor/": true, + "vendor/github.com/": false, } if goMinor >= 19 { diff --git a/main.go b/main.go index 6b3aeb3b03..cd2e43ace0 100644 --- a/main.go +++ b/main.go @@ -306,16 +306,25 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options // reads any files. // // Ex. run --dir=.. --dir=../.. --dir=../../.. - dirs := dirsToModuleRoot(result.MainDir, result.ModuleRoot) + var dirs []string + switch config.Options.Target { + case "wasip1": + dirs = dirsToModuleRootRel(result.MainDir, result.ModuleRoot) + case "wasip2": + dirs = dirsToModuleRootAbs(result.MainDir, result.ModuleRoot) + default: + return fmt.Errorf("unknown GOOS target: %v", config.Options.Target) + } + args := []string{"run"} - for _, d := range dirs[1:] { + for _, d := range dirs { args = append(args, "--dir="+d) } - // The below re-organizes the arguments so that the current - // directory is added last. + args = append(args, "--env=PWD="+cmd.Dir) + args = append(args, cmd.Args[1:]...) - cmd.Args = append(cmd.Args[:1:1], args...) + cmd.Args = args } // Run the test. @@ -356,8 +365,8 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options return passed, err } -func dirsToModuleRoot(maindir, modroot string) []string { - var dirs = []string{"."} +func dirsToModuleRootRel(maindir, modroot string) []string { + var dirs []string last := ".." // strip off path elements until we hit the module root // adding `..`, `../..`, `../../..` until we're done @@ -366,6 +375,20 @@ func dirsToModuleRoot(maindir, modroot string) []string { last = filepath.Join(last, "..") maindir = filepath.Dir(maindir) } + dirs = append(dirs, ".") + return dirs +} + +func dirsToModuleRootAbs(maindir, modroot string) []string { + var dirs = []string{maindir} + last := filepath.Join(maindir, "..") + // strip off path elements until we hit the module root + // adding `..`, `../..`, `../../..` until we're done + for maindir != modroot { + dirs = append(dirs, last) + last = filepath.Join(last, "..") + maindir = filepath.Dir(maindir) + } return dirs } @@ -785,6 +808,9 @@ func Run(pkgName string, options *compileopts.Options, cmdArgs []string) error { // passes command line arguments and evironment variables in a way appropriate // for the given emulator. func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, cmdArgs, environmentVars []string, timeout time.Duration, run func(cmd *exec.Cmd, result builder.BuildResult) error) (builder.BuildResult, error) { + + isSingleFile := strings.HasSuffix(pkgName, ".go") + // Determine whether we're on a system that supports environment variables // and command line parameters (operating systems, WASI) or not (baremetal, // WebAssembly in the browser). If we're on a system without an environment, @@ -818,9 +844,6 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c } } } else if config.EmulatorName() == "wasmtime" { - // Wasmtime needs some special flags to pass environment variables - // and allow reading from the current directory. - emuArgs = append(emuArgs, "--dir=.") for _, v := range environmentVars { emuArgs = append(emuArgs, "--env", v) } @@ -876,7 +899,26 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c if err != nil { return result, err } + name = emulator[0] + + if name == "wasmtime" { + // Wasmtime needs some special flags to pass environment variables + // and allow reading from the current directory. + switch config.Options.Target { + case "wasip1": + emuArgs = append(emuArgs, "--dir=.") + case "wasip2": + dir := result.MainDir + if isSingleFile { + cwd, _ := os.Getwd() + dir = cwd + } + emuArgs = append(emuArgs, "--dir="+dir) + emuArgs = append(emuArgs, "--env=PWD="+dir) + } + } + emuArgs = append(emuArgs, emulator[1:]...) args = append(emuArgs, args...) } @@ -1465,6 +1507,12 @@ func main() { flag.StringVar(&outpath, "o", "", "output filename") } + var witPackage, witWorld string + if command == "help" || command == "build" || command == "test" || command == "run" { + flag.StringVar(&witPackage, "wit-package", "", "wit package for wasm component embedding") + flag.StringVar(&witWorld, "wit-world", "", "wit world for wasm component embedding") + } + var testConfig compileopts.TestConfig if command == "help" || command == "test" { flag.BoolVar(&testConfig.CompileOnly, "c", false, "compile the test binary but do not run it") @@ -1544,6 +1592,8 @@ func main() { Monitor: *monitor, BaudRate: *baudrate, Timeout: *timeout, + WITPackage: witPackage, + WITWorld: witWorld, } if *printCommands { options.PrintCommands = printCommand diff --git a/main_test.go b/main_test.go index 1ea16cd143..a4ec3c810d 100644 --- a/main_test.go +++ b/main_test.go @@ -181,6 +181,10 @@ func TestBuild(t *testing.T) { t.Parallel() runPlatTests(optionsFromTarget("wasip1", sema), tests, t) }) + t.Run("WASIp2", func(t *testing.T) { + t.Parallel() + runPlatTests(optionsFromTarget("wasip2", sema), tests, t) + }) } } @@ -235,6 +239,14 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { default: } } + if options.Target == "wasip2" { + switch name { + case "cgo/": + // waisp2 use our own libc; cgo tests fail + continue + } + } + name := name // redefine to avoid race condition t.Run(name, func(t *testing.T) { t.Parallel() diff --git a/src/crypto/rand/rand_arc4random.go b/src/crypto/rand/rand_arc4random.go index 1b1796b4d6..64a6218bd9 100644 --- a/src/crypto/rand/rand_arc4random.go +++ b/src/crypto/rand/rand_arc4random.go @@ -1,4 +1,4 @@ -//go:build darwin || tinygo.wasm +//go:build darwin || wasip1 || wasip2 || wasm // This implementation of crypto/rand uses the arc4random_buf function // (available on both MacOS and WASI) to generate random numbers. diff --git a/src/crypto/rand/rand_urandom.go b/src/crypto/rand/rand_urandom.go index e9a8d485e9..53554529b4 100644 --- a/src/crypto/rand/rand_urandom.go +++ b/src/crypto/rand/rand_urandom.go @@ -1,4 +1,4 @@ -//go:build linux && !baremetal && !wasip1 +//go:build linux && !baremetal && !wasip1 && !wasip2 // This implementation of crypto/rand uses the /dev/urandom pseudo-file to // generate random numbers. diff --git a/src/internal/wasi/cli/v0.2.0/command/command.wit.go b/src/internal/wasi/cli/v0.2.0/command/command.wit.go new file mode 100644 index 0000000000..be6dd30450 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/command/command.wit.go @@ -0,0 +1,6 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package command represents the world "wasi:cli/command@0.2.0". +package command diff --git a/src/internal/wasi/cli/v0.2.0/environment/empty.s b/src/internal/wasi/cli/v0.2.0/environment/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/environment/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go b/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go new file mode 100644 index 0000000000..b75e74b185 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go @@ -0,0 +1,66 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package environment represents the imported interface "wasi:cli/environment@0.2.0". +package environment + +import ( + "github.com/ydnar/wasm-tools-go/cm" +) + +// GetEnvironment represents the imported function "get-environment". +// +// Get the POSIX-style environment variables. +// +// Each environment variable is provided as a pair of string variable names +// and string value. +// +// Morally, these are a value import, but until value imports are available +// in the component model, this import function should return the same +// values each time it is called. +// +// get-environment: func() -> list> +// +//go:nosplit +func GetEnvironment() (result cm.List[[2]string]) { + wasmimport_GetEnvironment(&result) + return +} + +//go:wasmimport wasi:cli/environment@0.2.0 get-environment +//go:noescape +func wasmimport_GetEnvironment(result *cm.List[[2]string]) + +// GetArguments represents the imported function "get-arguments". +// +// Get the POSIX-style arguments to the program. +// +// get-arguments: func() -> list +// +//go:nosplit +func GetArguments() (result cm.List[string]) { + wasmimport_GetArguments(&result) + return +} + +//go:wasmimport wasi:cli/environment@0.2.0 get-arguments +//go:noescape +func wasmimport_GetArguments(result *cm.List[string]) + +// InitialCWD represents the imported function "initial-cwd". +// +// Return a path that programs should use as their initial current working +// directory, interpreting `.` as shorthand for this. +// +// initial-cwd: func() -> option +// +//go:nosplit +func InitialCWD() (result cm.Option[string]) { + wasmimport_InitialCWD(&result) + return +} + +//go:wasmimport wasi:cli/environment@0.2.0 initial-cwd +//go:noescape +func wasmimport_InitialCWD(result *cm.Option[string]) diff --git a/src/internal/wasi/cli/v0.2.0/exit/empty.s b/src/internal/wasi/cli/v0.2.0/exit/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/exit/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go b/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go new file mode 100644 index 0000000000..36af67d244 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go @@ -0,0 +1,27 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package exit represents the imported interface "wasi:cli/exit@0.2.0". +package exit + +import ( + "github.com/ydnar/wasm-tools-go/cm" +) + +// Exit represents the imported function "exit". +// +// Exit the current instance and any linked instances. +// +// exit: func(status: result) +// +//go:nosplit +func Exit(status cm.Result) { + status0 := cm.LowerResult(status) + wasmimport_Exit((uint32)(status0)) + return +} + +//go:wasmimport wasi:cli/exit@0.2.0 exit +//go:noescape +func wasmimport_Exit(status0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/run/empty.s b/src/internal/wasi/cli/v0.2.0/run/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/run/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/cli/v0.2.0/run/run.exports.go b/src/internal/wasi/cli/v0.2.0/run/run.exports.go new file mode 100644 index 0000000000..267e75a081 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/run/run.exports.go @@ -0,0 +1,19 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +package run + +import ( + "github.com/ydnar/wasm-tools-go/cm" +) + +// Exports represents the caller-defined exports from "wasi:cli/run@0.2.0". +var Exports struct { + // Run represents the caller-defined, exported function "run". + // + // Run the program. + // + // run: func() -> result + Run func() (result cm.Result) +} diff --git a/src/internal/wasi/cli/v0.2.0/run/run.wit.go b/src/internal/wasi/cli/v0.2.0/run/run.wit.go new file mode 100644 index 0000000000..728d0292ad --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/run/run.wit.go @@ -0,0 +1,18 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package run represents the exported interface "wasi:cli/run@0.2.0". +package run + +import ( + "github.com/ydnar/wasm-tools-go/cm" +) + +//go:wasmexport wasi:cli/run@0.2.0#run +//export wasi:cli/run@0.2.0#run +func wasmexport_Run() (result0 uint32) { + result := Exports.Run() + result0 = cm.LowerResult(result) + return +} diff --git a/src/internal/wasi/cli/v0.2.0/stderr/empty.s b/src/internal/wasi/cli/v0.2.0/stderr/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/stderr/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go new file mode 100644 index 0000000000..fd2b7517c2 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go @@ -0,0 +1,26 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package stderr represents the imported interface "wasi:cli/stderr@0.2.0". +package stderr + +import ( + "github.com/ydnar/wasm-tools-go/cm" + "internal/wasi/io/v0.2.0/streams" +) + +// GetStderr represents the imported function "get-stderr". +// +// get-stderr: func() -> output-stream +// +//go:nosplit +func GetStderr() (result streams.OutputStream) { + result0 := wasmimport_GetStderr() + result = cm.Reinterpret[streams.OutputStream]((uint32)(result0)) + return +} + +//go:wasmimport wasi:cli/stderr@0.2.0 get-stderr +//go:noescape +func wasmimport_GetStderr() (result0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/stdin/empty.s b/src/internal/wasi/cli/v0.2.0/stdin/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/stdin/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go new file mode 100644 index 0000000000..abe35cbbb0 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go @@ -0,0 +1,26 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package stdin represents the imported interface "wasi:cli/stdin@0.2.0". +package stdin + +import ( + "github.com/ydnar/wasm-tools-go/cm" + "internal/wasi/io/v0.2.0/streams" +) + +// GetStdin represents the imported function "get-stdin". +// +// get-stdin: func() -> input-stream +// +//go:nosplit +func GetStdin() (result streams.InputStream) { + result0 := wasmimport_GetStdin() + result = cm.Reinterpret[streams.InputStream]((uint32)(result0)) + return +} + +//go:wasmimport wasi:cli/stdin@0.2.0 get-stdin +//go:noescape +func wasmimport_GetStdin() (result0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/stdout/empty.s b/src/internal/wasi/cli/v0.2.0/stdout/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/stdout/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go new file mode 100644 index 0000000000..2f56b19ec4 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go @@ -0,0 +1,26 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package stdout represents the imported interface "wasi:cli/stdout@0.2.0". +package stdout + +import ( + "github.com/ydnar/wasm-tools-go/cm" + "internal/wasi/io/v0.2.0/streams" +) + +// GetStdout represents the imported function "get-stdout". +// +// get-stdout: func() -> output-stream +// +//go:nosplit +func GetStdout() (result streams.OutputStream) { + result0 := wasmimport_GetStdout() + result = cm.Reinterpret[streams.OutputStream]((uint32)(result0)) + return +} + +//go:wasmimport wasi:cli/stdout@0.2.0 get-stdout +//go:noescape +func wasmimport_GetStdout() (result0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-input/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-input/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-input/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go new file mode 100644 index 0000000000..4788644280 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go @@ -0,0 +1,38 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package terminalinput represents the imported interface "wasi:cli/terminal-input@0.2.0". +// +// Terminal input. +// +// In the future, this may include functions for disabling echoing, +// disabling input buffering so that keyboard events are sent through +// immediately, querying supported features, and so on. +package terminalinput + +import ( + "github.com/ydnar/wasm-tools-go/cm" +) + +// TerminalInput represents the imported resource "wasi:cli/terminal-input@0.2.0#terminal-input". +// +// The input side of a terminal. +// +// resource terminal-input +type TerminalInput cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "terminal-input". +// +// Drops a resource handle. +// +//go:nosplit +func (self TerminalInput) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TerminalInputResourceDrop((uint32)(self0)) + return +} + +//go:wasmimport wasi:cli/terminal-input@0.2.0 [resource-drop]terminal-input +//go:noescape +func wasmimport_TerminalInputResourceDrop(self0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-output/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-output/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-output/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go new file mode 100644 index 0000000000..759348b85e --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go @@ -0,0 +1,38 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package terminaloutput represents the imported interface "wasi:cli/terminal-output@0.2.0". +// +// Terminal output. +// +// In the future, this may include functions for querying the terminal +// size, being notified of terminal size changes, querying supported +// features, and so on. +package terminaloutput + +import ( + "github.com/ydnar/wasm-tools-go/cm" +) + +// TerminalOutput represents the imported resource "wasi:cli/terminal-output@0.2.0#terminal-output". +// +// The output side of a terminal. +// +// resource terminal-output +type TerminalOutput cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "terminal-output". +// +// Drops a resource handle. +// +//go:nosplit +func (self TerminalOutput) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TerminalOutputResourceDrop((uint32)(self0)) + return +} + +//go:wasmimport wasi:cli/terminal-output@0.2.0 [resource-drop]terminal-output +//go:noescape +func wasmimport_TerminalOutputResourceDrop(self0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stderr/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-stderr/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-stderr/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go new file mode 100644 index 0000000000..db720e1511 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go @@ -0,0 +1,31 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package terminalstderr represents the imported interface "wasi:cli/terminal-stderr@0.2.0". +// +// An interface providing an optional `terminal-output` for stderr as a +// link-time authority. +package terminalstderr + +import ( + "github.com/ydnar/wasm-tools-go/cm" + terminaloutput "internal/wasi/cli/v0.2.0/terminal-output" +) + +// GetTerminalStderr represents the imported function "get-terminal-stderr". +// +// If stderr is connected to a terminal, return a `terminal-output` handle +// allowing further interaction with it. +// +// get-terminal-stderr: func() -> option +// +//go:nosplit +func GetTerminalStderr() (result cm.Option[terminaloutput.TerminalOutput]) { + wasmimport_GetTerminalStderr(&result) + return +} + +//go:wasmimport wasi:cli/terminal-stderr@0.2.0 get-terminal-stderr +//go:noescape +func wasmimport_GetTerminalStderr(result *cm.Option[terminaloutput.TerminalOutput]) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdin/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-stdin/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-stdin/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go new file mode 100644 index 0000000000..091d8e8c5f --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go @@ -0,0 +1,31 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package terminalstdin represents the imported interface "wasi:cli/terminal-stdin@0.2.0". +// +// An interface providing an optional `terminal-input` for stdin as a +// link-time authority. +package terminalstdin + +import ( + "github.com/ydnar/wasm-tools-go/cm" + terminalinput "internal/wasi/cli/v0.2.0/terminal-input" +) + +// GetTerminalStdin represents the imported function "get-terminal-stdin". +// +// If stdin is connected to a terminal, return a `terminal-input` handle +// allowing further interaction with it. +// +// get-terminal-stdin: func() -> option +// +//go:nosplit +func GetTerminalStdin() (result cm.Option[terminalinput.TerminalInput]) { + wasmimport_GetTerminalStdin(&result) + return +} + +//go:wasmimport wasi:cli/terminal-stdin@0.2.0 get-terminal-stdin +//go:noescape +func wasmimport_GetTerminalStdin(result *cm.Option[terminalinput.TerminalInput]) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdout/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-stdout/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-stdout/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go new file mode 100644 index 0000000000..d0d9bfe485 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go @@ -0,0 +1,31 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package terminalstdout represents the imported interface "wasi:cli/terminal-stdout@0.2.0". +// +// An interface providing an optional `terminal-output` for stdout as a +// link-time authority. +package terminalstdout + +import ( + "github.com/ydnar/wasm-tools-go/cm" + terminaloutput "internal/wasi/cli/v0.2.0/terminal-output" +) + +// GetTerminalStdout represents the imported function "get-terminal-stdout". +// +// If stdout is connected to a terminal, return a `terminal-output` handle +// allowing further interaction with it. +// +// get-terminal-stdout: func() -> option +// +//go:nosplit +func GetTerminalStdout() (result cm.Option[terminaloutput.TerminalOutput]) { + wasmimport_GetTerminalStdout(&result) + return +} + +//go:wasmimport wasi:cli/terminal-stdout@0.2.0 get-terminal-stdout +//go:noescape +func wasmimport_GetTerminalStdout(result *cm.Option[terminaloutput.TerminalOutput]) diff --git a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/empty.s b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go new file mode 100644 index 0000000000..4c68033654 --- /dev/null +++ b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go @@ -0,0 +1,115 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package monotonicclock represents the imported interface "wasi:clocks/monotonic-clock@0.2.0". +// +// WASI Monotonic Clock is a clock API intended to let users measure elapsed +// time. +// +// It is intended to be portable at least between Unix-family platforms and +// Windows. +// +// A monotonic clock is a clock which has an unspecified initial value, and +// successive reads of the clock will produce non-decreasing values. +// +// It is intended for measuring elapsed time. +package monotonicclock + +import ( + "github.com/ydnar/wasm-tools-go/cm" + "internal/wasi/io/v0.2.0/poll" +) + +// Instant represents the u64 "wasi:clocks/monotonic-clock@0.2.0#instant". +// +// An instant in time, in nanoseconds. An instant is relative to an +// unspecified initial value, and can only be compared to instances from +// the same monotonic-clock. +// +// type instant = u64 +type Instant uint64 + +// Duration represents the u64 "wasi:clocks/monotonic-clock@0.2.0#duration". +// +// A duration of time, in nanoseconds. +// +// type duration = u64 +type Duration uint64 + +// Now represents the imported function "now". +// +// Read the current value of the clock. +// +// The clock is monotonic, therefore calling this function repeatedly will +// produce a sequence of non-decreasing values. +// +// now: func() -> instant +// +//go:nosplit +func Now() (result Instant) { + result0 := wasmimport_Now() + result = (Instant)((uint64)(result0)) + return +} + +//go:wasmimport wasi:clocks/monotonic-clock@0.2.0 now +//go:noescape +func wasmimport_Now() (result0 uint64) + +// Resolution represents the imported function "resolution". +// +// Query the resolution of the clock. Returns the duration of time +// corresponding to a clock tick. +// +// resolution: func() -> duration +// +//go:nosplit +func Resolution() (result Duration) { + result0 := wasmimport_Resolution() + result = (Duration)((uint64)(result0)) + return +} + +//go:wasmimport wasi:clocks/monotonic-clock@0.2.0 resolution +//go:noescape +func wasmimport_Resolution() (result0 uint64) + +// SubscribeInstant represents the imported function "subscribe-instant". +// +// Create a `pollable` which will resolve once the specified instant +// occured. +// +// subscribe-instant: func(when: instant) -> pollable +// +//go:nosplit +func SubscribeInstant(when Instant) (result poll.Pollable) { + when0 := (uint64)(when) + result0 := wasmimport_SubscribeInstant((uint64)(when0)) + result = cm.Reinterpret[poll.Pollable]((uint32)(result0)) + return +} + +//go:wasmimport wasi:clocks/monotonic-clock@0.2.0 subscribe-instant +//go:noescape +func wasmimport_SubscribeInstant(when0 uint64) (result0 uint32) + +// SubscribeDuration represents the imported function "subscribe-duration". +// +// Create a `pollable` which will resolve once the given duration has +// elapsed, starting at the time at which this function was called. +// occured. +// +// subscribe-duration: func(when: duration) -> pollable +// +//go:nosplit +func SubscribeDuration(when Duration) (result poll.Pollable) { + when0 := (uint64)(when) + result0 := wasmimport_SubscribeDuration((uint64)(when0)) + result = cm.Reinterpret[poll.Pollable]((uint32)(result0)) + return +} + +//go:wasmimport wasi:clocks/monotonic-clock@0.2.0 subscribe-duration +//go:noescape +func wasmimport_SubscribeDuration(when0 uint64) (result0 uint32) diff --git a/src/internal/wasi/clocks/v0.2.0/wall-clock/empty.s b/src/internal/wasi/clocks/v0.2.0/wall-clock/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/clocks/v0.2.0/wall-clock/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go b/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go new file mode 100644 index 0000000000..a270f78d6d --- /dev/null +++ b/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go @@ -0,0 +1,80 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package wallclock represents the imported interface "wasi:clocks/wall-clock@0.2.0". +// +// WASI Wall Clock is a clock API intended to let users query the current +// time. The name "wall" makes an analogy to a "clock on the wall", which +// is not necessarily monotonic as it may be reset. +// +// It is intended to be portable at least between Unix-family platforms and +// Windows. +// +// A wall clock is a clock which measures the date and time according to +// some external reference. +// +// External references may be reset, so this clock is not necessarily +// monotonic, making it unsuitable for measuring elapsed time. +// +// It is intended for reporting the current date and time for humans. +package wallclock + +// DateTime represents the record "wasi:clocks/wall-clock@0.2.0#datetime". +// +// A time and date in seconds plus nanoseconds. +// +// record datetime { +// seconds: u64, +// nanoseconds: u32, +// } +type DateTime struct { + Seconds uint64 + Nanoseconds uint32 +} + +// Now represents the imported function "now". +// +// Read the current value of the clock. +// +// This clock is not monotonic, therefore calling this function repeatedly +// will not necessarily produce a sequence of non-decreasing values. +// +// The returned timestamps represent the number of seconds since +// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], +// also known as [Unix Time]. +// +// The nanoseconds field of the output is always less than 1000000000. +// +// now: func() -> datetime +// +// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 +// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time +// +//go:nosplit +func Now() (result DateTime) { + wasmimport_Now(&result) + return +} + +//go:wasmimport wasi:clocks/wall-clock@0.2.0 now +//go:noescape +func wasmimport_Now(result *DateTime) + +// Resolution represents the imported function "resolution". +// +// Query the resolution of the clock. +// +// The nanoseconds field of the output is always less than 1000000000. +// +// resolution: func() -> datetime +// +//go:nosplit +func Resolution() (result DateTime) { + wasmimport_Resolution(&result) + return +} + +//go:wasmimport wasi:clocks/wall-clock@0.2.0 resolution +//go:noescape +func wasmimport_Resolution(result *DateTime) diff --git a/src/internal/wasi/filesystem/v0.2.0/preopens/empty.s b/src/internal/wasi/filesystem/v0.2.0/preopens/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/filesystem/v0.2.0/preopens/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go new file mode 100644 index 0000000000..d0b71bf110 --- /dev/null +++ b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go @@ -0,0 +1,27 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package preopens represents the imported interface "wasi:filesystem/preopens@0.2.0". +package preopens + +import ( + "github.com/ydnar/wasm-tools-go/cm" + "internal/wasi/filesystem/v0.2.0/types" +) + +// GetDirectories represents the imported function "get-directories". +// +// Return the set of preopened directories, and their path. +// +// get-directories: func() -> list> +// +//go:nosplit +func GetDirectories() (result cm.List[cm.Tuple[types.Descriptor, string]]) { + wasmimport_GetDirectories(&result) + return +} + +//go:wasmimport wasi:filesystem/preopens@0.2.0 get-directories +//go:noescape +func wasmimport_GetDirectories(result *cm.List[cm.Tuple[types.Descriptor, string]]) diff --git a/src/internal/wasi/filesystem/v0.2.0/types/abi.go b/src/internal/wasi/filesystem/v0.2.0/types/abi.go new file mode 100644 index 0000000000..288ef2e227 --- /dev/null +++ b/src/internal/wasi/filesystem/v0.2.0/types/abi.go @@ -0,0 +1,27 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +package types + +import ( + "github.com/ydnar/wasm-tools-go/cm" + wallclock "internal/wasi/clocks/v0.2.0/wall-clock" +) + +func lower_DateTime(v wallclock.DateTime) (f0 uint64, f1 uint32) { + f0 = (uint64)(v.Seconds) + f1 = (uint32)(v.Nanoseconds) + return +} + +func lower_NewTimestamp(v NewTimestamp) (f0 uint32, f1 uint64, f2 uint32) { + f0 = (uint32)(cm.Tag(&v)) + switch f0 { + case 2: // timestamp + v1, v2 := lower_DateTime(*v.Timestamp()) + f1 = (uint64)(v1) + f2 = (uint32)(v2) + } + return +} diff --git a/src/internal/wasi/filesystem/v0.2.0/types/empty.s b/src/internal/wasi/filesystem/v0.2.0/types/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/filesystem/v0.2.0/types/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go b/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go new file mode 100644 index 0000000000..387426d1b6 --- /dev/null +++ b/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go @@ -0,0 +1,1284 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package types represents the imported interface "wasi:filesystem/types@0.2.0". +// +// WASI filesystem is a filesystem API primarily intended to let users run WASI +// programs that access their files on their existing filesystems, without +// significant overhead. +// +// It is intended to be roughly portable between Unix-family platforms and +// Windows, though it does not hide many of the major differences. +// +// Paths are passed as interface-type `string`s, meaning they must consist of +// a sequence of Unicode Scalar Values (USVs). Some filesystems may contain +// paths which are not accessible by this API. +// +// The directory separator in WASI is always the forward-slash (`/`). +// +// All paths in WASI are relative paths, and are interpreted relative to a +// `descriptor` referring to a base directory. If a `path` argument to any WASI +// function starts with `/`, or if any step of resolving a `path`, including +// `..` and symbolic link steps, reaches a directory outside of the base +// directory, or reaches a symlink to an absolute or rooted path in the +// underlying filesystem, the function fails with `error-code::not-permitted`. +// +// For more information about WASI path resolution and sandboxing, see +// [WASI filesystem path resolution]. +// +// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md +package types + +import ( + "github.com/ydnar/wasm-tools-go/cm" + wallclock "internal/wasi/clocks/v0.2.0/wall-clock" + ioerror "internal/wasi/io/v0.2.0/error" + "internal/wasi/io/v0.2.0/streams" +) + +// FileSize represents the u64 "wasi:filesystem/types@0.2.0#filesize". +// +// File size or length of a region within a file. +// +// type filesize = u64 +type FileSize uint64 + +// DescriptorType represents the enum "wasi:filesystem/types@0.2.0#descriptor-type". +// +// The type of a filesystem object referenced by a descriptor. +// +// Note: This was called `filetype` in earlier versions of WASI. +// +// enum descriptor-type { +// unknown, +// block-device, +// character-device, +// directory, +// fifo, +// symbolic-link, +// regular-file, +// socket +// } +type DescriptorType uint8 + +const ( + // The type of the descriptor or file is unknown or is different from + // any of the other types specified. + DescriptorTypeUnknown DescriptorType = iota + + // The descriptor refers to a block device inode. + DescriptorTypeBlockDevice + + // The descriptor refers to a character device inode. + DescriptorTypeCharacterDevice + + // The descriptor refers to a directory inode. + DescriptorTypeDirectory + + // The descriptor refers to a named pipe. + DescriptorTypeFIFO + + // The file refers to a symbolic link inode. + DescriptorTypeSymbolicLink + + // The descriptor refers to a regular file inode. + DescriptorTypeRegularFile + + // The descriptor refers to a socket. + DescriptorTypeSocket +) + +// DescriptorFlags represents the flags "wasi:filesystem/types@0.2.0#descriptor-flags". +// +// Descriptor flags. +// +// Note: This was called `fdflags` in earlier versions of WASI. +// +// flags descriptor-flags { +// read, +// write, +// file-integrity-sync, +// data-integrity-sync, +// requested-write-sync, +// mutate-directory, +// } +type DescriptorFlags uint8 + +const ( + // Read mode: Data can be read. + DescriptorFlagsRead DescriptorFlags = 1 << iota + + // Write mode: Data can be written to. + DescriptorFlagsWrite + + // Request that writes be performed according to synchronized I/O file + // integrity completion. The data stored in the file and the file's + // metadata are synchronized. This is similar to `O_SYNC` in POSIX. + // + // The precise semantics of this operation have not yet been defined for + // WASI. At this time, it should be interpreted as a request, and not a + // requirement. + DescriptorFlagsFileIntegritySync + + // Request that writes be performed according to synchronized I/O data + // integrity completion. Only the data stored in the file is + // synchronized. This is similar to `O_DSYNC` in POSIX. + // + // The precise semantics of this operation have not yet been defined for + // WASI. At this time, it should be interpreted as a request, and not a + // requirement. + DescriptorFlagsDataIntegritySync + + // Requests that reads be performed at the same level of integrety + // requested for writes. This is similar to `O_RSYNC` in POSIX. + // + // The precise semantics of this operation have not yet been defined for + // WASI. At this time, it should be interpreted as a request, and not a + // requirement. + DescriptorFlagsRequestedWriteSync + + // Mutating directories mode: Directory contents may be mutated. + // + // When this flag is unset on a descriptor, operations using the + // descriptor which would create, rename, delete, modify the data or + // metadata of filesystem objects, or obtain another handle which + // would permit any of those, shall fail with `error-code::read-only` if + // they would otherwise succeed. + // + // This may only be set on directories. + DescriptorFlagsMutateDirectory +) + +// PathFlags represents the flags "wasi:filesystem/types@0.2.0#path-flags". +// +// Flags determining the method of how paths are resolved. +// +// flags path-flags { +// symlink-follow, +// } +type PathFlags uint8 + +const ( + // As long as the resolved path corresponds to a symbolic link, it is + // expanded. + PathFlagsSymlinkFollow PathFlags = 1 << iota +) + +// OpenFlags represents the flags "wasi:filesystem/types@0.2.0#open-flags". +// +// Open flags used by `open-at`. +// +// flags open-flags { +// create, +// directory, +// exclusive, +// truncate, +// } +type OpenFlags uint8 + +const ( + // Create file if it does not exist, similar to `O_CREAT` in POSIX. + OpenFlagsCreate OpenFlags = 1 << iota + + // Fail if not a directory, similar to `O_DIRECTORY` in POSIX. + OpenFlagsDirectory + + // Fail if file already exists, similar to `O_EXCL` in POSIX. + OpenFlagsExclusive + + // Truncate file to size 0, similar to `O_TRUNC` in POSIX. + OpenFlagsTruncate +) + +// LinkCount represents the u64 "wasi:filesystem/types@0.2.0#link-count". +// +// Number of hard links to an inode. +// +// type link-count = u64 +type LinkCount uint64 + +// DescriptorStat represents the record "wasi:filesystem/types@0.2.0#descriptor-stat". +// +// File attributes. +// +// Note: This was called `filestat` in earlier versions of WASI. +// +// record descriptor-stat { +// %type: descriptor-type, +// link-count: link-count, +// size: filesize, +// data-access-timestamp: option, +// data-modification-timestamp: option, +// status-change-timestamp: option, +// } +type DescriptorStat struct { + // File type. + Type DescriptorType + + // Number of hard links to the file. + LinkCount LinkCount + + // For regular files, the file size in bytes. For symbolic links, the + // length in bytes of the pathname contained in the symbolic link. + Size FileSize + + // Last data access timestamp. + // + // If the `option` is none, the platform doesn't maintain an access + // timestamp for this file. + DataAccessTimestamp cm.Option[wallclock.DateTime] + + // Last data modification timestamp. + // + // If the `option` is none, the platform doesn't maintain a + // modification timestamp for this file. + DataModificationTimestamp cm.Option[wallclock.DateTime] + + // Last file status-change timestamp. + // + // If the `option` is none, the platform doesn't maintain a + // status-change timestamp for this file. + StatusChangeTimestamp cm.Option[wallclock.DateTime] +} + +// NewTimestamp represents the variant "wasi:filesystem/types@0.2.0#new-timestamp". +// +// When setting a timestamp, this gives the value to set it to. +// +// variant new-timestamp { +// no-change, +// now, +// timestamp(datetime), +// } +type NewTimestamp cm.Variant[uint8, wallclock.DateTime, wallclock.DateTime] + +// NewTimestampNoChange returns a [NewTimestamp] of case "no-change". +// +// Leave the timestamp set to its previous value. +func NewTimestampNoChange() NewTimestamp { + var data struct{} + return cm.New[NewTimestamp](0, data) +} + +// NoChange returns true if [NewTimestamp] represents the variant case "no-change". +func (self *NewTimestamp) NoChange() bool { + return cm.Tag(self) == 0 +} + +// NewTimestampNow returns a [NewTimestamp] of case "now". +// +// Set the timestamp to the current time of the system clock associated +// with the filesystem. +func NewTimestampNow() NewTimestamp { + var data struct{} + return cm.New[NewTimestamp](1, data) +} + +// Now returns true if [NewTimestamp] represents the variant case "now". +func (self *NewTimestamp) Now() bool { + return cm.Tag(self) == 1 +} + +// NewTimestampTimestamp returns a [NewTimestamp] of case "timestamp". +// +// Set the timestamp to the given value. +func NewTimestampTimestamp(data wallclock.DateTime) NewTimestamp { + return cm.New[NewTimestamp](2, data) +} + +// Timestamp returns a non-nil *[wallclock.DateTime] if [NewTimestamp] represents the variant case "timestamp". +func (self *NewTimestamp) Timestamp() *wallclock.DateTime { + return cm.Case[wallclock.DateTime](self, 2) +} + +// DirectoryEntry represents the record "wasi:filesystem/types@0.2.0#directory-entry". +// +// A directory entry. +// +// record directory-entry { +// %type: descriptor-type, +// name: string, +// } +type DirectoryEntry struct { + // The type of the file referred to by this directory entry. + Type DescriptorType + + // The name of the object. + Name string +} + +// ErrorCode represents the enum "wasi:filesystem/types@0.2.0#error-code". +// +// Error codes returned by functions, similar to `errno` in POSIX. +// Not all of these error codes are returned by the functions provided by this +// API; some are used in higher-level library layers, and others are provided +// merely for alignment with POSIX. +// +// enum error-code { +// access, +// would-block, +// already, +// bad-descriptor, +// busy, +// deadlock, +// quota, +// exist, +// file-too-large, +// illegal-byte-sequence, +// in-progress, +// interrupted, +// invalid, +// io, +// is-directory, +// loop, +// too-many-links, +// message-size, +// name-too-long, +// no-device, +// no-entry, +// no-lock, +// insufficient-memory, +// insufficient-space, +// not-directory, +// not-empty, +// not-recoverable, +// unsupported, +// no-tty, +// no-such-device, +// overflow, +// not-permitted, +// pipe, +// read-only, +// invalid-seek, +// text-file-busy, +// cross-device +// } +type ErrorCode uint8 + +const ( + // Permission denied, similar to `EACCES` in POSIX. + ErrorCodeAccess ErrorCode = iota + + // Resource unavailable, or operation would block, similar to `EAGAIN` and `EWOULDBLOCK` + // in POSIX. + ErrorCodeWouldBlock + + // Connection already in progress, similar to `EALREADY` in POSIX. + ErrorCodeAlready + + // Bad descriptor, similar to `EBADF` in POSIX. + ErrorCodeBadDescriptor + + // Device or resource busy, similar to `EBUSY` in POSIX. + ErrorCodeBusy + + // Resource deadlock would occur, similar to `EDEADLK` in POSIX. + ErrorCodeDeadlock + + // Storage quota exceeded, similar to `EDQUOT` in POSIX. + ErrorCodeQuota + + // File exists, similar to `EEXIST` in POSIX. + ErrorCodeExist + + // File too large, similar to `EFBIG` in POSIX. + ErrorCodeFileTooLarge + + // Illegal byte sequence, similar to `EILSEQ` in POSIX. + ErrorCodeIllegalByteSequence + + // Operation in progress, similar to `EINPROGRESS` in POSIX. + ErrorCodeInProgress + + // Interrupted function, similar to `EINTR` in POSIX. + ErrorCodeInterrupted + + // Invalid argument, similar to `EINVAL` in POSIX. + ErrorCodeInvalid + + // I/O error, similar to `EIO` in POSIX. + ErrorCodeIO + + // Is a directory, similar to `EISDIR` in POSIX. + ErrorCodeIsDirectory + + // Too many levels of symbolic links, similar to `ELOOP` in POSIX. + ErrorCodeLoop + + // Too many links, similar to `EMLINK` in POSIX. + ErrorCodeTooManyLinks + + // Message too large, similar to `EMSGSIZE` in POSIX. + ErrorCodeMessageSize + + // Filename too long, similar to `ENAMETOOLONG` in POSIX. + ErrorCodeNameTooLong + + // No such device, similar to `ENODEV` in POSIX. + ErrorCodeNoDevice + + // No such file or directory, similar to `ENOENT` in POSIX. + ErrorCodeNoEntry + + // No locks available, similar to `ENOLCK` in POSIX. + ErrorCodeNoLock + + // Not enough space, similar to `ENOMEM` in POSIX. + ErrorCodeInsufficientMemory + + // No space left on device, similar to `ENOSPC` in POSIX. + ErrorCodeInsufficientSpace + + // Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX. + ErrorCodeNotDirectory + + // Directory not empty, similar to `ENOTEMPTY` in POSIX. + ErrorCodeNotEmpty + + // State not recoverable, similar to `ENOTRECOVERABLE` in POSIX. + ErrorCodeNotRecoverable + + // Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX. + ErrorCodeUnsupported + + // Inappropriate I/O control operation, similar to `ENOTTY` in POSIX. + ErrorCodeNoTTY + + // No such device or address, similar to `ENXIO` in POSIX. + ErrorCodeNoSuchDevice + + // Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX. + ErrorCodeOverflow + + // Operation not permitted, similar to `EPERM` in POSIX. + ErrorCodeNotPermitted + + // Broken pipe, similar to `EPIPE` in POSIX. + ErrorCodePipe + + // Read-only file system, similar to `EROFS` in POSIX. + ErrorCodeReadOnly + + // Invalid seek, similar to `ESPIPE` in POSIX. + ErrorCodeInvalidSeek + + // Text file busy, similar to `ETXTBSY` in POSIX. + ErrorCodeTextFileBusy + + // Cross-device link, similar to `EXDEV` in POSIX. + ErrorCodeCrossDevice +) + +// Advice represents the enum "wasi:filesystem/types@0.2.0#advice". +// +// File or memory access pattern advisory information. +// +// enum advice { +// normal, +// sequential, +// random, +// will-need, +// dont-need, +// no-reuse +// } +type Advice uint8 + +const ( + // The application has no advice to give on its behavior with respect + // to the specified data. + AdviceNormal Advice = iota + + // The application expects to access the specified data sequentially + // from lower offsets to higher offsets. + AdviceSequential + + // The application expects to access the specified data in a random + // order. + AdviceRandom + + // The application expects to access the specified data in the near + // future. + AdviceWillNeed + + // The application expects that it will not access the specified data + // in the near future. + AdviceDontNeed + + // The application expects to access the specified data once and then + // not reuse it thereafter. + AdviceNoReuse +) + +// MetadataHashValue represents the record "wasi:filesystem/types@0.2.0#metadata-hash-value". +// +// A 128-bit hash value, split into parts because wasm doesn't have a +// 128-bit integer type. +// +// record metadata-hash-value { +// lower: u64, +// upper: u64, +// } +type MetadataHashValue struct { + // 64 bits of a 128-bit hash value. + Lower uint64 + + // Another 64 bits of a 128-bit hash value. + Upper uint64 +} + +// Descriptor represents the imported resource "wasi:filesystem/types@0.2.0#descriptor". +// +// A descriptor is a reference to a filesystem object, which may be a file, +// directory, named pipe, special file, or other object on which filesystem +// calls may be made. +// +// resource descriptor +type Descriptor cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "descriptor". +// +// Drops a resource handle. +// +//go:nosplit +func (self Descriptor) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_DescriptorResourceDrop((uint32)(self0)) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [resource-drop]descriptor +//go:noescape +func wasmimport_DescriptorResourceDrop(self0 uint32) + +// Advise represents the imported method "advise". +// +// Provide file advisory information on a descriptor. +// +// This is similar to `posix_fadvise` in POSIX. +// +// advise: func(offset: filesize, length: filesize, advice: advice) -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) Advise(offset FileSize, length FileSize, advice Advice) (result cm.ErrResult[struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + offset0 := (uint64)(offset) + length0 := (uint64)(length) + advice0 := (uint32)(advice) + wasmimport_DescriptorAdvise((uint32)(self0), (uint64)(offset0), (uint64)(length0), (uint32)(advice0), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.advise +//go:noescape +func wasmimport_DescriptorAdvise(self0 uint32, offset0 uint64, length0 uint64, advice0 uint32, result *cm.ErrResult[struct{}, ErrorCode]) + +// AppendViaStream represents the imported method "append-via-stream". +// +// Return a stream for appending to a file, if available. +// +// May fail with an error-code describing why the file cannot be appended. +// +// Note: This allows using `write-stream`, which is similar to `write` with +// `O_APPEND` in in POSIX. +// +// append-via-stream: func() -> result +// +//go:nosplit +func (self Descriptor) AppendViaStream() (result cm.OKResult[streams.OutputStream, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_DescriptorAppendViaStream((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.append-via-stream +//go:noescape +func wasmimport_DescriptorAppendViaStream(self0 uint32, result *cm.OKResult[streams.OutputStream, ErrorCode]) + +// CreateDirectoryAt represents the imported method "create-directory-at". +// +// Create a directory. +// +// Note: This is similar to `mkdirat` in POSIX. +// +// create-directory-at: func(path: string) -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) CreateDirectoryAt(path string) (result cm.ErrResult[struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + path0, path1 := cm.LowerString(path) + wasmimport_DescriptorCreateDirectoryAt((uint32)(self0), (*uint8)(path0), (uint32)(path1), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.create-directory-at +//go:noescape +func wasmimport_DescriptorCreateDirectoryAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.ErrResult[struct{}, ErrorCode]) + +// GetFlags represents the imported method "get-flags". +// +// Get flags associated with a descriptor. +// +// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. +// +// Note: This returns the value that was the `fs_flags` value returned +// from `fdstat_get` in earlier versions of WASI. +// +// get-flags: func() -> result +// +//go:nosplit +func (self Descriptor) GetFlags() (result cm.OKResult[DescriptorFlags, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_DescriptorGetFlags((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.get-flags +//go:noescape +func wasmimport_DescriptorGetFlags(self0 uint32, result *cm.OKResult[DescriptorFlags, ErrorCode]) + +// GetType represents the imported method "get-type". +// +// Get the dynamic type of a descriptor. +// +// Note: This returns the same value as the `type` field of the `fd-stat` +// returned by `stat`, `stat-at` and similar. +// +// Note: This returns similar flags to the `st_mode & S_IFMT` value provided +// by `fstat` in POSIX. +// +// Note: This returns the value that was the `fs_filetype` value returned +// from `fdstat_get` in earlier versions of WASI. +// +// get-type: func() -> result +// +//go:nosplit +func (self Descriptor) GetType() (result cm.OKResult[DescriptorType, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_DescriptorGetType((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.get-type +//go:noescape +func wasmimport_DescriptorGetType(self0 uint32, result *cm.OKResult[DescriptorType, ErrorCode]) + +// IsSameObject represents the imported method "is-same-object". +// +// Test whether two descriptors refer to the same filesystem object. +// +// In POSIX, this corresponds to testing whether the two descriptors have the +// same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers. +// wasi-filesystem does not expose device and inode numbers, so this function +// may be used instead. +// +// is-same-object: func(other: borrow) -> bool +// +//go:nosplit +func (self Descriptor) IsSameObject(other Descriptor) (result bool) { + self0 := cm.Reinterpret[uint32](self) + other0 := cm.Reinterpret[uint32](other) + result0 := wasmimport_DescriptorIsSameObject((uint32)(self0), (uint32)(other0)) + result = cm.U32ToBool((uint32)(result0)) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.is-same-object +//go:noescape +func wasmimport_DescriptorIsSameObject(self0 uint32, other0 uint32) (result0 uint32) + +// LinkAt represents the imported method "link-at". +// +// Create a hard link. +// +// Note: This is similar to `linkat` in POSIX. +// +// link-at: func(old-path-flags: path-flags, old-path: string, new-descriptor: borrow, +// new-path: string) -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) LinkAt(oldPathFlags PathFlags, oldPath string, newDescriptor Descriptor, newPath string) (result cm.ErrResult[struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + oldPathFlags0 := (uint32)(oldPathFlags) + oldPath0, oldPath1 := cm.LowerString(oldPath) + newDescriptor0 := cm.Reinterpret[uint32](newDescriptor) + newPath0, newPath1 := cm.LowerString(newPath) + wasmimport_DescriptorLinkAt((uint32)(self0), (uint32)(oldPathFlags0), (*uint8)(oldPath0), (uint32)(oldPath1), (uint32)(newDescriptor0), (*uint8)(newPath0), (uint32)(newPath1), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.link-at +//go:noescape +func wasmimport_DescriptorLinkAt(self0 uint32, oldPathFlags0 uint32, oldPath0 *uint8, oldPath1 uint32, newDescriptor0 uint32, newPath0 *uint8, newPath1 uint32, result *cm.ErrResult[struct{}, ErrorCode]) + +// MetadataHash represents the imported method "metadata-hash". +// +// Return a hash of the metadata associated with a filesystem object referred +// to by a descriptor. +// +// This returns a hash of the last-modification timestamp and file size, and +// may also include the inode number, device number, birth timestamp, and +// other metadata fields that may change when the file is modified or +// replaced. It may also include a secret value chosen by the +// implementation and not otherwise exposed. +// +// Implementations are encourated to provide the following properties: +// +// - If the file is not modified or replaced, the computed hash value should +// usually not change. +// - If the object is modified or replaced, the computed hash value should +// usually change. +// - The inputs to the hash should not be easily computable from the +// computed hash. +// +// However, none of these is required. +// +// metadata-hash: func() -> result +// +//go:nosplit +func (self Descriptor) MetadataHash() (result cm.OKResult[MetadataHashValue, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_DescriptorMetadataHash((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.metadata-hash +//go:noescape +func wasmimport_DescriptorMetadataHash(self0 uint32, result *cm.OKResult[MetadataHashValue, ErrorCode]) + +// MetadataHashAt represents the imported method "metadata-hash-at". +// +// Return a hash of the metadata associated with a filesystem object referred +// to by a directory descriptor and a relative path. +// +// This performs the same hash computation as `metadata-hash`. +// +// metadata-hash-at: func(path-flags: path-flags, path: string) -> result +// +//go:nosplit +func (self Descriptor) MetadataHashAt(pathFlags PathFlags, path string) (result cm.OKResult[MetadataHashValue, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + pathFlags0 := (uint32)(pathFlags) + path0, path1 := cm.LowerString(path) + wasmimport_DescriptorMetadataHashAt((uint32)(self0), (uint32)(pathFlags0), (*uint8)(path0), (uint32)(path1), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.metadata-hash-at +//go:noescape +func wasmimport_DescriptorMetadataHashAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, result *cm.OKResult[MetadataHashValue, ErrorCode]) + +// OpenAt represents the imported method "open-at". +// +// Open a file or directory. +// +// The returned descriptor is not guaranteed to be the lowest-numbered +// descriptor not currently open/ it is randomized to prevent applications +// from depending on making assumptions about indexes, since this is +// error-prone in multi-threaded contexts. The returned descriptor is +// guaranteed to be less than 2**31. +// +// If `flags` contains `descriptor-flags::mutate-directory`, and the base +// descriptor doesn't have `descriptor-flags::mutate-directory` set, +// `open-at` fails with `error-code::read-only`. +// +// If `flags` contains `write` or `mutate-directory`, or `open-flags` +// contains `truncate` or `create`, and the base descriptor doesn't have +// `descriptor-flags::mutate-directory` set, `open-at` fails with +// `error-code::read-only`. +// +// Note: This is similar to `openat` in POSIX. +// +// open-at: func(path-flags: path-flags, path: string, open-flags: open-flags, %flags: +// descriptor-flags) -> result +// +//go:nosplit +func (self Descriptor) OpenAt(pathFlags PathFlags, path string, openFlags OpenFlags, flags DescriptorFlags) (result cm.OKResult[Descriptor, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + pathFlags0 := (uint32)(pathFlags) + path0, path1 := cm.LowerString(path) + openFlags0 := (uint32)(openFlags) + flags0 := (uint32)(flags) + wasmimport_DescriptorOpenAt((uint32)(self0), (uint32)(pathFlags0), (*uint8)(path0), (uint32)(path1), (uint32)(openFlags0), (uint32)(flags0), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.open-at +//go:noescape +func wasmimport_DescriptorOpenAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, openFlags0 uint32, flags0 uint32, result *cm.OKResult[Descriptor, ErrorCode]) + +// Read represents the imported method "read". +// +// Read from a descriptor, without using and updating the descriptor's offset. +// +// This function returns a list of bytes containing the data that was +// read, along with a bool which, when true, indicates that the end of the +// file was reached. The returned list will contain up to `length` bytes; it +// may return fewer than requested, if the end of the file is reached or +// if the I/O operation is interrupted. +// +// In the future, this may change to return a `stream`. +// +// Note: This is similar to `pread` in POSIX. +// +// read: func(length: filesize, offset: filesize) -> result, bool>, +// error-code> +// +//go:nosplit +func (self Descriptor) Read(length FileSize, offset FileSize) (result cm.OKResult[cm.Tuple[cm.List[uint8], bool], ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + length0 := (uint64)(length) + offset0 := (uint64)(offset) + wasmimport_DescriptorRead((uint32)(self0), (uint64)(length0), (uint64)(offset0), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.read +//go:noescape +func wasmimport_DescriptorRead(self0 uint32, length0 uint64, offset0 uint64, result *cm.OKResult[cm.Tuple[cm.List[uint8], bool], ErrorCode]) + +// ReadDirectory represents the imported method "read-directory". +// +// Read directory entries from a directory. +// +// On filesystems where directories contain entries referring to themselves +// and their parents, often named `.` and `..` respectively, these entries +// are omitted. +// +// This always returns a new stream which starts at the beginning of the +// directory. Multiple streams may be active on the same directory, and they +// do not interfere with each other. +// +// read-directory: func() -> result +// +//go:nosplit +func (self Descriptor) ReadDirectory() (result cm.OKResult[DirectoryEntryStream, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_DescriptorReadDirectory((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.read-directory +//go:noescape +func wasmimport_DescriptorReadDirectory(self0 uint32, result *cm.OKResult[DirectoryEntryStream, ErrorCode]) + +// ReadViaStream represents the imported method "read-via-stream". +// +// Return a stream for reading from a file, if available. +// +// May fail with an error-code describing why the file cannot be read. +// +// Multiple read, write, and append streams may be active on the same open +// file and they do not interfere with each other. +// +// Note: This allows using `read-stream`, which is similar to `read` in POSIX. +// +// read-via-stream: func(offset: filesize) -> result +// +//go:nosplit +func (self Descriptor) ReadViaStream(offset FileSize) (result cm.OKResult[streams.InputStream, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + offset0 := (uint64)(offset) + wasmimport_DescriptorReadViaStream((uint32)(self0), (uint64)(offset0), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.read-via-stream +//go:noescape +func wasmimport_DescriptorReadViaStream(self0 uint32, offset0 uint64, result *cm.OKResult[streams.InputStream, ErrorCode]) + +// ReadLinkAt represents the imported method "readlink-at". +// +// Read the contents of a symbolic link. +// +// If the contents contain an absolute or rooted path in the underlying +// filesystem, this function fails with `error-code::not-permitted`. +// +// Note: This is similar to `readlinkat` in POSIX. +// +// readlink-at: func(path: string) -> result +// +//go:nosplit +func (self Descriptor) ReadLinkAt(path string) (result cm.OKResult[string, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + path0, path1 := cm.LowerString(path) + wasmimport_DescriptorReadLinkAt((uint32)(self0), (*uint8)(path0), (uint32)(path1), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.readlink-at +//go:noescape +func wasmimport_DescriptorReadLinkAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.OKResult[string, ErrorCode]) + +// RemoveDirectoryAt represents the imported method "remove-directory-at". +// +// Remove a directory. +// +// Return `error-code::not-empty` if the directory is not empty. +// +// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. +// +// remove-directory-at: func(path: string) -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) RemoveDirectoryAt(path string) (result cm.ErrResult[struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + path0, path1 := cm.LowerString(path) + wasmimport_DescriptorRemoveDirectoryAt((uint32)(self0), (*uint8)(path0), (uint32)(path1), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.remove-directory-at +//go:noescape +func wasmimport_DescriptorRemoveDirectoryAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.ErrResult[struct{}, ErrorCode]) + +// RenameAt represents the imported method "rename-at". +// +// Rename a filesystem object. +// +// Note: This is similar to `renameat` in POSIX. +// +// rename-at: func(old-path: string, new-descriptor: borrow, new-path: +// string) -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) RenameAt(oldPath string, newDescriptor Descriptor, newPath string) (result cm.ErrResult[struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + oldPath0, oldPath1 := cm.LowerString(oldPath) + newDescriptor0 := cm.Reinterpret[uint32](newDescriptor) + newPath0, newPath1 := cm.LowerString(newPath) + wasmimport_DescriptorRenameAt((uint32)(self0), (*uint8)(oldPath0), (uint32)(oldPath1), (uint32)(newDescriptor0), (*uint8)(newPath0), (uint32)(newPath1), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.rename-at +//go:noescape +func wasmimport_DescriptorRenameAt(self0 uint32, oldPath0 *uint8, oldPath1 uint32, newDescriptor0 uint32, newPath0 *uint8, newPath1 uint32, result *cm.ErrResult[struct{}, ErrorCode]) + +// SetSize represents the imported method "set-size". +// +// Adjust the size of an open file. If this increases the file's size, the +// extra bytes are filled with zeros. +// +// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. +// +// set-size: func(size: filesize) -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) SetSize(size FileSize) (result cm.ErrResult[struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + size0 := (uint64)(size) + wasmimport_DescriptorSetSize((uint32)(self0), (uint64)(size0), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.set-size +//go:noescape +func wasmimport_DescriptorSetSize(self0 uint32, size0 uint64, result *cm.ErrResult[struct{}, ErrorCode]) + +// SetTimes represents the imported method "set-times". +// +// Adjust the timestamps of an open file or directory. +// +// Note: This is similar to `futimens` in POSIX. +// +// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. +// +// set-times: func(data-access-timestamp: new-timestamp, data-modification-timestamp: +// new-timestamp) -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) SetTimes(dataAccessTimestamp NewTimestamp, dataModificationTimestamp NewTimestamp) (result cm.ErrResult[struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + dataAccessTimestamp0, dataAccessTimestamp1, dataAccessTimestamp2 := lower_NewTimestamp(dataAccessTimestamp) + dataModificationTimestamp0, dataModificationTimestamp1, dataModificationTimestamp2 := lower_NewTimestamp(dataModificationTimestamp) + wasmimport_DescriptorSetTimes((uint32)(self0), (uint32)(dataAccessTimestamp0), (uint64)(dataAccessTimestamp1), (uint32)(dataAccessTimestamp2), (uint32)(dataModificationTimestamp0), (uint64)(dataModificationTimestamp1), (uint32)(dataModificationTimestamp2), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.set-times +//go:noescape +func wasmimport_DescriptorSetTimes(self0 uint32, dataAccessTimestamp0 uint32, dataAccessTimestamp1 uint64, dataAccessTimestamp2 uint32, dataModificationTimestamp0 uint32, dataModificationTimestamp1 uint64, dataModificationTimestamp2 uint32, result *cm.ErrResult[struct{}, ErrorCode]) + +// SetTimesAt represents the imported method "set-times-at". +// +// Adjust the timestamps of a file or directory. +// +// Note: This is similar to `utimensat` in POSIX. +// +// Note: This was called `path_filestat_set_times` in earlier versions of +// WASI. +// +// set-times-at: func(path-flags: path-flags, path: string, data-access-timestamp: +// new-timestamp, data-modification-timestamp: new-timestamp) -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) SetTimesAt(pathFlags PathFlags, path string, dataAccessTimestamp NewTimestamp, dataModificationTimestamp NewTimestamp) (result cm.ErrResult[struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + pathFlags0 := (uint32)(pathFlags) + path0, path1 := cm.LowerString(path) + dataAccessTimestamp0, dataAccessTimestamp1, dataAccessTimestamp2 := lower_NewTimestamp(dataAccessTimestamp) + dataModificationTimestamp0, dataModificationTimestamp1, dataModificationTimestamp2 := lower_NewTimestamp(dataModificationTimestamp) + wasmimport_DescriptorSetTimesAt((uint32)(self0), (uint32)(pathFlags0), (*uint8)(path0), (uint32)(path1), (uint32)(dataAccessTimestamp0), (uint64)(dataAccessTimestamp1), (uint32)(dataAccessTimestamp2), (uint32)(dataModificationTimestamp0), (uint64)(dataModificationTimestamp1), (uint32)(dataModificationTimestamp2), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.set-times-at +//go:noescape +func wasmimport_DescriptorSetTimesAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, dataAccessTimestamp0 uint32, dataAccessTimestamp1 uint64, dataAccessTimestamp2 uint32, dataModificationTimestamp0 uint32, dataModificationTimestamp1 uint64, dataModificationTimestamp2 uint32, result *cm.ErrResult[struct{}, ErrorCode]) + +// Stat represents the imported method "stat". +// +// Return the attributes of an open file or directory. +// +// Note: This is similar to `fstat` in POSIX, except that it does not return +// device and inode information. For testing whether two descriptors refer to +// the same underlying filesystem object, use `is-same-object`. To obtain +// additional data that can be used do determine whether a file has been +// modified, use `metadata-hash`. +// +// Note: This was called `fd_filestat_get` in earlier versions of WASI. +// +// stat: func() -> result +// +//go:nosplit +func (self Descriptor) Stat() (result cm.OKResult[DescriptorStat, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_DescriptorStat((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.stat +//go:noescape +func wasmimport_DescriptorStat(self0 uint32, result *cm.OKResult[DescriptorStat, ErrorCode]) + +// StatAt represents the imported method "stat-at". +// +// Return the attributes of a file or directory. +// +// Note: This is similar to `fstatat` in POSIX, except that it does not +// return device and inode information. See the `stat` description for a +// discussion of alternatives. +// +// Note: This was called `path_filestat_get` in earlier versions of WASI. +// +// stat-at: func(path-flags: path-flags, path: string) -> result +// +//go:nosplit +func (self Descriptor) StatAt(pathFlags PathFlags, path string) (result cm.OKResult[DescriptorStat, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + pathFlags0 := (uint32)(pathFlags) + path0, path1 := cm.LowerString(path) + wasmimport_DescriptorStatAt((uint32)(self0), (uint32)(pathFlags0), (*uint8)(path0), (uint32)(path1), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.stat-at +//go:noescape +func wasmimport_DescriptorStatAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, result *cm.OKResult[DescriptorStat, ErrorCode]) + +// SymlinkAt represents the imported method "symlink-at". +// +// Create a symbolic link (also known as a "symlink"). +// +// If `old-path` starts with `/`, the function fails with +// `error-code::not-permitted`. +// +// Note: This is similar to `symlinkat` in POSIX. +// +// symlink-at: func(old-path: string, new-path: string) -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) SymlinkAt(oldPath string, newPath string) (result cm.ErrResult[struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + oldPath0, oldPath1 := cm.LowerString(oldPath) + newPath0, newPath1 := cm.LowerString(newPath) + wasmimport_DescriptorSymlinkAt((uint32)(self0), (*uint8)(oldPath0), (uint32)(oldPath1), (*uint8)(newPath0), (uint32)(newPath1), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.symlink-at +//go:noescape +func wasmimport_DescriptorSymlinkAt(self0 uint32, oldPath0 *uint8, oldPath1 uint32, newPath0 *uint8, newPath1 uint32, result *cm.ErrResult[struct{}, ErrorCode]) + +// Sync represents the imported method "sync". +// +// Synchronize the data and metadata of a file to disk. +// +// This function succeeds with no effect if the file descriptor is not +// opened for writing. +// +// Note: This is similar to `fsync` in POSIX. +// +// sync: func() -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) Sync() (result cm.ErrResult[struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_DescriptorSync((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.sync +//go:noescape +func wasmimport_DescriptorSync(self0 uint32, result *cm.ErrResult[struct{}, ErrorCode]) + +// SyncData represents the imported method "sync-data". +// +// Synchronize the data of a file to disk. +// +// This function succeeds with no effect if the file descriptor is not +// opened for writing. +// +// Note: This is similar to `fdatasync` in POSIX. +// +// sync-data: func() -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) SyncData() (result cm.ErrResult[struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_DescriptorSyncData((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.sync-data +//go:noescape +func wasmimport_DescriptorSyncData(self0 uint32, result *cm.ErrResult[struct{}, ErrorCode]) + +// UnlinkFileAt represents the imported method "unlink-file-at". +// +// Unlink a filesystem object that is not a directory. +// +// Return `error-code::is-directory` if the path refers to a directory. +// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. +// +// unlink-file-at: func(path: string) -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) UnlinkFileAt(path string) (result cm.ErrResult[struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + path0, path1 := cm.LowerString(path) + wasmimport_DescriptorUnlinkFileAt((uint32)(self0), (*uint8)(path0), (uint32)(path1), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.unlink-file-at +//go:noescape +func wasmimport_DescriptorUnlinkFileAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.ErrResult[struct{}, ErrorCode]) + +// Write represents the imported method "write". +// +// Write to a descriptor, without using and updating the descriptor's offset. +// +// It is valid to write past the end of a file; the file is extended to the +// extent of the write, with bytes between the previous end and the start of +// the write set to zero. +// +// In the future, this may change to take a `stream`. +// +// Note: This is similar to `pwrite` in POSIX. +// +// write: func(buffer: list, offset: filesize) -> result +// +//go:nosplit +func (self Descriptor) Write(buffer cm.List[uint8], offset FileSize) (result cm.OKResult[FileSize, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + buffer0, buffer1 := cm.LowerList(buffer) + offset0 := (uint64)(offset) + wasmimport_DescriptorWrite((uint32)(self0), (*uint8)(buffer0), (uint32)(buffer1), (uint64)(offset0), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.write +//go:noescape +func wasmimport_DescriptorWrite(self0 uint32, buffer0 *uint8, buffer1 uint32, offset0 uint64, result *cm.OKResult[FileSize, ErrorCode]) + +// WriteViaStream represents the imported method "write-via-stream". +// +// Return a stream for writing to a file, if available. +// +// May fail with an error-code describing why the file cannot be written. +// +// Note: This allows using `write-stream`, which is similar to `write` in +// POSIX. +// +// write-via-stream: func(offset: filesize) -> result +// +//go:nosplit +func (self Descriptor) WriteViaStream(offset FileSize) (result cm.OKResult[streams.OutputStream, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + offset0 := (uint64)(offset) + wasmimport_DescriptorWriteViaStream((uint32)(self0), (uint64)(offset0), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.write-via-stream +//go:noescape +func wasmimport_DescriptorWriteViaStream(self0 uint32, offset0 uint64, result *cm.OKResult[streams.OutputStream, ErrorCode]) + +// DirectoryEntryStream represents the imported resource "wasi:filesystem/types@0.2.0#directory-entry-stream". +// +// A stream of directory entries. +// +// resource directory-entry-stream +type DirectoryEntryStream cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "directory-entry-stream". +// +// Drops a resource handle. +// +//go:nosplit +func (self DirectoryEntryStream) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_DirectoryEntryStreamResourceDrop((uint32)(self0)) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [resource-drop]directory-entry-stream +//go:noescape +func wasmimport_DirectoryEntryStreamResourceDrop(self0 uint32) + +// ReadDirectoryEntry represents the imported method "read-directory-entry". +// +// Read a single directory entry from a `directory-entry-stream`. +// +// read-directory-entry: func() -> result, error-code> +// +//go:nosplit +func (self DirectoryEntryStream) ReadDirectoryEntry() (result cm.OKResult[cm.Option[DirectoryEntry], ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_DirectoryEntryStreamReadDirectoryEntry((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]directory-entry-stream.read-directory-entry +//go:noescape +func wasmimport_DirectoryEntryStreamReadDirectoryEntry(self0 uint32, result *cm.OKResult[cm.Option[DirectoryEntry], ErrorCode]) + +// FilesystemErrorCode represents the imported function "filesystem-error-code". +// +// Attempts to extract a filesystem-related `error-code` from the stream +// `error` provided. +// +// Stream operations which return `stream-error::last-operation-failed` +// have a payload with more information about the operation that failed. +// This payload can be passed through to this function to see if there's +// filesystem-related information about the error to return. +// +// Note that this function is fallible because not all stream-related +// errors are filesystem-related errors. +// +// filesystem-error-code: func(err: borrow) -> option +// +//go:nosplit +func FilesystemErrorCode(err ioerror.Error) (result cm.Option[ErrorCode]) { + err0 := cm.Reinterpret[uint32](err) + wasmimport_FilesystemErrorCode((uint32)(err0), &result) + return +} + +//go:wasmimport wasi:filesystem/types@0.2.0 filesystem-error-code +//go:noescape +func wasmimport_FilesystemErrorCode(err0 uint32, result *cm.Option[ErrorCode]) diff --git a/src/internal/wasi/io/v0.2.0/error/empty.s b/src/internal/wasi/io/v0.2.0/error/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/io/v0.2.0/error/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/io/v0.2.0/error/error.wit.go b/src/internal/wasi/io/v0.2.0/error/error.wit.go new file mode 100644 index 0000000000..f962f22ea3 --- /dev/null +++ b/src/internal/wasi/io/v0.2.0/error/error.wit.go @@ -0,0 +1,73 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package ioerror represents the imported interface "wasi:io/error@0.2.0". +package ioerror + +import ( + "github.com/ydnar/wasm-tools-go/cm" +) + +// Error represents the imported resource "wasi:io/error@0.2.0#error". +// +// A resource which represents some error information. +// +// The only method provided by this resource is `to-debug-string`, +// which provides some human-readable information about the error. +// +// In the `wasi:io` package, this resource is returned through the +// `wasi:io/streams/stream-error` type. +// +// To provide more specific error information, other interfaces may +// provide functions to further "downcast" this error into more specific +// error information. For example, `error`s returned in streams derived +// from filesystem types to be described using the filesystem's own +// error-code type, using the function +// `wasi:filesystem/types/filesystem-error-code`, which takes a parameter +// `borrow` and returns +// `option`. +// +// The set of functions which can "downcast" an `error` into a more +// concrete type is open. +// +// resource error +type Error cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "error". +// +// Drops a resource handle. +// +//go:nosplit +func (self Error) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_ErrorResourceDrop((uint32)(self0)) + return +} + +//go:wasmimport wasi:io/error@0.2.0 [resource-drop]error +//go:noescape +func wasmimport_ErrorResourceDrop(self0 uint32) + +// ToDebugString represents the imported method "to-debug-string". +// +// Returns a string that is suitable to assist humans in debugging +// this error. +// +// WARNING: The returned string should not be consumed mechanically! +// It may change across platforms, hosts, or other implementation +// details. Parsing this string is a major platform-compatibility +// hazard. +// +// to-debug-string: func() -> string +// +//go:nosplit +func (self Error) ToDebugString() (result string) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_ErrorToDebugString((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:io/error@0.2.0 [method]error.to-debug-string +//go:noescape +func wasmimport_ErrorToDebugString(self0 uint32, result *string) diff --git a/src/internal/wasi/io/v0.2.0/poll/empty.s b/src/internal/wasi/io/v0.2.0/poll/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/io/v0.2.0/poll/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/io/v0.2.0/poll/poll.wit.go b/src/internal/wasi/io/v0.2.0/poll/poll.wit.go new file mode 100644 index 0000000000..0c362c0791 --- /dev/null +++ b/src/internal/wasi/io/v0.2.0/poll/poll.wit.go @@ -0,0 +1,110 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package poll represents the imported interface "wasi:io/poll@0.2.0". +// +// A poll API intended to let users wait for I/O events on multiple handles +// at once. +package poll + +import ( + "github.com/ydnar/wasm-tools-go/cm" +) + +// Pollable represents the imported resource "wasi:io/poll@0.2.0#pollable". +// +// `pollable` represents a single I/O event which may be ready, or not. +// +// resource pollable +type Pollable cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "pollable". +// +// Drops a resource handle. +// +//go:nosplit +func (self Pollable) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_PollableResourceDrop((uint32)(self0)) + return +} + +//go:wasmimport wasi:io/poll@0.2.0 [resource-drop]pollable +//go:noescape +func wasmimport_PollableResourceDrop(self0 uint32) + +// Block represents the imported method "block". +// +// `block` returns immediately if the pollable is ready, and otherwise +// blocks until ready. +// +// This function is equivalent to calling `poll.poll` on a list +// containing only this pollable. +// +// block: func() +// +//go:nosplit +func (self Pollable) Block() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_PollableBlock((uint32)(self0)) + return +} + +//go:wasmimport wasi:io/poll@0.2.0 [method]pollable.block +//go:noescape +func wasmimport_PollableBlock(self0 uint32) + +// Ready represents the imported method "ready". +// +// Return the readiness of a pollable. This function never blocks. +// +// Returns `true` when the pollable is ready, and `false` otherwise. +// +// ready: func() -> bool +// +//go:nosplit +func (self Pollable) Ready() (result bool) { + self0 := cm.Reinterpret[uint32](self) + result0 := wasmimport_PollableReady((uint32)(self0)) + result = cm.U32ToBool((uint32)(result0)) + return +} + +//go:wasmimport wasi:io/poll@0.2.0 [method]pollable.ready +//go:noescape +func wasmimport_PollableReady(self0 uint32) (result0 uint32) + +// Poll represents the imported function "poll". +// +// Poll for completion on a set of pollables. +// +// This function takes a list of pollables, which identify I/O sources of +// interest, and waits until one or more of the events is ready for I/O. +// +// The result `list` contains one or more indices of handles in the +// argument list that is ready for I/O. +// +// If the list contains more elements than can be indexed with a `u32` +// value, this function traps. +// +// A timeout can be implemented by adding a pollable from the +// wasi-clocks API to the list. +// +// This function does not return a `result`; polling in itself does not +// do any I/O so it doesn't fail. If any of the I/O sources identified by +// the pollables has an error, it is indicated by marking the source as +// being reaedy for I/O. +// +// poll: func(in: list>) -> list +// +//go:nosplit +func Poll(in cm.List[Pollable]) (result cm.List[uint32]) { + in0, in1 := cm.LowerList(in) + wasmimport_Poll((*Pollable)(in0), (uint32)(in1), &result) + return +} + +//go:wasmimport wasi:io/poll@0.2.0 poll +//go:noescape +func wasmimport_Poll(in0 *Pollable, in1 uint32, result *cm.List[uint32]) diff --git a/src/internal/wasi/io/v0.2.0/streams/empty.s b/src/internal/wasi/io/v0.2.0/streams/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/io/v0.2.0/streams/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/io/v0.2.0/streams/streams.wit.go b/src/internal/wasi/io/v0.2.0/streams/streams.wit.go new file mode 100644 index 0000000000..8983d5ac0c --- /dev/null +++ b/src/internal/wasi/io/v0.2.0/streams/streams.wit.go @@ -0,0 +1,521 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package streams represents the imported interface "wasi:io/streams@0.2.0". +// +// WASI I/O is an I/O abstraction API which is currently focused on providing +// stream types. +// +// In the future, the component model is expected to add built-in stream types; +// when it does, they are expected to subsume this API. +package streams + +import ( + "github.com/ydnar/wasm-tools-go/cm" + ioerror "internal/wasi/io/v0.2.0/error" + "internal/wasi/io/v0.2.0/poll" +) + +// StreamError represents the imported variant "wasi:io/streams@0.2.0#stream-error". +// +// An error for input-stream and output-stream operations. +// +// variant stream-error { +// last-operation-failed(error), +// closed, +// } +type StreamError cm.Variant[uint8, ioerror.Error, ioerror.Error] + +// StreamErrorLastOperationFailed returns a [StreamError] of case "last-operation-failed". +// +// The last operation (a write or flush) failed before completion. +// +// More information is available in the `error` payload. +func StreamErrorLastOperationFailed(data ioerror.Error) StreamError { + return cm.New[StreamError](0, data) +} + +// LastOperationFailed returns a non-nil *[ioerror.Error] if [StreamError] represents the variant case "last-operation-failed". +func (self *StreamError) LastOperationFailed() *ioerror.Error { + return cm.Case[ioerror.Error](self, 0) +} + +// StreamErrorClosed returns a [StreamError] of case "closed". +// +// The stream is closed: no more input will be accepted by the +// stream. A closed output-stream will return this error on all +// future operations. +func StreamErrorClosed() StreamError { + var data struct{} + return cm.New[StreamError](1, data) +} + +// Closed returns true if [StreamError] represents the variant case "closed". +func (self *StreamError) Closed() bool { + return cm.Tag(self) == 1 +} + +// InputStream represents the imported resource "wasi:io/streams@0.2.0#input-stream". +// +// An input bytestream. +// +// `input-stream`s are *non-blocking* to the extent practical on underlying +// platforms. I/O operations always return promptly; if fewer bytes are +// promptly available than requested, they return the number of bytes promptly +// available, which could even be zero. To wait for data to be available, +// use the `subscribe` function to obtain a `pollable` which can be polled +// for using `wasi:io/poll`. +// +// resource input-stream +type InputStream cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "input-stream". +// +// Drops a resource handle. +// +//go:nosplit +func (self InputStream) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_InputStreamResourceDrop((uint32)(self0)) + return +} + +//go:wasmimport wasi:io/streams@0.2.0 [resource-drop]input-stream +//go:noescape +func wasmimport_InputStreamResourceDrop(self0 uint32) + +// BlockingRead represents the imported method "blocking-read". +// +// Read bytes from a stream, after blocking until at least one byte can +// be read. Except for blocking, behavior is identical to `read`. +// +// blocking-read: func(len: u64) -> result, stream-error> +// +//go:nosplit +func (self InputStream) BlockingRead(len_ uint64) (result cm.OKResult[cm.List[uint8], StreamError]) { + self0 := cm.Reinterpret[uint32](self) + len0 := (uint64)(len_) + wasmimport_InputStreamBlockingRead((uint32)(self0), (uint64)(len0), &result) + return +} + +//go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.blocking-read +//go:noescape +func wasmimport_InputStreamBlockingRead(self0 uint32, len0 uint64, result *cm.OKResult[cm.List[uint8], StreamError]) + +// BlockingSkip represents the imported method "blocking-skip". +// +// Skip bytes from a stream, after blocking until at least one byte +// can be skipped. Except for blocking behavior, identical to `skip`. +// +// blocking-skip: func(len: u64) -> result +// +//go:nosplit +func (self InputStream) BlockingSkip(len_ uint64) (result cm.OKResult[uint64, StreamError]) { + self0 := cm.Reinterpret[uint32](self) + len0 := (uint64)(len_) + wasmimport_InputStreamBlockingSkip((uint32)(self0), (uint64)(len0), &result) + return +} + +//go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.blocking-skip +//go:noescape +func wasmimport_InputStreamBlockingSkip(self0 uint32, len0 uint64, result *cm.OKResult[uint64, StreamError]) + +// Read represents the imported method "read". +// +// Perform a non-blocking read from the stream. +// +// When the source of a `read` is binary data, the bytes from the source +// are returned verbatim. When the source of a `read` is known to the +// implementation to be text, bytes containing the UTF-8 encoding of the +// text are returned. +// +// This function returns a list of bytes containing the read data, +// when successful. The returned list will contain up to `len` bytes; +// it may return fewer than requested, but not more. The list is +// empty when no bytes are available for reading at this time. The +// pollable given by `subscribe` will be ready when more bytes are +// available. +// +// This function fails with a `stream-error` when the operation +// encounters an error, giving `last-operation-failed`, or when the +// stream is closed, giving `closed`. +// +// When the caller gives a `len` of 0, it represents a request to +// read 0 bytes. If the stream is still open, this call should +// succeed and return an empty list, or otherwise fail with `closed`. +// +// The `len` parameter is a `u64`, which could represent a list of u8 which +// is not possible to allocate in wasm32, or not desirable to allocate as +// as a return value by the callee. The callee may return a list of bytes +// less than `len` in size while more bytes are available for reading. +// +// read: func(len: u64) -> result, stream-error> +// +//go:nosplit +func (self InputStream) Read(len_ uint64) (result cm.OKResult[cm.List[uint8], StreamError]) { + self0 := cm.Reinterpret[uint32](self) + len0 := (uint64)(len_) + wasmimport_InputStreamRead((uint32)(self0), (uint64)(len0), &result) + return +} + +//go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.read +//go:noescape +func wasmimport_InputStreamRead(self0 uint32, len0 uint64, result *cm.OKResult[cm.List[uint8], StreamError]) + +// Skip represents the imported method "skip". +// +// Skip bytes from a stream. Returns number of bytes skipped. +// +// Behaves identical to `read`, except instead of returning a list +// of bytes, returns the number of bytes consumed from the stream. +// +// skip: func(len: u64) -> result +// +//go:nosplit +func (self InputStream) Skip(len_ uint64) (result cm.OKResult[uint64, StreamError]) { + self0 := cm.Reinterpret[uint32](self) + len0 := (uint64)(len_) + wasmimport_InputStreamSkip((uint32)(self0), (uint64)(len0), &result) + return +} + +//go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.skip +//go:noescape +func wasmimport_InputStreamSkip(self0 uint32, len0 uint64, result *cm.OKResult[uint64, StreamError]) + +// Subscribe represents the imported method "subscribe". +// +// Create a `pollable` which will resolve once either the specified stream +// has bytes available to read or the other end of the stream has been +// closed. +// The created `pollable` is a child resource of the `input-stream`. +// Implementations may trap if the `input-stream` is dropped before +// all derived `pollable`s created with this function are dropped. +// +// subscribe: func() -> pollable +// +//go:nosplit +func (self InputStream) Subscribe() (result poll.Pollable) { + self0 := cm.Reinterpret[uint32](self) + result0 := wasmimport_InputStreamSubscribe((uint32)(self0)) + result = cm.Reinterpret[poll.Pollable]((uint32)(result0)) + return +} + +//go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.subscribe +//go:noescape +func wasmimport_InputStreamSubscribe(self0 uint32) (result0 uint32) + +// OutputStream represents the imported resource "wasi:io/streams@0.2.0#output-stream". +// +// An output bytestream. +// +// `output-stream`s are *non-blocking* to the extent practical on +// underlying platforms. Except where specified otherwise, I/O operations also +// always return promptly, after the number of bytes that can be written +// promptly, which could even be zero. To wait for the stream to be ready to +// accept data, the `subscribe` function to obtain a `pollable` which can be +// polled for using `wasi:io/poll`. +// +// resource output-stream +type OutputStream cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "output-stream". +// +// Drops a resource handle. +// +//go:nosplit +func (self OutputStream) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_OutputStreamResourceDrop((uint32)(self0)) + return +} + +//go:wasmimport wasi:io/streams@0.2.0 [resource-drop]output-stream +//go:noescape +func wasmimport_OutputStreamResourceDrop(self0 uint32) + +// BlockingFlush represents the imported method "blocking-flush". +// +// Request to flush buffered output, and block until flush completes +// and stream is ready for writing again. +// +// blocking-flush: func() -> result<_, stream-error> +// +//go:nosplit +func (self OutputStream) BlockingFlush() (result cm.ErrResult[struct{}, StreamError]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_OutputStreamBlockingFlush((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-flush +//go:noescape +func wasmimport_OutputStreamBlockingFlush(self0 uint32, result *cm.ErrResult[struct{}, StreamError]) + +// BlockingSplice represents the imported method "blocking-splice". +// +// Read from one stream and write to another, with blocking. +// +// This is similar to `splice`, except that it blocks until the +// `output-stream` is ready for writing, and the `input-stream` +// is ready for reading, before performing the `splice`. +// +// blocking-splice: func(src: borrow, len: u64) -> result +// +//go:nosplit +func (self OutputStream) BlockingSplice(src InputStream, len_ uint64) (result cm.OKResult[uint64, StreamError]) { + self0 := cm.Reinterpret[uint32](self) + src0 := cm.Reinterpret[uint32](src) + len0 := (uint64)(len_) + wasmimport_OutputStreamBlockingSplice((uint32)(self0), (uint32)(src0), (uint64)(len0), &result) + return +} + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-splice +//go:noescape +func wasmimport_OutputStreamBlockingSplice(self0 uint32, src0 uint32, len0 uint64, result *cm.OKResult[uint64, StreamError]) + +// BlockingWriteAndFlush represents the imported method "blocking-write-and-flush". +// +// Perform a write of up to 4096 bytes, and then flush the stream. Block +// until all of these operations are complete, or an error occurs. +// +// This is a convenience wrapper around the use of `check-write`, +// `subscribe`, `write`, and `flush`, and is implemented with the +// following pseudo-code: +// +// let pollable = this.subscribe(); +// while !contents.is_empty() { +// // Wait for the stream to become writable +// pollable.block(); +// let Ok(n) = this.check-write(); // eliding error handling +// let len = min(n, contents.len()); +// let (chunk, rest) = contents.split_at(len); +// this.write(chunk ); // eliding error handling +// contents = rest; +// } +// this.flush(); +// // Wait for completion of `flush` +// pollable.block(); +// // Check for any errors that arose during `flush` +// let _ = this.check-write(); // eliding error handling +// +// blocking-write-and-flush: func(contents: list) -> result<_, stream-error> +// +//go:nosplit +func (self OutputStream) BlockingWriteAndFlush(contents cm.List[uint8]) (result cm.ErrResult[struct{}, StreamError]) { + self0 := cm.Reinterpret[uint32](self) + contents0, contents1 := cm.LowerList(contents) + wasmimport_OutputStreamBlockingWriteAndFlush((uint32)(self0), (*uint8)(contents0), (uint32)(contents1), &result) + return +} + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-write-and-flush +//go:noescape +func wasmimport_OutputStreamBlockingWriteAndFlush(self0 uint32, contents0 *uint8, contents1 uint32, result *cm.ErrResult[struct{}, StreamError]) + +// BlockingWriteZeroesAndFlush represents the imported method "blocking-write-zeroes-and-flush". +// +// Perform a write of up to 4096 zeroes, and then flush the stream. +// Block until all of these operations are complete, or an error +// occurs. +// +// This is a convenience wrapper around the use of `check-write`, +// `subscribe`, `write-zeroes`, and `flush`, and is implemented with +// the following pseudo-code: +// +// let pollable = this.subscribe(); +// while num_zeroes != 0 { +// // Wait for the stream to become writable +// pollable.block(); +// let Ok(n) = this.check-write(); // eliding error handling +// let len = min(n, num_zeroes); +// this.write-zeroes(len); // eliding error handling +// num_zeroes -= len; +// } +// this.flush(); +// // Wait for completion of `flush` +// pollable.block(); +// // Check for any errors that arose during `flush` +// let _ = this.check-write(); // eliding error handling +// +// blocking-write-zeroes-and-flush: func(len: u64) -> result<_, stream-error> +// +//go:nosplit +func (self OutputStream) BlockingWriteZeroesAndFlush(len_ uint64) (result cm.ErrResult[struct{}, StreamError]) { + self0 := cm.Reinterpret[uint32](self) + len0 := (uint64)(len_) + wasmimport_OutputStreamBlockingWriteZeroesAndFlush((uint32)(self0), (uint64)(len0), &result) + return +} + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-write-zeroes-and-flush +//go:noescape +func wasmimport_OutputStreamBlockingWriteZeroesAndFlush(self0 uint32, len0 uint64, result *cm.ErrResult[struct{}, StreamError]) + +// CheckWrite represents the imported method "check-write". +// +// Check readiness for writing. This function never blocks. +// +// Returns the number of bytes permitted for the next call to `write`, +// or an error. Calling `write` with more bytes than this function has +// permitted will trap. +// +// When this function returns 0 bytes, the `subscribe` pollable will +// become ready when this function will report at least 1 byte, or an +// error. +// +// check-write: func() -> result +// +//go:nosplit +func (self OutputStream) CheckWrite() (result cm.OKResult[uint64, StreamError]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_OutputStreamCheckWrite((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.check-write +//go:noescape +func wasmimport_OutputStreamCheckWrite(self0 uint32, result *cm.OKResult[uint64, StreamError]) + +// Flush represents the imported method "flush". +// +// Request to flush buffered output. This function never blocks. +// +// This tells the output-stream that the caller intends any buffered +// output to be flushed. the output which is expected to be flushed +// is all that has been passed to `write` prior to this call. +// +// Upon calling this function, the `output-stream` will not accept any +// writes (`check-write` will return `ok(0)`) until the flush has +// completed. The `subscribe` pollable will become ready when the +// flush has completed and the stream can accept more writes. +// +// flush: func() -> result<_, stream-error> +// +//go:nosplit +func (self OutputStream) Flush() (result cm.ErrResult[struct{}, StreamError]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_OutputStreamFlush((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.flush +//go:noescape +func wasmimport_OutputStreamFlush(self0 uint32, result *cm.ErrResult[struct{}, StreamError]) + +// Splice represents the imported method "splice". +// +// Read from one stream and write to another. +// +// The behavior of splice is equivelant to: +// 1. calling `check-write` on the `output-stream` +// 2. calling `read` on the `input-stream` with the smaller of the +// `check-write` permitted length and the `len` provided to `splice` +// 3. calling `write` on the `output-stream` with that read data. +// +// Any error reported by the call to `check-write`, `read`, or +// `write` ends the splice and reports that error. +// +// This function returns the number of bytes transferred; it may be less +// than `len`. +// +// splice: func(src: borrow, len: u64) -> result +// +//go:nosplit +func (self OutputStream) Splice(src InputStream, len_ uint64) (result cm.OKResult[uint64, StreamError]) { + self0 := cm.Reinterpret[uint32](self) + src0 := cm.Reinterpret[uint32](src) + len0 := (uint64)(len_) + wasmimport_OutputStreamSplice((uint32)(self0), (uint32)(src0), (uint64)(len0), &result) + return +} + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.splice +//go:noescape +func wasmimport_OutputStreamSplice(self0 uint32, src0 uint32, len0 uint64, result *cm.OKResult[uint64, StreamError]) + +// Subscribe represents the imported method "subscribe". +// +// Create a `pollable` which will resolve once the output-stream +// is ready for more writing, or an error has occured. When this +// pollable is ready, `check-write` will return `ok(n)` with n>0, or an +// error. +// +// If the stream is closed, this pollable is always ready immediately. +// +// The created `pollable` is a child resource of the `output-stream`. +// Implementations may trap if the `output-stream` is dropped before +// all derived `pollable`s created with this function are dropped. +// +// subscribe: func() -> pollable +// +//go:nosplit +func (self OutputStream) Subscribe() (result poll.Pollable) { + self0 := cm.Reinterpret[uint32](self) + result0 := wasmimport_OutputStreamSubscribe((uint32)(self0)) + result = cm.Reinterpret[poll.Pollable]((uint32)(result0)) + return +} + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.subscribe +//go:noescape +func wasmimport_OutputStreamSubscribe(self0 uint32) (result0 uint32) + +// Write represents the imported method "write". +// +// Perform a write. This function never blocks. +// +// When the destination of a `write` is binary data, the bytes from +// `contents` are written verbatim. When the destination of a `write` is +// known to the implementation to be text, the bytes of `contents` are +// transcoded from UTF-8 into the encoding of the destination and then +// written. +// +// Precondition: check-write gave permit of Ok(n) and contents has a +// length of less than or equal to n. Otherwise, this function will trap. +// +// returns Err(closed) without writing if the stream has closed since +// the last call to check-write provided a permit. +// +// write: func(contents: list) -> result<_, stream-error> +// +//go:nosplit +func (self OutputStream) Write(contents cm.List[uint8]) (result cm.ErrResult[struct{}, StreamError]) { + self0 := cm.Reinterpret[uint32](self) + contents0, contents1 := cm.LowerList(contents) + wasmimport_OutputStreamWrite((uint32)(self0), (*uint8)(contents0), (uint32)(contents1), &result) + return +} + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.write +//go:noescape +func wasmimport_OutputStreamWrite(self0 uint32, contents0 *uint8, contents1 uint32, result *cm.ErrResult[struct{}, StreamError]) + +// WriteZeroes represents the imported method "write-zeroes". +// +// Write zeroes to a stream. +// +// This should be used precisely like `write` with the exact same +// preconditions (must use check-write first), but instead of +// passing a list of bytes, you simply pass the number of zero-bytes +// that should be written. +// +// write-zeroes: func(len: u64) -> result<_, stream-error> +// +//go:nosplit +func (self OutputStream) WriteZeroes(len_ uint64) (result cm.ErrResult[struct{}, StreamError]) { + self0 := cm.Reinterpret[uint32](self) + len0 := (uint64)(len_) + wasmimport_OutputStreamWriteZeroes((uint32)(self0), (uint64)(len0), &result) + return +} + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.write-zeroes +//go:noescape +func wasmimport_OutputStreamWriteZeroes(self0 uint32, len0 uint64, result *cm.ErrResult[struct{}, StreamError]) diff --git a/src/internal/wasi/random/v0.2.0/insecure-seed/empty.s b/src/internal/wasi/random/v0.2.0/insecure-seed/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/random/v0.2.0/insecure-seed/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go b/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go new file mode 100644 index 0000000000..e9ac780821 --- /dev/null +++ b/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go @@ -0,0 +1,43 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package insecureseed represents the imported interface "wasi:random/insecure-seed@0.2.0". +// +// The insecure-seed interface for seeding hash-map DoS resistance. +// +// It is intended to be portable at least between Unix-family platforms and +// Windows. +package insecureseed + +// InsecureSeed represents the imported function "insecure-seed". +// +// Return a 128-bit value that may contain a pseudo-random value. +// +// The returned value is not required to be computed from a CSPRNG, and may +// even be entirely deterministic. Host implementations are encouraged to +// provide pseudo-random values to any program exposed to +// attacker-controlled content, to enable DoS protection built into many +// languages' hash-map implementations. +// +// This function is intended to only be called once, by a source language +// to initialize Denial Of Service (DoS) protection in its hash-map +// implementation. +// +// # Expected future evolution +// +// This will likely be changed to a value import, to prevent it from being +// called multiple times and potentially used for purposes other than DoS +// protection. +// +// insecure-seed: func() -> tuple +// +//go:nosplit +func InsecureSeed() (result [2]uint64) { + wasmimport_InsecureSeed(&result) + return +} + +//go:wasmimport wasi:random/insecure-seed@0.2.0 insecure-seed +//go:noescape +func wasmimport_InsecureSeed(result *[2]uint64) diff --git a/src/internal/wasi/random/v0.2.0/insecure/empty.s b/src/internal/wasi/random/v0.2.0/insecure/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/random/v0.2.0/insecure/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go b/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go new file mode 100644 index 0000000000..fbea789b5d --- /dev/null +++ b/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go @@ -0,0 +1,59 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package insecure represents the imported interface "wasi:random/insecure@0.2.0". +// +// The insecure interface for insecure pseudo-random numbers. +// +// It is intended to be portable at least between Unix-family platforms and +// Windows. +package insecure + +import ( + "github.com/ydnar/wasm-tools-go/cm" +) + +// GetInsecureRandomBytes represents the imported function "get-insecure-random-bytes". +// +// Return `len` insecure pseudo-random bytes. +// +// This function is not cryptographically secure. Do not use it for +// anything related to security. +// +// There are no requirements on the values of the returned bytes, however +// implementations are encouraged to return evenly distributed values with +// a long period. +// +// get-insecure-random-bytes: func(len: u64) -> list +// +//go:nosplit +func GetInsecureRandomBytes(len_ uint64) (result cm.List[uint8]) { + len0 := (uint64)(len_) + wasmimport_GetInsecureRandomBytes((uint64)(len0), &result) + return +} + +//go:wasmimport wasi:random/insecure@0.2.0 get-insecure-random-bytes +//go:noescape +func wasmimport_GetInsecureRandomBytes(len0 uint64, result *cm.List[uint8]) + +// GetInsecureRandomU64 represents the imported function "get-insecure-random-u64". +// +// Return an insecure pseudo-random `u64` value. +// +// This function returns the same type of pseudo-random data as +// `get-insecure-random-bytes`, represented as a `u64`. +// +// get-insecure-random-u64: func() -> u64 +// +//go:nosplit +func GetInsecureRandomU64() (result uint64) { + result0 := wasmimport_GetInsecureRandomU64() + result = (uint64)((uint64)(result0)) + return +} + +//go:wasmimport wasi:random/insecure@0.2.0 get-insecure-random-u64 +//go:noescape +func wasmimport_GetInsecureRandomU64() (result0 uint64) diff --git a/src/internal/wasi/random/v0.2.0/random/empty.s b/src/internal/wasi/random/v0.2.0/random/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/random/v0.2.0/random/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/random/v0.2.0/random/random.wit.go b/src/internal/wasi/random/v0.2.0/random/random.wit.go new file mode 100644 index 0000000000..f60d468e19 --- /dev/null +++ b/src/internal/wasi/random/v0.2.0/random/random.wit.go @@ -0,0 +1,63 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package random represents the imported interface "wasi:random/random@0.2.0". +// +// WASI Random is a random data API. +// +// It is intended to be portable at least between Unix-family platforms and +// Windows. +package random + +import ( + "github.com/ydnar/wasm-tools-go/cm" +) + +// GetRandomBytes represents the imported function "get-random-bytes". +// +// Return `len` cryptographically-secure random or pseudo-random bytes. +// +// This function must produce data at least as cryptographically secure and +// fast as an adequately seeded cryptographically-secure pseudo-random +// number generator (CSPRNG). It must not block, from the perspective of +// the calling program, under any circumstances, including on the first +// request and on requests for numbers of bytes. The returned data must +// always be unpredictable. +// +// This function must always return fresh data. Deterministic environments +// must omit this function, rather than implementing it with deterministic +// data. +// +// get-random-bytes: func(len: u64) -> list +// +//go:nosplit +func GetRandomBytes(len_ uint64) (result cm.List[uint8]) { + len0 := (uint64)(len_) + wasmimport_GetRandomBytes((uint64)(len0), &result) + return +} + +//go:wasmimport wasi:random/random@0.2.0 get-random-bytes +//go:noescape +func wasmimport_GetRandomBytes(len0 uint64, result *cm.List[uint8]) + +// GetRandomU64 represents the imported function "get-random-u64". +// +// Return a cryptographically-secure random or pseudo-random `u64` value. +// +// This function returns the same type of data as `get-random-bytes`, +// represented as a `u64`. +// +// get-random-u64: func() -> u64 +// +//go:nosplit +func GetRandomU64() (result uint64) { + result0 := wasmimport_GetRandomU64() + result = (uint64)((uint64)(result0)) + return +} + +//go:wasmimport wasi:random/random@0.2.0 get-random-u64 +//go:noescape +func wasmimport_GetRandomU64() (result0 uint64) diff --git a/src/internal/wasi/sockets/v0.2.0/instance-network/empty.s b/src/internal/wasi/sockets/v0.2.0/instance-network/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/instance-network/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go b/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go new file mode 100644 index 0000000000..fbd9dc8ad4 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go @@ -0,0 +1,30 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package instancenetwork represents the imported interface "wasi:sockets/instance-network@0.2.0". +// +// This interface provides a value-export of the default network handle.. +package instancenetwork + +import ( + "github.com/ydnar/wasm-tools-go/cm" + "internal/wasi/sockets/v0.2.0/network" +) + +// InstanceNetwork represents the imported function "instance-network". +// +// Get a handle to the default network. +// +// instance-network: func() -> network +// +//go:nosplit +func InstanceNetwork() (result network.Network) { + result0 := wasmimport_InstanceNetwork() + result = cm.Reinterpret[network.Network]((uint32)(result0)) + return +} + +//go:wasmimport wasi:sockets/instance-network@0.2.0 instance-network +//go:noescape +func wasmimport_InstanceNetwork() (result0 uint32) diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/empty.s b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go new file mode 100644 index 0000000000..fc81b36ac8 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go @@ -0,0 +1,123 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package ipnamelookup represents the imported interface "wasi:sockets/ip-name-lookup@0.2.0". +package ipnamelookup + +import ( + "github.com/ydnar/wasm-tools-go/cm" + "internal/wasi/io/v0.2.0/poll" + "internal/wasi/sockets/v0.2.0/network" +) + +// ResolveAddressStream represents the imported resource "wasi:sockets/ip-name-lookup@0.2.0#resolve-address-stream". +// +// resource resolve-address-stream +type ResolveAddressStream cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "resolve-address-stream". +// +// Drops a resource handle. +// +//go:nosplit +func (self ResolveAddressStream) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_ResolveAddressStreamResourceDrop((uint32)(self0)) + return +} + +//go:wasmimport wasi:sockets/ip-name-lookup@0.2.0 [resource-drop]resolve-address-stream +//go:noescape +func wasmimport_ResolveAddressStreamResourceDrop(self0 uint32) + +// ResolveNextAddress represents the imported method "resolve-next-address". +// +// Returns the next address from the resolver. +// +// This function should be called multiple times. On each call, it will +// return the next address in connection order preference. If all +// addresses have been exhausted, this function returns `none`. +// +// This function never returns IPv4-mapped IPv6 addresses. +// +// # Typical errors +// - `name-unresolvable`: Name does not exist or has no suitable associated +// IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) +// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. +// (EAI_AGAIN) +// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. +// (EAI_FAIL) +// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) +// +// resolve-next-address: func() -> result, error-code> +// +//go:nosplit +func (self ResolveAddressStream) ResolveNextAddress() (result cm.OKResult[cm.Option[network.IPAddress], network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_ResolveAddressStreamResolveNextAddress((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:sockets/ip-name-lookup@0.2.0 [method]resolve-address-stream.resolve-next-address +//go:noescape +func wasmimport_ResolveAddressStreamResolveNextAddress(self0 uint32, result *cm.OKResult[cm.Option[network.IPAddress], network.ErrorCode]) + +// Subscribe represents the imported method "subscribe". +// +// Create a `pollable` which will resolve once the stream is ready for I/O. +// +// Note: this function is here for WASI Preview2 only. +// It's planned to be removed when `future` is natively supported in Preview3. +// +// subscribe: func() -> pollable +// +//go:nosplit +func (self ResolveAddressStream) Subscribe() (result poll.Pollable) { + self0 := cm.Reinterpret[uint32](self) + result0 := wasmimport_ResolveAddressStreamSubscribe((uint32)(self0)) + result = cm.Reinterpret[poll.Pollable]((uint32)(result0)) + return +} + +//go:wasmimport wasi:sockets/ip-name-lookup@0.2.0 [method]resolve-address-stream.subscribe +//go:noescape +func wasmimport_ResolveAddressStreamSubscribe(self0 uint32) (result0 uint32) + +// ResolveAddresses represents the imported function "resolve-addresses". +// +// Resolve an internet host name to a list of IP addresses. +// +// Unicode domain names are automatically converted to ASCII using IDNA encoding. +// If the input is an IP address string, the address is parsed and returned +// as-is without making any external requests. +// +// See the wasi-socket proposal README.md for a comparison with getaddrinfo. +// +// This function never blocks. It either immediately fails or immediately +// returns successfully with a `resolve-address-stream` that can be used +// to (asynchronously) fetch the results. +// +// # Typical errors +// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address. +// +// # References: +// - +// - +// - +// - +// +// resolve-addresses: func(network: borrow, name: string) -> result +// +//go:nosplit +func ResolveAddresses(network_ network.Network, name string) (result cm.OKResult[ResolveAddressStream, network.ErrorCode]) { + network0 := cm.Reinterpret[uint32](network_) + name0, name1 := cm.LowerString(name) + wasmimport_ResolveAddresses((uint32)(network0), (*uint8)(name0), (uint32)(name1), &result) + return +} + +//go:wasmimport wasi:sockets/ip-name-lookup@0.2.0 resolve-addresses +//go:noescape +func wasmimport_ResolveAddresses(network0 uint32, name0 *uint8, name1 uint32, result *cm.OKResult[ResolveAddressStream, network.ErrorCode]) diff --git a/src/internal/wasi/sockets/v0.2.0/network/empty.s b/src/internal/wasi/sockets/v0.2.0/network/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/network/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/sockets/v0.2.0/network/network.wit.go b/src/internal/wasi/sockets/v0.2.0/network/network.wit.go new file mode 100644 index 0000000000..4cca93aca0 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/network/network.wit.go @@ -0,0 +1,278 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package network represents the imported interface "wasi:sockets/network@0.2.0". +package network + +import ( + "github.com/ydnar/wasm-tools-go/cm" +) + +// Network represents the imported resource "wasi:sockets/network@0.2.0#network". +// +// An opaque resource that represents access to (a subset of) the network. +// This enables context-based security for networking. +// There is no need for this to map 1:1 to a physical network interface. +// +// resource network +type Network cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "network". +// +// Drops a resource handle. +// +//go:nosplit +func (self Network) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_NetworkResourceDrop((uint32)(self0)) + return +} + +//go:wasmimport wasi:sockets/network@0.2.0 [resource-drop]network +//go:noescape +func wasmimport_NetworkResourceDrop(self0 uint32) + +// ErrorCode represents the enum "wasi:sockets/network@0.2.0#error-code". +// +// Error codes. +// +// In theory, every API can return any error code. +// In practice, API's typically only return the errors documented per API +// combined with a couple of errors that are always possible: +// - `unknown` +// - `access-denied` +// - `not-supported` +// - `out-of-memory` +// - `concurrency-conflict` +// +// See each individual API for what the POSIX equivalents are. They sometimes differ +// per API. +// +// enum error-code { +// unknown, +// access-denied, +// not-supported, +// invalid-argument, +// out-of-memory, +// timeout, +// concurrency-conflict, +// not-in-progress, +// would-block, +// invalid-state, +// new-socket-limit, +// address-not-bindable, +// address-in-use, +// remote-unreachable, +// connection-refused, +// connection-reset, +// connection-aborted, +// datagram-too-large, +// name-unresolvable, +// temporary-resolver-failure, +// permanent-resolver-failure +// } +type ErrorCode uint8 + +const ( + // Unknown error + ErrorCodeUnknown ErrorCode = iota + + // Access denied. + // + // POSIX equivalent: EACCES, EPERM + ErrorCodeAccessDenied + + // The operation is not supported. + // + // POSIX equivalent: EOPNOTSUPP + ErrorCodeNotSupported + + // One of the arguments is invalid. + // + // POSIX equivalent: EINVAL + ErrorCodeInvalidArgument + + // Not enough memory to complete the operation. + // + // POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY + ErrorCodeOutOfMemory + + // The operation timed out before it could finish completely. + ErrorCodeTimeout + + // This operation is incompatible with another asynchronous operation that is already + // in progress. + // + // POSIX equivalent: EALREADY + ErrorCodeConcurrencyConflict + + // Trying to finish an asynchronous operation that: + // - has not been started yet, or: + // - was already finished by a previous `finish-*` call. + // + // Note: this is scheduled to be removed when `future`s are natively supported. + ErrorCodeNotInProgress + + // The operation has been aborted because it could not be completed immediately. + // + // Note: this is scheduled to be removed when `future`s are natively supported. + ErrorCodeWouldBlock + + // The operation is not valid in the socket's current state. + ErrorCodeInvalidState + + // A new socket resource could not be created because of a system limit. + ErrorCodeNewSocketLimit + + // A bind operation failed because the provided address is not an address that the + // `network` can bind to. + ErrorCodeAddressNotBindable + + // A bind operation failed because the provided address is already in use or because + // there are no ephemeral ports available. + ErrorCodeAddressInUse + + // The remote address is not reachable + ErrorCodeRemoteUnreachable + + // The TCP connection was forcefully rejected + ErrorCodeConnectionRefused + + // The TCP connection was reset. + ErrorCodeConnectionReset + + // A TCP connection was aborted. + ErrorCodeConnectionAborted + + // The size of a datagram sent to a UDP socket exceeded the maximum + // supported size. + ErrorCodeDatagramTooLarge + + // Name does not exist or has no suitable associated IP addresses. + ErrorCodeNameUnresolvable + + // A temporary failure in name resolution occurred. + ErrorCodeTemporaryResolverFailure + + // A permanent failure in name resolution occurred. + ErrorCodePermanentResolverFailure +) + +// IPAddressFamily represents the enum "wasi:sockets/network@0.2.0#ip-address-family". +// +// enum ip-address-family { +// ipv4, +// ipv6 +// } +type IPAddressFamily uint8 + +const ( + // Similar to `AF_INET` in POSIX. + IPAddressFamilyIPv4 IPAddressFamily = iota + + // Similar to `AF_INET6` in POSIX. + IPAddressFamilyIPv6 +) + +// IPv4Address represents the tuple "wasi:sockets/network@0.2.0#ipv4-address". +// +// type ipv4-address = tuple +type IPv4Address [4]uint8 + +// IPv6Address represents the tuple "wasi:sockets/network@0.2.0#ipv6-address". +// +// type ipv6-address = tuple +type IPv6Address [8]uint16 + +// IPAddress represents the variant "wasi:sockets/network@0.2.0#ip-address". +// +// variant ip-address { +// ipv4(ipv4-address), +// ipv6(ipv6-address), +// } +type IPAddress cm.Variant[uint8, IPv6Address, IPv6Address] + +// IPAddressIPv4 returns a [IPAddress] of case "ipv4". +func IPAddressIPv4(data IPv4Address) IPAddress { + return cm.New[IPAddress](0, data) +} + +// IPv4 returns a non-nil *[IPv4Address] if [IPAddress] represents the variant case "ipv4". +func (self *IPAddress) IPv4() *IPv4Address { + return cm.Case[IPv4Address](self, 0) +} + +// IPAddressIPv6 returns a [IPAddress] of case "ipv6". +func IPAddressIPv6(data IPv6Address) IPAddress { + return cm.New[IPAddress](1, data) +} + +// IPv6 returns a non-nil *[IPv6Address] if [IPAddress] represents the variant case "ipv6". +func (self *IPAddress) IPv6() *IPv6Address { + return cm.Case[IPv6Address](self, 1) +} + +// IPv4SocketAddress represents the record "wasi:sockets/network@0.2.0#ipv4-socket-address". +// +// record ipv4-socket-address { +// port: u16, +// address: ipv4-address, +// } +type IPv4SocketAddress struct { + // sin_port + Port uint16 + + // sin_addr + Address IPv4Address +} + +// IPv6SocketAddress represents the record "wasi:sockets/network@0.2.0#ipv6-socket-address". +// +// record ipv6-socket-address { +// port: u16, +// flow-info: u32, +// address: ipv6-address, +// scope-id: u32, +// } +type IPv6SocketAddress struct { + // sin6_port + Port uint16 + + // sin6_flowinfo + FlowInfo uint32 + + // sin6_addr + Address IPv6Address + + // sin6_scope_id + ScopeID uint32 +} + +// IPSocketAddress represents the variant "wasi:sockets/network@0.2.0#ip-socket-address". +// +// variant ip-socket-address { +// ipv4(ipv4-socket-address), +// ipv6(ipv6-socket-address), +// } +type IPSocketAddress cm.Variant[uint8, IPv6SocketAddress, IPv6SocketAddress] + +// IPSocketAddressIPv4 returns a [IPSocketAddress] of case "ipv4". +func IPSocketAddressIPv4(data IPv4SocketAddress) IPSocketAddress { + return cm.New[IPSocketAddress](0, data) +} + +// IPv4 returns a non-nil *[IPv4SocketAddress] if [IPSocketAddress] represents the variant case "ipv4". +func (self *IPSocketAddress) IPv4() *IPv4SocketAddress { + return cm.Case[IPv4SocketAddress](self, 0) +} + +// IPSocketAddressIPv6 returns a [IPSocketAddress] of case "ipv6". +func IPSocketAddressIPv6(data IPv6SocketAddress) IPSocketAddress { + return cm.New[IPSocketAddress](1, data) +} + +// IPv6 returns a non-nil *[IPv6SocketAddress] if [IPSocketAddress] represents the variant case "ipv6". +func (self *IPSocketAddress) IPv6() *IPv6SocketAddress { + return cm.Case[IPv6SocketAddress](self, 1) +} diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/empty.s b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go new file mode 100644 index 0000000000..906128abea --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go @@ -0,0 +1,54 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package tcpcreatesocket represents the imported interface "wasi:sockets/tcp-create-socket@0.2.0". +package tcpcreatesocket + +import ( + "github.com/ydnar/wasm-tools-go/cm" + "internal/wasi/sockets/v0.2.0/network" + "internal/wasi/sockets/v0.2.0/tcp" +) + +// CreateTCPSocket represents the imported function "create-tcp-socket". +// +// Create a new TCP socket. +// +// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. +// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. +// +// This function does not require a network capability handle. This is considered +// to be safe because +// at time of creation, the socket is not bound to any `network` yet. Up to the moment +// `bind`/`connect` +// is called, the socket is effectively an in-memory configuration object, unable +// to communicate with the outside world. +// +// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous +// operations. +// +// # Typical errors +// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) +// - `new-socket-limit`: The new socket resource could not be created because of +// a system limit. (EMFILE, ENFILE) +// +// # References +// - +// - +// - +// - +// +// create-tcp-socket: func(address-family: ip-address-family) -> result +// +//go:nosplit +func CreateTCPSocket(addressFamily network.IPAddressFamily) (result cm.OKResult[tcp.TCPSocket, network.ErrorCode]) { + addressFamily0 := (uint32)(addressFamily) + wasmimport_CreateTCPSocket((uint32)(addressFamily0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp-create-socket@0.2.0 create-tcp-socket +//go:noescape +func wasmimport_CreateTCPSocket(addressFamily0 uint32, result *cm.OKResult[tcp.TCPSocket, network.ErrorCode]) diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/abi.go b/src/internal/wasi/sockets/v0.2.0/tcp/abi.go new file mode 100644 index 0000000000..fecab79fdf --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/tcp/abi.go @@ -0,0 +1,71 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +package tcp + +import ( + "github.com/ydnar/wasm-tools-go/cm" + "internal/wasi/sockets/v0.2.0/network" +) + +func lower_IPv4Address(v network.IPv4Address) (f0 uint32, f1 uint32, f2 uint32, f3 uint32) { + f0 = (uint32)(v[0]) + f1 = (uint32)(v[1]) + f2 = (uint32)(v[2]) + f3 = (uint32)(v[3]) + return +} + +func lower_IPv4SocketAddress(v network.IPv4SocketAddress) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32) { + f0 = (uint32)(v.Port) + f1, f2, f3, f4 = lower_IPv4Address(v.Address) + return +} + +func lower_IPv6Address(v network.IPv6Address) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32) { + f0 = (uint32)(v[0]) + f1 = (uint32)(v[1]) + f2 = (uint32)(v[2]) + f3 = (uint32)(v[3]) + f4 = (uint32)(v[4]) + f5 = (uint32)(v[5]) + f6 = (uint32)(v[6]) + f7 = (uint32)(v[7]) + return +} + +func lower_IPv6SocketAddress(v network.IPv6SocketAddress) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32, f8 uint32, f9 uint32, f10 uint32) { + f0 = (uint32)(v.Port) + f1 = (uint32)(v.FlowInfo) + f2, f3, f4, f5, f6, f7, f8, f9 = lower_IPv6Address(v.Address) + f10 = (uint32)(v.ScopeID) + return +} + +func lower_IPSocketAddress(v network.IPSocketAddress) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32, f8 uint32, f9 uint32, f10 uint32, f11 uint32) { + f0 = (uint32)(cm.Tag(&v)) + switch f0 { + case 0: // ipv4 + v1, v2, v3, v4, v5 := lower_IPv4SocketAddress(*v.IPv4()) + f1 = (uint32)(v1) + f2 = (uint32)(v2) + f3 = (uint32)(v3) + f4 = (uint32)(v4) + f5 = (uint32)(v5) + case 1: // ipv6 + v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 := lower_IPv6SocketAddress(*v.IPv6()) + f1 = (uint32)(v1) + f2 = (uint32)(v2) + f3 = (uint32)(v3) + f4 = (uint32)(v4) + f5 = (uint32)(v5) + f6 = (uint32)(v6) + f7 = (uint32)(v7) + f8 = (uint32)(v8) + f9 = (uint32)(v9) + f10 = (uint32)(v10) + f11 = (uint32)(v11) + } + return +} diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/empty.s b/src/internal/wasi/sockets/v0.2.0/tcp/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/tcp/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go new file mode 100644 index 0000000000..f390849cba --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go @@ -0,0 +1,839 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package tcp represents the imported interface "wasi:sockets/tcp@0.2.0". +package tcp + +import ( + "github.com/ydnar/wasm-tools-go/cm" + monotonicclock "internal/wasi/clocks/v0.2.0/monotonic-clock" + "internal/wasi/io/v0.2.0/poll" + "internal/wasi/io/v0.2.0/streams" + "internal/wasi/sockets/v0.2.0/network" +) + +// ShutdownType represents the enum "wasi:sockets/tcp@0.2.0#shutdown-type". +// +// enum shutdown-type { +// receive, +// send, +// both +// } +type ShutdownType uint8 + +const ( + // Similar to `SHUT_RD` in POSIX. + ShutdownTypeReceive ShutdownType = iota + + // Similar to `SHUT_WR` in POSIX. + ShutdownTypeSend + + // Similar to `SHUT_RDWR` in POSIX. + ShutdownTypeBoth +) + +// TCPSocket represents the imported resource "wasi:sockets/tcp@0.2.0#tcp-socket". +// +// A TCP socket resource. +// +// The socket can be in one of the following states: +// - `unbound` +// - `bind-in-progress` +// - `bound` (See note below) +// - `listen-in-progress` +// - `listening` +// - `connect-in-progress` +// - `connected` +// - `closed` +// See +// for a more information. +// +// Note: Except where explicitly mentioned, whenever this documentation uses +// the term "bound" without backticks it actually means: in the `bound` state *or +// higher*. +// (i.e. `bound`, `listen-in-progress`, `listening`, `connect-in-progress` or `connected`) +// +// In addition to the general error codes documented on the +// `network::error-code` type, TCP socket methods may always return +// `error(invalid-state)` when in the `closed` state. +// +// resource tcp-socket +type TCPSocket cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "tcp-socket". +// +// Drops a resource handle. +// +//go:nosplit +func (self TCPSocket) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketResourceDrop((uint32)(self0)) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [resource-drop]tcp-socket +//go:noescape +func wasmimport_TCPSocketResourceDrop(self0 uint32) + +// Accept represents the imported method "accept". +// +// Accept a new client socket. +// +// The returned socket is bound and in the `connected` state. The following properties +// are inherited from the listener socket: +// - `address-family` +// - `keep-alive-enabled` +// - `keep-alive-idle-time` +// - `keep-alive-interval` +// - `keep-alive-count` +// - `hop-limit` +// - `receive-buffer-size` +// - `send-buffer-size` +// +// On success, this function returns the newly accepted client socket along with +// a pair of streams that can be used to read & write to the connection. +// +// # Typical errors +// - `invalid-state`: Socket is not in the `listening` state. (EINVAL) +// - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) +// - `connection-aborted`: An incoming connection was pending, but was terminated +// by the client before this listener could accept it. (ECONNABORTED) +// - `new-socket-limit`: The new socket resource could not be created because of +// a system limit. (EMFILE, ENFILE) +// +// # References +// - +// - +// - +// - +// +// accept: func() -> result, error-code> +// +//go:nosplit +func (self TCPSocket) Accept() (result cm.OKResult[cm.Tuple3[TCPSocket, streams.InputStream, streams.OutputStream], network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketAccept((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.accept +//go:noescape +func wasmimport_TCPSocketAccept(self0 uint32, result *cm.OKResult[cm.Tuple3[TCPSocket, streams.InputStream, streams.OutputStream], network.ErrorCode]) + +// AddressFamily represents the imported method "address-family". +// +// Whether this is a IPv4 or IPv6 socket. +// +// Equivalent to the SO_DOMAIN socket option. +// +// address-family: func() -> ip-address-family +// +//go:nosplit +func (self TCPSocket) AddressFamily() (result network.IPAddressFamily) { + self0 := cm.Reinterpret[uint32](self) + result0 := wasmimport_TCPSocketAddressFamily((uint32)(self0)) + result = (network.IPAddressFamily)((uint32)(result0)) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.address-family +//go:noescape +func wasmimport_TCPSocketAddressFamily(self0 uint32) (result0 uint32) + +// FinishBind represents the imported method "finish-bind". +// +// finish-bind: func() -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) FinishBind() (result cm.ErrResult[struct{}, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketFinishBind((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.finish-bind +//go:noescape +func wasmimport_TCPSocketFinishBind(self0 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) + +// FinishConnect represents the imported method "finish-connect". +// +// finish-connect: func() -> result, error-code> +// +//go:nosplit +func (self TCPSocket) FinishConnect() (result cm.OKResult[cm.Tuple[streams.InputStream, streams.OutputStream], network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketFinishConnect((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.finish-connect +//go:noescape +func wasmimport_TCPSocketFinishConnect(self0 uint32, result *cm.OKResult[cm.Tuple[streams.InputStream, streams.OutputStream], network.ErrorCode]) + +// FinishListen represents the imported method "finish-listen". +// +// finish-listen: func() -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) FinishListen() (result cm.ErrResult[struct{}, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketFinishListen((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.finish-listen +//go:noescape +func wasmimport_TCPSocketFinishListen(self0 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) + +// HopLimit represents the imported method "hop-limit". +// +// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. +// +// If the provided value is 0, an `invalid-argument` error is returned. +// +// # Typical errors +// - `invalid-argument`: (set) The TTL value must be 1 or higher. +// +// hop-limit: func() -> result +// +//go:nosplit +func (self TCPSocket) HopLimit() (result cm.OKResult[uint8, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketHopLimit((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.hop-limit +//go:noescape +func wasmimport_TCPSocketHopLimit(self0 uint32, result *cm.OKResult[uint8, network.ErrorCode]) + +// IsListening represents the imported method "is-listening". +// +// Whether the socket is in the `listening` state. +// +// Equivalent to the SO_ACCEPTCONN socket option. +// +// is-listening: func() -> bool +// +//go:nosplit +func (self TCPSocket) IsListening() (result bool) { + self0 := cm.Reinterpret[uint32](self) + result0 := wasmimport_TCPSocketIsListening((uint32)(self0)) + result = cm.U32ToBool((uint32)(result0)) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.is-listening +//go:noescape +func wasmimport_TCPSocketIsListening(self0 uint32) (result0 uint32) + +// KeepAliveCount represents the imported method "keep-alive-count". +// +// The maximum amount of keepalive packets TCP should send before aborting the connection. +// +// If the provided value is 0, an `invalid-argument` error is returned. +// Any other value will never cause an error, but it might be silently clamped and/or +// rounded. +// I.e. after setting a value, reading the same setting back may return a different +// value. +// +// Equivalent to the TCP_KEEPCNT socket option. +// +// # Typical errors +// - `invalid-argument`: (set) The provided value was 0. +// +// keep-alive-count: func() -> result +// +//go:nosplit +func (self TCPSocket) KeepAliveCount() (result cm.OKResult[uint32, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketKeepAliveCount((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-count +//go:noescape +func wasmimport_TCPSocketKeepAliveCount(self0 uint32, result *cm.OKResult[uint32, network.ErrorCode]) + +// KeepAliveEnabled represents the imported method "keep-alive-enabled". +// +// Enables or disables keepalive. +// +// The keepalive behavior can be adjusted using: +// - `keep-alive-idle-time` +// - `keep-alive-interval` +// - `keep-alive-count` +// These properties can be configured while `keep-alive-enabled` is false, but only +// come into effect when `keep-alive-enabled` is true. +// +// Equivalent to the SO_KEEPALIVE socket option. +// +// keep-alive-enabled: func() -> result +// +//go:nosplit +func (self TCPSocket) KeepAliveEnabled() (result cm.OKResult[bool, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketKeepAliveEnabled((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-enabled +//go:noescape +func wasmimport_TCPSocketKeepAliveEnabled(self0 uint32, result *cm.OKResult[bool, network.ErrorCode]) + +// KeepAliveIdleTime represents the imported method "keep-alive-idle-time". +// +// Amount of time the connection has to be idle before TCP starts sending keepalive +// packets. +// +// If the provided value is 0, an `invalid-argument` error is returned. +// Any other value will never cause an error, but it might be silently clamped and/or +// rounded. +// I.e. after setting a value, reading the same setting back may return a different +// value. +// +// Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS) +// +// # Typical errors +// - `invalid-argument`: (set) The provided value was 0. +// +// keep-alive-idle-time: func() -> result +// +//go:nosplit +func (self TCPSocket) KeepAliveIdleTime() (result cm.OKResult[monotonicclock.Duration, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketKeepAliveIdleTime((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-idle-time +//go:noescape +func wasmimport_TCPSocketKeepAliveIdleTime(self0 uint32, result *cm.OKResult[monotonicclock.Duration, network.ErrorCode]) + +// KeepAliveInterval represents the imported method "keep-alive-interval". +// +// The time between keepalive packets. +// +// If the provided value is 0, an `invalid-argument` error is returned. +// Any other value will never cause an error, but it might be silently clamped and/or +// rounded. +// I.e. after setting a value, reading the same setting back may return a different +// value. +// +// Equivalent to the TCP_KEEPINTVL socket option. +// +// # Typical errors +// - `invalid-argument`: (set) The provided value was 0. +// +// keep-alive-interval: func() -> result +// +//go:nosplit +func (self TCPSocket) KeepAliveInterval() (result cm.OKResult[monotonicclock.Duration, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketKeepAliveInterval((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-interval +//go:noescape +func wasmimport_TCPSocketKeepAliveInterval(self0 uint32, result *cm.OKResult[monotonicclock.Duration, network.ErrorCode]) + +// LocalAddress represents the imported method "local-address". +// +// Get the bound local address. +// +// POSIX mentions: +// > If the socket has not been bound to a local name, the value +// > stored in the object pointed to by `address` is unspecified. +// +// WASI is stricter and requires `local-address` to return `invalid-state` when the +// socket hasn't been bound yet. +// +// # Typical errors +// - `invalid-state`: The socket is not bound to any local address. +// +// # References +// - +// - +// - +// - +// +// local-address: func() -> result +// +//go:nosplit +func (self TCPSocket) LocalAddress() (result cm.OKResult[network.IPSocketAddress, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketLocalAddress((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.local-address +//go:noescape +func wasmimport_TCPSocketLocalAddress(self0 uint32, result *cm.OKResult[network.IPSocketAddress, network.ErrorCode]) + +// ReceiveBufferSize represents the imported method "receive-buffer-size". +// +// The kernel buffer space reserved for sends/receives on this socket. +// +// If the provided value is 0, an `invalid-argument` error is returned. +// Any other value will never cause an error, but it might be silently clamped and/or +// rounded. +// I.e. after setting a value, reading the same setting back may return a different +// value. +// +// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. +// +// # Typical errors +// - `invalid-argument`: (set) The provided value was 0. +// +// receive-buffer-size: func() -> result +// +//go:nosplit +func (self TCPSocket) ReceiveBufferSize() (result cm.OKResult[uint64, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketReceiveBufferSize((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.receive-buffer-size +//go:noescape +func wasmimport_TCPSocketReceiveBufferSize(self0 uint32, result *cm.OKResult[uint64, network.ErrorCode]) + +// RemoteAddress represents the imported method "remote-address". +// +// Get the remote address. +// +// # Typical errors +// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) +// +// # References +// - +// - +// - +// - +// +// remote-address: func() -> result +// +//go:nosplit +func (self TCPSocket) RemoteAddress() (result cm.OKResult[network.IPSocketAddress, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketRemoteAddress((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.remote-address +//go:noescape +func wasmimport_TCPSocketRemoteAddress(self0 uint32, result *cm.OKResult[network.IPSocketAddress, network.ErrorCode]) + +// SendBufferSize represents the imported method "send-buffer-size". +// +// send-buffer-size: func() -> result +// +//go:nosplit +func (self TCPSocket) SendBufferSize() (result cm.OKResult[uint64, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketSendBufferSize((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.send-buffer-size +//go:noescape +func wasmimport_TCPSocketSendBufferSize(self0 uint32, result *cm.OKResult[uint64, network.ErrorCode]) + +// SetHopLimit represents the imported method "set-hop-limit". +// +// set-hop-limit: func(value: u8) -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) SetHopLimit(value uint8) (result cm.ErrResult[struct{}, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + value0 := (uint32)(value) + wasmimport_TCPSocketSetHopLimit((uint32)(self0), (uint32)(value0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-hop-limit +//go:noescape +func wasmimport_TCPSocketSetHopLimit(self0 uint32, value0 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) + +// SetKeepAliveCount represents the imported method "set-keep-alive-count". +// +// set-keep-alive-count: func(value: u32) -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) SetKeepAliveCount(value uint32) (result cm.ErrResult[struct{}, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + value0 := (uint32)(value) + wasmimport_TCPSocketSetKeepAliveCount((uint32)(self0), (uint32)(value0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-count +//go:noescape +func wasmimport_TCPSocketSetKeepAliveCount(self0 uint32, value0 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) + +// SetKeepAliveEnabled represents the imported method "set-keep-alive-enabled". +// +// set-keep-alive-enabled: func(value: bool) -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) SetKeepAliveEnabled(value bool) (result cm.ErrResult[struct{}, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + value0 := cm.BoolToU32(value) + wasmimport_TCPSocketSetKeepAliveEnabled((uint32)(self0), (uint32)(value0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-enabled +//go:noescape +func wasmimport_TCPSocketSetKeepAliveEnabled(self0 uint32, value0 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) + +// SetKeepAliveIdleTime represents the imported method "set-keep-alive-idle-time". +// +// set-keep-alive-idle-time: func(value: duration) -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) SetKeepAliveIdleTime(value monotonicclock.Duration) (result cm.ErrResult[struct{}, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + value0 := (uint64)(value) + wasmimport_TCPSocketSetKeepAliveIdleTime((uint32)(self0), (uint64)(value0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-idle-time +//go:noescape +func wasmimport_TCPSocketSetKeepAliveIdleTime(self0 uint32, value0 uint64, result *cm.ErrResult[struct{}, network.ErrorCode]) + +// SetKeepAliveInterval represents the imported method "set-keep-alive-interval". +// +// set-keep-alive-interval: func(value: duration) -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) SetKeepAliveInterval(value monotonicclock.Duration) (result cm.ErrResult[struct{}, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + value0 := (uint64)(value) + wasmimport_TCPSocketSetKeepAliveInterval((uint32)(self0), (uint64)(value0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-interval +//go:noescape +func wasmimport_TCPSocketSetKeepAliveInterval(self0 uint32, value0 uint64, result *cm.ErrResult[struct{}, network.ErrorCode]) + +// SetListenBacklogSize represents the imported method "set-listen-backlog-size". +// +// Hints the desired listen queue size. Implementations are free to ignore this. +// +// If the provided value is 0, an `invalid-argument` error is returned. +// Any other value will never cause an error, but it might be silently clamped and/or +// rounded. +// +// # Typical errors +// - `not-supported`: (set) The platform does not support changing the backlog +// size after the initial listen. +// - `invalid-argument`: (set) The provided value was 0. +// - `invalid-state`: (set) The socket is in the `connect-in-progress` or `connected` +// state. +// +// set-listen-backlog-size: func(value: u64) -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) SetListenBacklogSize(value uint64) (result cm.ErrResult[struct{}, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + value0 := (uint64)(value) + wasmimport_TCPSocketSetListenBacklogSize((uint32)(self0), (uint64)(value0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-listen-backlog-size +//go:noescape +func wasmimport_TCPSocketSetListenBacklogSize(self0 uint32, value0 uint64, result *cm.ErrResult[struct{}, network.ErrorCode]) + +// SetReceiveBufferSize represents the imported method "set-receive-buffer-size". +// +// set-receive-buffer-size: func(value: u64) -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) SetReceiveBufferSize(value uint64) (result cm.ErrResult[struct{}, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + value0 := (uint64)(value) + wasmimport_TCPSocketSetReceiveBufferSize((uint32)(self0), (uint64)(value0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-receive-buffer-size +//go:noescape +func wasmimport_TCPSocketSetReceiveBufferSize(self0 uint32, value0 uint64, result *cm.ErrResult[struct{}, network.ErrorCode]) + +// SetSendBufferSize represents the imported method "set-send-buffer-size". +// +// set-send-buffer-size: func(value: u64) -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) SetSendBufferSize(value uint64) (result cm.ErrResult[struct{}, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + value0 := (uint64)(value) + wasmimport_TCPSocketSetSendBufferSize((uint32)(self0), (uint64)(value0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-send-buffer-size +//go:noescape +func wasmimport_TCPSocketSetSendBufferSize(self0 uint32, value0 uint64, result *cm.ErrResult[struct{}, network.ErrorCode]) + +// Shutdown represents the imported method "shutdown". +// +// Initiate a graceful shutdown. +// +// - `receive`: The socket is not expecting to receive any data from +// the peer. The `input-stream` associated with this socket will be +// closed. Any data still in the receive queue at time of calling +// this method will be discarded. +// - `send`: The socket has no more data to send to the peer. The `output-stream` +// associated with this socket will be closed and a FIN packet will be sent. +// - `both`: Same effect as `receive` & `send` combined. +// +// This function is idempotent. Shutting a down a direction more than once +// has no effect and returns `ok`. +// +// The shutdown function does not close (drop) the socket. +// +// # Typical errors +// - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) +// +// # References +// - +// - +// - +// - +// +// shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) Shutdown(shutdownType ShutdownType) (result cm.ErrResult[struct{}, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + shutdownType0 := (uint32)(shutdownType) + wasmimport_TCPSocketShutdown((uint32)(self0), (uint32)(shutdownType0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.shutdown +//go:noescape +func wasmimport_TCPSocketShutdown(self0 uint32, shutdownType0 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) + +// StartBind represents the imported method "start-bind". +// +// Bind the socket to a specific network on the provided IP address and port. +// +// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the +// implementation to decide which +// network interface(s) to bind to. +// If the TCP/UDP port is zero, the socket will be bound to a random free port. +// +// Bind can be attempted multiple times on the same socket, even with +// different arguments on each iteration. But never concurrently and +// only as long as the previous bind failed. Once a bind succeeds, the +// binding can't be changed anymore. +// +// # Typical errors +// - `invalid-argument`: The `local-address` has the wrong address family. +// (EAFNOSUPPORT, EFAULT on Windows) +// - `invalid-argument`: `local-address` is not a unicast address. (EINVAL) +// - `invalid-argument`: `local-address` is an IPv4-mapped IPv6 address. +// (EINVAL) +// - `invalid-state`: The socket is already bound. (EINVAL) +// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS +// on Windows) +// - `address-in-use`: Address is already in use. (EADDRINUSE) +// - `address-not-bindable`: `local-address` is not an address that the `network` +// can bind to. (EADDRNOTAVAIL) +// - `not-in-progress`: A `bind` operation is not in progress. +// - `would-block`: Can't finish the operation, it is still in progress. +// (EWOULDBLOCK, EAGAIN) +// +// # Implementors note +// When binding to a non-zero port, this bind operation shouldn't be affected by the +// TIME_WAIT +// state of a recently closed socket on the same local address. In practice this means +// that the SO_REUSEADDR +// socket option should be set implicitly on all platforms, except on Windows where +// this is the default behavior +// and SO_REUSEADDR performs something different entirely. +// +// Unlike in POSIX, in WASI the bind operation is async. This enables +// interactive WASI hosts to inject permission prompts. Runtimes that +// don't want to make use of this ability can simply call the native +// `bind` as part of either `start-bind` or `finish-bind`. +// +// # References +// - +// - +// - +// - +// +// start-bind: func(network: borrow, local-address: ip-socket-address) -> +// result<_, error-code> +// +//go:nosplit +func (self TCPSocket) StartBind(network_ network.Network, localAddress network.IPSocketAddress) (result cm.ErrResult[struct{}, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + network0 := cm.Reinterpret[uint32](network_) + localAddress0, localAddress1, localAddress2, localAddress3, localAddress4, localAddress5, localAddress6, localAddress7, localAddress8, localAddress9, localAddress10, localAddress11 := lower_IPSocketAddress(localAddress) + wasmimport_TCPSocketStartBind((uint32)(self0), (uint32)(network0), (uint32)(localAddress0), (uint32)(localAddress1), (uint32)(localAddress2), (uint32)(localAddress3), (uint32)(localAddress4), (uint32)(localAddress5), (uint32)(localAddress6), (uint32)(localAddress7), (uint32)(localAddress8), (uint32)(localAddress9), (uint32)(localAddress10), (uint32)(localAddress11), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.start-bind +//go:noescape +func wasmimport_TCPSocketStartBind(self0 uint32, network0 uint32, localAddress0 uint32, localAddress1 uint32, localAddress2 uint32, localAddress3 uint32, localAddress4 uint32, localAddress5 uint32, localAddress6 uint32, localAddress7 uint32, localAddress8 uint32, localAddress9 uint32, localAddress10 uint32, localAddress11 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) + +// StartConnect represents the imported method "start-connect". +// +// Connect to a remote endpoint. +// +// On success: +// - the socket is transitioned into the `connection` state. +// - a pair of streams is returned that can be used to read & write to the connection +// +// After a failed connection attempt, the socket will be in the `closed` +// state and the only valid action left is to `drop` the socket. A single +// socket can not be used to connect more than once. +// +// # Typical errors +// - `invalid-argument`: The `remote-address` has the wrong address family. +// (EAFNOSUPPORT) +// - `invalid-argument`: `remote-address` is not a unicast address. (EINVAL, +// ENETUNREACH on Linux, EAFNOSUPPORT on MacOS) +// - `invalid-argument`: `remote-address` is an IPv4-mapped IPv6 address. +// (EINVAL, EADDRNOTAVAIL on Illumos) +// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY +// (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) +// - `invalid-argument`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL +// on Windows) +// - `invalid-argument`: The socket is already attached to a different network. +// The `network` passed to `connect` must be identical to the one passed to `bind`. +// - `invalid-state`: The socket is already in the `connected` state. +// (EISCONN) +// - `invalid-state`: The socket is already in the `listening` state. +// (EOPNOTSUPP, EINVAL on Windows) +// - `timeout`: Connection timed out. (ETIMEDOUT) +// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) +// - `connection-reset`: The connection was reset. (ECONNRESET) +// - `connection-aborted`: The connection was aborted. (ECONNABORTED) +// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, +// EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) +// - `address-in-use`: Tried to perform an implicit bind, but there were +// no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) +// - `not-in-progress`: A connect operation is not in progress. +// - `would-block`: Can't finish the operation, it is still in progress. +// (EWOULDBLOCK, EAGAIN) +// +// # Implementors note +// The POSIX equivalent of `start-connect` is the regular `connect` syscall. +// Because all WASI sockets are non-blocking this is expected to return +// EINPROGRESS, which should be translated to `ok()` in WASI. +// +// The POSIX equivalent of `finish-connect` is a `poll` for event `POLLOUT` +// with a timeout of 0 on the socket descriptor. Followed by a check for +// the `SO_ERROR` socket option, in case the poll signaled readiness. +// +// # References +// - +// - +// - +// - +// +// start-connect: func(network: borrow, remote-address: ip-socket-address) +// -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) StartConnect(network_ network.Network, remoteAddress network.IPSocketAddress) (result cm.ErrResult[struct{}, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + network0 := cm.Reinterpret[uint32](network_) + remoteAddress0, remoteAddress1, remoteAddress2, remoteAddress3, remoteAddress4, remoteAddress5, remoteAddress6, remoteAddress7, remoteAddress8, remoteAddress9, remoteAddress10, remoteAddress11 := lower_IPSocketAddress(remoteAddress) + wasmimport_TCPSocketStartConnect((uint32)(self0), (uint32)(network0), (uint32)(remoteAddress0), (uint32)(remoteAddress1), (uint32)(remoteAddress2), (uint32)(remoteAddress3), (uint32)(remoteAddress4), (uint32)(remoteAddress5), (uint32)(remoteAddress6), (uint32)(remoteAddress7), (uint32)(remoteAddress8), (uint32)(remoteAddress9), (uint32)(remoteAddress10), (uint32)(remoteAddress11), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.start-connect +//go:noescape +func wasmimport_TCPSocketStartConnect(self0 uint32, network0 uint32, remoteAddress0 uint32, remoteAddress1 uint32, remoteAddress2 uint32, remoteAddress3 uint32, remoteAddress4 uint32, remoteAddress5 uint32, remoteAddress6 uint32, remoteAddress7 uint32, remoteAddress8 uint32, remoteAddress9 uint32, remoteAddress10 uint32, remoteAddress11 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) + +// StartListen represents the imported method "start-listen". +// +// Start listening for new connections. +// +// Transitions the socket into the `listening` state. +// +// Unlike POSIX, the socket must already be explicitly bound. +// +// # Typical errors +// - `invalid-state`: The socket is not bound to any local address. (EDESTADDRREQ) +// - `invalid-state`: The socket is already in the `connected` state. +// (EISCONN, EINVAL on BSD) +// - `invalid-state`: The socket is already in the `listening` state. +// - `address-in-use`: Tried to perform an implicit bind, but there were +// no ephemeral ports available. (EADDRINUSE) +// - `not-in-progress`: A listen operation is not in progress. +// - `would-block`: Can't finish the operation, it is still in progress. +// (EWOULDBLOCK, EAGAIN) +// +// # Implementors note +// Unlike in POSIX, in WASI the listen operation is async. This enables +// interactive WASI hosts to inject permission prompts. Runtimes that +// don't want to make use of this ability can simply call the native +// `listen` as part of either `start-listen` or `finish-listen`. +// +// # References +// - +// - +// - +// - +// +// start-listen: func() -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) StartListen() (result cm.ErrResult[struct{}, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketStartListen((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.start-listen +//go:noescape +func wasmimport_TCPSocketStartListen(self0 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) + +// Subscribe represents the imported method "subscribe". +// +// Create a `pollable` which can be used to poll for, or block on, +// completion of any of the asynchronous operations of this socket. +// +// When `finish-bind`, `finish-listen`, `finish-connect` or `accept` +// return `error(would-block)`, this pollable can be used to wait for +// their success or failure, after which the method can be retried. +// +// The pollable is not limited to the async operation that happens to be +// in progress at the time of calling `subscribe` (if any). Theoretically, +// `subscribe` only has to be called once per socket and can then be +// (re)used for the remainder of the socket's lifetime. +// +// See +// for a more information. +// +// Note: this function is here for WASI Preview2 only. +// It's planned to be removed when `future` is natively supported in Preview3. +// +// subscribe: func() -> pollable +// +//go:nosplit +func (self TCPSocket) Subscribe() (result poll.Pollable) { + self0 := cm.Reinterpret[uint32](self) + result0 := wasmimport_TCPSocketSubscribe((uint32)(self0)) + result = cm.Reinterpret[poll.Pollable]((uint32)(result0)) + return +} + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.subscribe +//go:noescape +func wasmimport_TCPSocketSubscribe(self0 uint32) (result0 uint32) diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/empty.s b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go new file mode 100644 index 0000000000..a73695a9c4 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go @@ -0,0 +1,54 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package udpcreatesocket represents the imported interface "wasi:sockets/udp-create-socket@0.2.0". +package udpcreatesocket + +import ( + "github.com/ydnar/wasm-tools-go/cm" + "internal/wasi/sockets/v0.2.0/network" + "internal/wasi/sockets/v0.2.0/udp" +) + +// CreateUDPSocket represents the imported function "create-udp-socket". +// +// Create a new UDP socket. +// +// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. +// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. +// +// This function does not require a network capability handle. This is considered +// to be safe because +// at time of creation, the socket is not bound to any `network` yet. Up to the moment +// `bind` is called, +// the socket is effectively an in-memory configuration object, unable to communicate +// with the outside world. +// +// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous +// operations. +// +// # Typical errors +// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) +// - `new-socket-limit`: The new socket resource could not be created because of +// a system limit. (EMFILE, ENFILE) +// +// # References: +// - +// - +// - +// - +// +// create-udp-socket: func(address-family: ip-address-family) -> result +// +//go:nosplit +func CreateUDPSocket(addressFamily network.IPAddressFamily) (result cm.OKResult[udp.UDPSocket, network.ErrorCode]) { + addressFamily0 := (uint32)(addressFamily) + wasmimport_CreateUDPSocket((uint32)(addressFamily0), &result) + return +} + +//go:wasmimport wasi:sockets/udp-create-socket@0.2.0 create-udp-socket +//go:noescape +func wasmimport_CreateUDPSocket(addressFamily0 uint32, result *cm.OKResult[udp.UDPSocket, network.ErrorCode]) diff --git a/src/internal/wasi/sockets/v0.2.0/udp/abi.go b/src/internal/wasi/sockets/v0.2.0/udp/abi.go new file mode 100644 index 0000000000..c62d3322dd --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/udp/abi.go @@ -0,0 +1,92 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +package udp + +import ( + "github.com/ydnar/wasm-tools-go/cm" + "internal/wasi/sockets/v0.2.0/network" +) + +func lower_IPv4Address(v network.IPv4Address) (f0 uint32, f1 uint32, f2 uint32, f3 uint32) { + f0 = (uint32)(v[0]) + f1 = (uint32)(v[1]) + f2 = (uint32)(v[2]) + f3 = (uint32)(v[3]) + return +} + +func lower_IPv4SocketAddress(v network.IPv4SocketAddress) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32) { + f0 = (uint32)(v.Port) + f1, f2, f3, f4 = lower_IPv4Address(v.Address) + return +} + +func lower_IPv6Address(v network.IPv6Address) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32) { + f0 = (uint32)(v[0]) + f1 = (uint32)(v[1]) + f2 = (uint32)(v[2]) + f3 = (uint32)(v[3]) + f4 = (uint32)(v[4]) + f5 = (uint32)(v[5]) + f6 = (uint32)(v[6]) + f7 = (uint32)(v[7]) + return +} + +func lower_IPv6SocketAddress(v network.IPv6SocketAddress) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32, f8 uint32, f9 uint32, f10 uint32) { + f0 = (uint32)(v.Port) + f1 = (uint32)(v.FlowInfo) + f2, f3, f4, f5, f6, f7, f8, f9 = lower_IPv6Address(v.Address) + f10 = (uint32)(v.ScopeID) + return +} + +func lower_IPSocketAddress(v network.IPSocketAddress) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32, f8 uint32, f9 uint32, f10 uint32, f11 uint32) { + f0 = (uint32)(cm.Tag(&v)) + switch f0 { + case 0: // ipv4 + v1, v2, v3, v4, v5 := lower_IPv4SocketAddress(*v.IPv4()) + f1 = (uint32)(v1) + f2 = (uint32)(v2) + f3 = (uint32)(v3) + f4 = (uint32)(v4) + f5 = (uint32)(v5) + case 1: // ipv6 + v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 := lower_IPv6SocketAddress(*v.IPv6()) + f1 = (uint32)(v1) + f2 = (uint32)(v2) + f3 = (uint32)(v3) + f4 = (uint32)(v4) + f5 = (uint32)(v5) + f6 = (uint32)(v6) + f7 = (uint32)(v7) + f8 = (uint32)(v8) + f9 = (uint32)(v9) + f10 = (uint32)(v10) + f11 = (uint32)(v11) + } + return +} + +func lower_OptionIPSocketAddress(v cm.Option[network.IPSocketAddress]) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32, f8 uint32, f9 uint32, f10 uint32, f11 uint32, f12 uint32) { + some := v.Some() + if some != nil { + f0 = 1 + v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12 := lower_IPSocketAddress(*some) + f1 = (uint32)(v1) + f2 = (uint32)(v2) + f3 = (uint32)(v3) + f4 = (uint32)(v4) + f5 = (uint32)(v5) + f6 = (uint32)(v6) + f7 = (uint32)(v7) + f8 = (uint32)(v8) + f9 = (uint32)(v9) + f10 = (uint32)(v10) + f11 = (uint32)(v11) + f12 = (uint32)(v12) + } + return +} diff --git a/src/internal/wasi/sockets/v0.2.0/udp/empty.s b/src/internal/wasi/sockets/v0.2.0/udp/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/udp/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go b/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go new file mode 100644 index 0000000000..41b3b9ac0c --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go @@ -0,0 +1,643 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +// Package udp represents the imported interface "wasi:sockets/udp@0.2.0". +package udp + +import ( + "github.com/ydnar/wasm-tools-go/cm" + "internal/wasi/io/v0.2.0/poll" + "internal/wasi/sockets/v0.2.0/network" +) + +// IncomingDatagram represents the record "wasi:sockets/udp@0.2.0#incoming-datagram". +// +// A received datagram. +// +// record incoming-datagram { +// data: list, +// remote-address: ip-socket-address, +// } +type IncomingDatagram struct { + // The payload. + // + // Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. + Data cm.List[uint8] + + // The source address. + // + // This field is guaranteed to match the remote address the stream was initialized + // with, if any. + // + // Equivalent to the `src_addr` out parameter of `recvfrom`. + RemoteAddress network.IPSocketAddress +} + +// OutgoingDatagram represents the record "wasi:sockets/udp@0.2.0#outgoing-datagram". +// +// A datagram to be sent out. +// +// record outgoing-datagram { +// data: list, +// remote-address: option, +// } +type OutgoingDatagram struct { + // The payload. + Data cm.List[uint8] + + // The destination address. + // + // The requirements on this field depend on how the stream was initialized: + // - with a remote address: this field must be None or match the stream's remote address + // exactly. + // - without a remote address: this field is required. + // + // If this value is None, the send operation is equivalent to `send` in POSIX. Otherwise + // it is equivalent to `sendto`. + RemoteAddress cm.Option[network.IPSocketAddress] +} + +// UDPSocket represents the imported resource "wasi:sockets/udp@0.2.0#udp-socket". +// +// A UDP socket handle. +// +// resource udp-socket +type UDPSocket cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "udp-socket". +// +// Drops a resource handle. +// +//go:nosplit +func (self UDPSocket) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_UDPSocketResourceDrop((uint32)(self0)) + return +} + +//go:wasmimport wasi:sockets/udp@0.2.0 [resource-drop]udp-socket +//go:noescape +func wasmimport_UDPSocketResourceDrop(self0 uint32) + +// AddressFamily represents the imported method "address-family". +// +// Whether this is a IPv4 or IPv6 socket. +// +// Equivalent to the SO_DOMAIN socket option. +// +// address-family: func() -> ip-address-family +// +//go:nosplit +func (self UDPSocket) AddressFamily() (result network.IPAddressFamily) { + self0 := cm.Reinterpret[uint32](self) + result0 := wasmimport_UDPSocketAddressFamily((uint32)(self0)) + result = (network.IPAddressFamily)((uint32)(result0)) + return +} + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.address-family +//go:noescape +func wasmimport_UDPSocketAddressFamily(self0 uint32) (result0 uint32) + +// FinishBind represents the imported method "finish-bind". +// +// finish-bind: func() -> result<_, error-code> +// +//go:nosplit +func (self UDPSocket) FinishBind() (result cm.ErrResult[struct{}, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_UDPSocketFinishBind((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.finish-bind +//go:noescape +func wasmimport_UDPSocketFinishBind(self0 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) + +// LocalAddress represents the imported method "local-address". +// +// Get the current bound address. +// +// POSIX mentions: +// > If the socket has not been bound to a local name, the value +// > stored in the object pointed to by `address` is unspecified. +// +// WASI is stricter and requires `local-address` to return `invalid-state` when the +// socket hasn't been bound yet. +// +// # Typical errors +// - `invalid-state`: The socket is not bound to any local address. +// +// # References +// - +// - +// - +// - +// +// local-address: func() -> result +// +//go:nosplit +func (self UDPSocket) LocalAddress() (result cm.OKResult[network.IPSocketAddress, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_UDPSocketLocalAddress((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.local-address +//go:noescape +func wasmimport_UDPSocketLocalAddress(self0 uint32, result *cm.OKResult[network.IPSocketAddress, network.ErrorCode]) + +// ReceiveBufferSize represents the imported method "receive-buffer-size". +// +// The kernel buffer space reserved for sends/receives on this socket. +// +// If the provided value is 0, an `invalid-argument` error is returned. +// Any other value will never cause an error, but it might be silently clamped and/or +// rounded. +// I.e. after setting a value, reading the same setting back may return a different +// value. +// +// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. +// +// # Typical errors +// - `invalid-argument`: (set) The provided value was 0. +// +// receive-buffer-size: func() -> result +// +//go:nosplit +func (self UDPSocket) ReceiveBufferSize() (result cm.OKResult[uint64, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_UDPSocketReceiveBufferSize((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.receive-buffer-size +//go:noescape +func wasmimport_UDPSocketReceiveBufferSize(self0 uint32, result *cm.OKResult[uint64, network.ErrorCode]) + +// RemoteAddress represents the imported method "remote-address". +// +// Get the address the socket is currently streaming to. +// +// # Typical errors +// - `invalid-state`: The socket is not streaming to a specific remote address. (ENOTCONN) +// +// # References +// - +// - +// - +// - +// +// remote-address: func() -> result +// +//go:nosplit +func (self UDPSocket) RemoteAddress() (result cm.OKResult[network.IPSocketAddress, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_UDPSocketRemoteAddress((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.remote-address +//go:noescape +func wasmimport_UDPSocketRemoteAddress(self0 uint32, result *cm.OKResult[network.IPSocketAddress, network.ErrorCode]) + +// SendBufferSize represents the imported method "send-buffer-size". +// +// send-buffer-size: func() -> result +// +//go:nosplit +func (self UDPSocket) SendBufferSize() (result cm.OKResult[uint64, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_UDPSocketSendBufferSize((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.send-buffer-size +//go:noescape +func wasmimport_UDPSocketSendBufferSize(self0 uint32, result *cm.OKResult[uint64, network.ErrorCode]) + +// SetReceiveBufferSize represents the imported method "set-receive-buffer-size". +// +// set-receive-buffer-size: func(value: u64) -> result<_, error-code> +// +//go:nosplit +func (self UDPSocket) SetReceiveBufferSize(value uint64) (result cm.ErrResult[struct{}, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + value0 := (uint64)(value) + wasmimport_UDPSocketSetReceiveBufferSize((uint32)(self0), (uint64)(value0), &result) + return +} + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.set-receive-buffer-size +//go:noescape +func wasmimport_UDPSocketSetReceiveBufferSize(self0 uint32, value0 uint64, result *cm.ErrResult[struct{}, network.ErrorCode]) + +// SetSendBufferSize represents the imported method "set-send-buffer-size". +// +// set-send-buffer-size: func(value: u64) -> result<_, error-code> +// +//go:nosplit +func (self UDPSocket) SetSendBufferSize(value uint64) (result cm.ErrResult[struct{}, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + value0 := (uint64)(value) + wasmimport_UDPSocketSetSendBufferSize((uint32)(self0), (uint64)(value0), &result) + return +} + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.set-send-buffer-size +//go:noescape +func wasmimport_UDPSocketSetSendBufferSize(self0 uint32, value0 uint64, result *cm.ErrResult[struct{}, network.ErrorCode]) + +// SetUnicastHopLimit represents the imported method "set-unicast-hop-limit". +// +// set-unicast-hop-limit: func(value: u8) -> result<_, error-code> +// +//go:nosplit +func (self UDPSocket) SetUnicastHopLimit(value uint8) (result cm.ErrResult[struct{}, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + value0 := (uint32)(value) + wasmimport_UDPSocketSetUnicastHopLimit((uint32)(self0), (uint32)(value0), &result) + return +} + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.set-unicast-hop-limit +//go:noescape +func wasmimport_UDPSocketSetUnicastHopLimit(self0 uint32, value0 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) + +// StartBind represents the imported method "start-bind". +// +// Bind the socket to a specific network on the provided IP address and port. +// +// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the +// implementation to decide which +// network interface(s) to bind to. +// If the port is zero, the socket will be bound to a random free port. +// +// # Typical errors +// - `invalid-argument`: The `local-address` has the wrong address family. +// (EAFNOSUPPORT, EFAULT on Windows) +// - `invalid-state`: The socket is already bound. (EINVAL) +// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS +// on Windows) +// - `address-in-use`: Address is already in use. (EADDRINUSE) +// - `address-not-bindable`: `local-address` is not an address that the `network` +// can bind to. (EADDRNOTAVAIL) +// - `not-in-progress`: A `bind` operation is not in progress. +// - `would-block`: Can't finish the operation, it is still in progress. +// (EWOULDBLOCK, EAGAIN) +// +// # Implementors note +// Unlike in POSIX, in WASI the bind operation is async. This enables +// interactive WASI hosts to inject permission prompts. Runtimes that +// don't want to make use of this ability can simply call the native +// `bind` as part of either `start-bind` or `finish-bind`. +// +// # References +// - +// - +// - +// - +// +// start-bind: func(network: borrow, local-address: ip-socket-address) -> +// result<_, error-code> +// +//go:nosplit +func (self UDPSocket) StartBind(network_ network.Network, localAddress network.IPSocketAddress) (result cm.ErrResult[struct{}, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + network0 := cm.Reinterpret[uint32](network_) + localAddress0, localAddress1, localAddress2, localAddress3, localAddress4, localAddress5, localAddress6, localAddress7, localAddress8, localAddress9, localAddress10, localAddress11 := lower_IPSocketAddress(localAddress) + wasmimport_UDPSocketStartBind((uint32)(self0), (uint32)(network0), (uint32)(localAddress0), (uint32)(localAddress1), (uint32)(localAddress2), (uint32)(localAddress3), (uint32)(localAddress4), (uint32)(localAddress5), (uint32)(localAddress6), (uint32)(localAddress7), (uint32)(localAddress8), (uint32)(localAddress9), (uint32)(localAddress10), (uint32)(localAddress11), &result) + return +} + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.start-bind +//go:noescape +func wasmimport_UDPSocketStartBind(self0 uint32, network0 uint32, localAddress0 uint32, localAddress1 uint32, localAddress2 uint32, localAddress3 uint32, localAddress4 uint32, localAddress5 uint32, localAddress6 uint32, localAddress7 uint32, localAddress8 uint32, localAddress9 uint32, localAddress10 uint32, localAddress11 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) + +// Stream represents the imported method "stream". +// +// Set up inbound & outbound communication channels, optionally to a specific peer. +// +// This function only changes the local socket configuration and does not generate +// any network traffic. +// On success, the `remote-address` of the socket is updated. The `local-address` +// may be updated as well, +// based on the best network path to `remote-address`. +// +// When a `remote-address` is provided, the returned streams are limited to communicating +// with that specific peer: +// - `send` can only be used to send to this destination. +// - `receive` will only return datagrams sent from the provided `remote-address`. +// +// This method may be called multiple times on the same socket to change its association, +// but +// only the most recently returned pair of streams will be operational. Implementations +// may trap if +// the streams returned by a previous invocation haven't been dropped yet before calling +// `stream` again. +// +// The POSIX equivalent in pseudo-code is: +// +// if (was previously connected) { +// connect(s, AF_UNSPEC) +// } +// if (remote_address is Some) { +// connect(s, remote_address) +// } +// +// Unlike in POSIX, the socket must already be explicitly bound. +// +// # Typical errors +// - `invalid-argument`: The `remote-address` has the wrong address family. +// (EAFNOSUPPORT) +// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY +// (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) +// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, +// EADDRNOTAVAIL) +// - `invalid-state`: The socket is not bound. +// - `address-in-use`: Tried to perform an implicit bind, but there were +// no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) +// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, +// ENETRESET, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) +// - `connection-refused`: The connection was refused. (ECONNREFUSED) +// +// # References +// - +// - +// - +// - +// +// %stream: func(remote-address: option) -> result, error-code> +// +//go:nosplit +func (self UDPSocket) Stream(remoteAddress cm.Option[network.IPSocketAddress]) (result cm.OKResult[cm.Tuple[IncomingDatagramStream, OutgoingDatagramStream], network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + remoteAddress0, remoteAddress1, remoteAddress2, remoteAddress3, remoteAddress4, remoteAddress5, remoteAddress6, remoteAddress7, remoteAddress8, remoteAddress9, remoteAddress10, remoteAddress11, remoteAddress12 := lower_OptionIPSocketAddress(remoteAddress) + wasmimport_UDPSocketStream((uint32)(self0), (uint32)(remoteAddress0), (uint32)(remoteAddress1), (uint32)(remoteAddress2), (uint32)(remoteAddress3), (uint32)(remoteAddress4), (uint32)(remoteAddress5), (uint32)(remoteAddress6), (uint32)(remoteAddress7), (uint32)(remoteAddress8), (uint32)(remoteAddress9), (uint32)(remoteAddress10), (uint32)(remoteAddress11), (uint32)(remoteAddress12), &result) + return +} + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.stream +//go:noescape +func wasmimport_UDPSocketStream(self0 uint32, remoteAddress0 uint32, remoteAddress1 uint32, remoteAddress2 uint32, remoteAddress3 uint32, remoteAddress4 uint32, remoteAddress5 uint32, remoteAddress6 uint32, remoteAddress7 uint32, remoteAddress8 uint32, remoteAddress9 uint32, remoteAddress10 uint32, remoteAddress11 uint32, remoteAddress12 uint32, result *cm.OKResult[cm.Tuple[IncomingDatagramStream, OutgoingDatagramStream], network.ErrorCode]) + +// Subscribe represents the imported method "subscribe". +// +// Create a `pollable` which will resolve once the socket is ready for I/O. +// +// Note: this function is here for WASI Preview2 only. +// It's planned to be removed when `future` is natively supported in Preview3. +// +// subscribe: func() -> pollable +// +//go:nosplit +func (self UDPSocket) Subscribe() (result poll.Pollable) { + self0 := cm.Reinterpret[uint32](self) + result0 := wasmimport_UDPSocketSubscribe((uint32)(self0)) + result = cm.Reinterpret[poll.Pollable]((uint32)(result0)) + return +} + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.subscribe +//go:noescape +func wasmimport_UDPSocketSubscribe(self0 uint32) (result0 uint32) + +// UnicastHopLimit represents the imported method "unicast-hop-limit". +// +// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. +// +// If the provided value is 0, an `invalid-argument` error is returned. +// +// # Typical errors +// - `invalid-argument`: (set) The TTL value must be 1 or higher. +// +// unicast-hop-limit: func() -> result +// +//go:nosplit +func (self UDPSocket) UnicastHopLimit() (result cm.OKResult[uint8, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_UDPSocketUnicastHopLimit((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.unicast-hop-limit +//go:noescape +func wasmimport_UDPSocketUnicastHopLimit(self0 uint32, result *cm.OKResult[uint8, network.ErrorCode]) + +// IncomingDatagramStream represents the imported resource "wasi:sockets/udp@0.2.0#incoming-datagram-stream". +// +// resource incoming-datagram-stream +type IncomingDatagramStream cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "incoming-datagram-stream". +// +// Drops a resource handle. +// +//go:nosplit +func (self IncomingDatagramStream) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_IncomingDatagramStreamResourceDrop((uint32)(self0)) + return +} + +//go:wasmimport wasi:sockets/udp@0.2.0 [resource-drop]incoming-datagram-stream +//go:noescape +func wasmimport_IncomingDatagramStreamResourceDrop(self0 uint32) + +// Receive represents the imported method "receive". +// +// Receive messages on the socket. +// +// This function attempts to receive up to `max-results` datagrams on the socket without +// blocking. +// The returned list may contain fewer elements than requested, but never more. +// +// This function returns successfully with an empty list when either: +// - `max-results` is 0, or: +// - `max-results` is greater than 0, but no results are immediately available. +// This function never returns `error(would-block)`. +// +// # Typical errors +// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET +// on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) +// - `connection-refused`: The connection was refused. (ECONNREFUSED) +// +// # References +// - +// - +// - +// - +// - +// - +// - +// - +// +// receive: func(max-results: u64) -> result, error-code> +// +//go:nosplit +func (self IncomingDatagramStream) Receive(maxResults uint64) (result cm.OKResult[cm.List[IncomingDatagram], network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + maxResults0 := (uint64)(maxResults) + wasmimport_IncomingDatagramStreamReceive((uint32)(self0), (uint64)(maxResults0), &result) + return +} + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]incoming-datagram-stream.receive +//go:noescape +func wasmimport_IncomingDatagramStreamReceive(self0 uint32, maxResults0 uint64, result *cm.OKResult[cm.List[IncomingDatagram], network.ErrorCode]) + +// Subscribe represents the imported method "subscribe". +// +// Create a `pollable` which will resolve once the stream is ready to receive again. +// +// Note: this function is here for WASI Preview2 only. +// It's planned to be removed when `future` is natively supported in Preview3. +// +// subscribe: func() -> pollable +// +//go:nosplit +func (self IncomingDatagramStream) Subscribe() (result poll.Pollable) { + self0 := cm.Reinterpret[uint32](self) + result0 := wasmimport_IncomingDatagramStreamSubscribe((uint32)(self0)) + result = cm.Reinterpret[poll.Pollable]((uint32)(result0)) + return +} + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]incoming-datagram-stream.subscribe +//go:noescape +func wasmimport_IncomingDatagramStreamSubscribe(self0 uint32) (result0 uint32) + +// OutgoingDatagramStream represents the imported resource "wasi:sockets/udp@0.2.0#outgoing-datagram-stream". +// +// resource outgoing-datagram-stream +type OutgoingDatagramStream cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "outgoing-datagram-stream". +// +// Drops a resource handle. +// +//go:nosplit +func (self OutgoingDatagramStream) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_OutgoingDatagramStreamResourceDrop((uint32)(self0)) + return +} + +//go:wasmimport wasi:sockets/udp@0.2.0 [resource-drop]outgoing-datagram-stream +//go:noescape +func wasmimport_OutgoingDatagramStreamResourceDrop(self0 uint32) + +// CheckSend represents the imported method "check-send". +// +// Check readiness for sending. This function never blocks. +// +// Returns the number of datagrams permitted for the next call to `send`, +// or an error. Calling `send` with more datagrams than this function has +// permitted will trap. +// +// When this function returns ok(0), the `subscribe` pollable will +// become ready when this function will report at least ok(1), or an +// error. +// +// Never returns `would-block`. +// +// check-send: func() -> result +// +//go:nosplit +func (self OutgoingDatagramStream) CheckSend() (result cm.OKResult[uint64, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_OutgoingDatagramStreamCheckSend((uint32)(self0), &result) + return +} + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]outgoing-datagram-stream.check-send +//go:noescape +func wasmimport_OutgoingDatagramStreamCheckSend(self0 uint32, result *cm.OKResult[uint64, network.ErrorCode]) + +// Send represents the imported method "send". +// +// Send messages on the socket. +// +// This function attempts to send all provided `datagrams` on the socket without blocking +// and +// returns how many messages were actually sent (or queued for sending). This function +// never +// returns `error(would-block)`. If none of the datagrams were able to be sent, `ok(0)` +// is returned. +// +// This function semantically behaves the same as iterating the `datagrams` list and +// sequentially +// sending each individual datagram until either the end of the list has been reached +// or the first error occurred. +// If at least one datagram has been sent successfully, this function never returns +// an error. +// +// If the input list is empty, the function returns `ok(0)`. +// +// Each call to `send` must be permitted by a preceding `check-send`. Implementations +// must trap if +// either `check-send` was not called or `datagrams` contains more items than `check-send` +// permitted. +// +// # Typical errors +// - `invalid-argument`: The `remote-address` has the wrong address family. +// (EAFNOSUPPORT) +// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY +// (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) +// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, +// EADDRNOTAVAIL) +// - `invalid-argument`: The socket is in "connected" mode and `remote-address` +// is `some` value that does not match the address passed to `stream`. (EISCONN) +// - `invalid-argument`: The socket is not "connected" and no value for `remote-address` +// was provided. (EDESTADDRREQ) +// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, +// ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) +// - `connection-refused`: The connection was refused. (ECONNREFUSED) +// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) +// +// # References +// - +// - +// - +// - +// - +// - +// - +// - +// +// send: func(datagrams: list) -> result +// +//go:nosplit +func (self OutgoingDatagramStream) Send(datagrams cm.List[OutgoingDatagram]) (result cm.OKResult[uint64, network.ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + datagrams0, datagrams1 := cm.LowerList(datagrams) + wasmimport_OutgoingDatagramStreamSend((uint32)(self0), (*OutgoingDatagram)(datagrams0), (uint32)(datagrams1), &result) + return +} + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]outgoing-datagram-stream.send +//go:noescape +func wasmimport_OutgoingDatagramStreamSend(self0 uint32, datagrams0 *OutgoingDatagram, datagrams1 uint32, result *cm.OKResult[uint64, network.ErrorCode]) + +// Subscribe represents the imported method "subscribe". +// +// Create a `pollable` which will resolve once the stream is ready to send again. +// +// Note: this function is here for WASI Preview2 only. +// It's planned to be removed when `future` is natively supported in Preview3. +// +// subscribe: func() -> pollable +// +//go:nosplit +func (self OutgoingDatagramStream) Subscribe() (result poll.Pollable) { + self0 := cm.Reinterpret[uint32](self) + result0 := wasmimport_OutgoingDatagramStreamSubscribe((uint32)(self0)) + result = cm.Reinterpret[poll.Pollable]((uint32)(result0)) + return +} + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]outgoing-datagram-stream.subscribe +//go:noescape +func wasmimport_OutgoingDatagramStreamSubscribe(self0 uint32) (result0 uint32) diff --git a/src/os/dir_test.go b/src/os/dir_test.go index d661c98b44..e51e290b39 100644 --- a/src/os/dir_test.go +++ b/src/os/dir_test.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal && !js && !wasip1 && !386 && !arm) +//go:build darwin || (linux && !baremetal && !js && !wasip1 && !wasip2 && !386 && !arm) package os_test diff --git a/src/os/dir_unix.go b/src/os/dir_unix.go index 227dc9188d..03624e9cff 100644 --- a/src/os/dir_unix.go +++ b/src/os/dir_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build linux && !baremetal && !wasip1 && !wasm_unknown +//go:build linux && !baremetal && !wasip1 && !wasip2 && !wasm_unknown package os diff --git a/src/os/dir_wasip1.go b/src/os/dir_wasi.go similarity index 98% rename from src/os/dir_wasip1.go rename to src/os/dir_wasi.go index 0be0da4d64..6d9313110e 100644 --- a/src/os/dir_wasip1.go +++ b/src/os/dir_wasi.go @@ -6,7 +6,7 @@ // fairly similar: we use fdopendir, fdclosedir, and readdir from wasi-libc in // a similar way that the darwin code uses functions from libc. -//go:build wasip1 +//go:build wasip1 || wasip2 package os diff --git a/src/os/dirent_linux.go b/src/os/dirent_linux.go index 790c26890f..92e2b3a65b 100644 --- a/src/os/dirent_linux.go +++ b/src/os/dirent_linux.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !js && !wasip1 && !wasm_unknown +//go:build !baremetal && !js && !wasip1 && !wasip2 && !wasm_unknown // Copyright 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/env_unix_test.go b/src/os/env_unix_test.go index 034f481544..93dff91a14 100644 --- a/src/os/env_unix_test.go +++ b/src/os/env_unix_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build darwin || linux || wasip1 +//go:build darwin || linux || wasip1 || wasip2 package os_test diff --git a/src/os/exec_posix.go b/src/os/exec_posix.go index 0a968d9a10..720368572b 100644 --- a/src/os/exec_posix.go +++ b/src/os/exec_posix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build aix || darwin || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris || wasip1 || windows +//go:build aix || darwin || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris || wasip1 || wasip2 || windows package os diff --git a/src/os/file_other.go b/src/os/file_other.go index 82552c1a8c..d359b0fb67 100644 --- a/src/os/file_other.go +++ b/src/os/file_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || (tinygo.wasm && !wasip1) +//go:build baremetal || (tinygo.wasm && !wasip1 && !wasip2) package os diff --git a/src/os/file_unix.go b/src/os/file_unix.go index 25c2161e3e..badfc71ff9 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal && !wasm_unknown) || wasip1 +//go:build darwin || (linux && !baremetal && !wasm_unknown) || wasip1 || wasip2 // target wasi sets GOOS=linux and thus the +linux build tag, // even though it doesn't show up in "tinygo info target -wasi" diff --git a/src/os/getpagesize_test.go b/src/os/getpagesize_test.go index 80475552be..5017a9b807 100644 --- a/src/os/getpagesize_test.go +++ b/src/os/getpagesize_test.go @@ -1,4 +1,4 @@ -//go:build windows || darwin || (linux && !baremetal) || wasip1 +//go:build windows || darwin || (linux && !baremetal) || wasip1 || wasip2 package os_test diff --git a/src/os/os_anyos_test.go b/src/os/os_anyos_test.go index 8a082d6522..67c609949e 100644 --- a/src/os/os_anyos_test.go +++ b/src/os/os_anyos_test.go @@ -1,4 +1,4 @@ -//go:build windows || darwin || (linux && !baremetal) || wasip1 +//go:build windows || darwin || (linux && !baremetal) || wasip1 || wasip2 package os_test @@ -275,7 +275,7 @@ func TestDirFS(t *testing.T) { t.Log("TODO: implement Readdir for Windows") return } - if runtime.GOOS == "wasip1" { + if runtime.GOOS == "wasip1" || runtime.GOOS == "wasip2" { t.Log("TODO: allow foo/bar/. as synonym for path foo/bar on wasi?") return } @@ -296,7 +296,7 @@ func TestDirFSPathsValid(t *testing.T) { t.Log("skipping on Windows") return } - if runtime.GOOS == "wasip1" { + if runtime.GOOS == "wasip1" || runtime.GOOS == "wasip2" { t.Log("skipping on wasi because it fails on wasi on windows") return } diff --git a/src/os/os_chmod_test.go b/src/os/os_chmod_test.go index b8f7d354f4..3db6467add 100644 --- a/src/os/os_chmod_test.go +++ b/src/os/os_chmod_test.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !js && !wasip1 +//go:build !baremetal && !js && !wasip1 && !wasip2 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/os_hardlink_test.go b/src/os/os_hardlink_test.go index 96f1c9bbb6..9fa1ecb75f 100644 --- a/src/os/os_hardlink_test.go +++ b/src/os/os_hardlink_test.go @@ -1,4 +1,4 @@ -//go:build !windows && !baremetal && !js && !wasi && !wasip1 && !wasm_unknown +//go:build !windows && !baremetal && !js && !wasip1 && !wasm_unknown // Copyright 2024 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/os_symlink_test.go b/src/os/os_symlink_test.go index f885dd32c4..baa7047b27 100644 --- a/src/os/os_symlink_test.go +++ b/src/os/os_symlink_test.go @@ -1,4 +1,4 @@ -//go:build !windows && !baremetal && !js && !wasip1 +//go:build !windows && !baremetal && !js && !wasip1 && !wasip2 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/pipe_test.go b/src/os/pipe_test.go index b0553ffdb4..98c86089fd 100644 --- a/src/os/pipe_test.go +++ b/src/os/pipe_test.go @@ -1,4 +1,4 @@ -//go:build windows || darwin || (linux && !baremetal && !wasi) +//go:build windows || darwin || (linux && !baremetal && !wasip1 && !wasip2) // Copyright 2021 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/read_test.go b/src/os/read_test.go index 679d961132..68eb02966b 100644 --- a/src/os/read_test.go +++ b/src/os/read_test.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !js && !wasip1 +//go:build !baremetal && !js && !wasip1 && !wasip2 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/removeall_noat.go b/src/os/removeall_noat.go index d49162768c..40fc801379 100644 --- a/src/os/removeall_noat.go +++ b/src/os/removeall_noat.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !baremetal && !js && !wasip1 && !wasm_unknown +//go:build !baremetal && !js && !wasip1 && !wasip2 && !wasm_unknown package os diff --git a/src/os/removeall_other.go b/src/os/removeall_other.go index 75346bc838..dc65aaab20 100644 --- a/src/os/removeall_other.go +++ b/src/os/removeall_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || js || wasi || wasip1 || wasm_unknown +//go:build baremetal || js || wasip1 || wasip2 || wasm_unknown // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/removeall_test.go b/src/os/removeall_test.go index ea7c83b4e4..9f49e6793b 100644 --- a/src/os/removeall_test.go +++ b/src/os/removeall_test.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal && !js && !wasi) +//go:build darwin || (linux && !baremetal && !js && !wasip1 && !wasip2) // TODO: implement ReadDir on windows diff --git a/src/os/seek_unix_bad.go b/src/os/seek_unix_bad.go index 4243ced642..047c6346a4 100644 --- a/src/os/seek_unix_bad.go +++ b/src/os/seek_unix_bad.go @@ -1,4 +1,4 @@ -//go:build (linux && !baremetal && 386) || (linux && !baremetal && arm && !wasi) +//go:build (linux && !baremetal && 386) || (linux && !baremetal && arm && !wasip1 && !wasip2) package os diff --git a/src/os/stat_linuxlike.go b/src/os/stat_linuxlike.go index 59ee649707..7c512ec049 100644 --- a/src/os/stat_linuxlike.go +++ b/src/os/stat_linuxlike.go @@ -1,4 +1,4 @@ -//go:build (linux && !baremetal && !wasm_unknown) || wasip1 +//go:build (linux && !baremetal && !wasm_unknown) || wasip1 || wasip2 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/stat_other.go b/src/os/stat_other.go index d3e0af6ed5..234c59db21 100644 --- a/src/os/stat_other.go +++ b/src/os/stat_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || (tinygo.wasm && !wasip1) +//go:build baremetal || (tinygo.wasm && !wasip1 && !wasip2) // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/stat_unix.go b/src/os/stat_unix.go index a3aa491f36..af5110cf8e 100644 --- a/src/os/stat_unix.go +++ b/src/os/stat_unix.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal && !wasm_unknown) || wasip1 +//go:build darwin || (linux && !baremetal && !wasm_unknown) || wasip1 || wasip2 // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/tempfile_test.go b/src/os/tempfile_test.go index cf3fd46d70..d5ade369a0 100644 --- a/src/os/tempfile_test.go +++ b/src/os/tempfile_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !baremetal && !js && !wasip1 +//go:build !baremetal && !js && !wasip1 && !wasip2 package os_test diff --git a/src/os/types_unix.go b/src/os/types_unix.go index 3a552dfd94..0629b3bbcf 100644 --- a/src/os/types_unix.go +++ b/src/os/types_unix.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal && !wasm_unknown) || wasip1 +//go:build darwin || (linux && !baremetal && !wasm_unknown) || wasip1 || wasip2 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/runtime/os_linux.go b/src/runtime/os_linux.go index 9780e54018..7218ea653a 100644 --- a/src/runtime/os_linux.go +++ b/src/runtime/os_linux.go @@ -1,4 +1,4 @@ -//go:build linux && !baremetal && !nintendoswitch && !wasip1 && !wasm_unknown +//go:build linux && !baremetal && !nintendoswitch && !wasip1 && !wasm_unknown && !wasip2 package runtime diff --git a/src/runtime/os_other.go b/src/runtime/os_other.go index c8330bafc1..33814c4368 100644 --- a/src/runtime/os_other.go +++ b/src/runtime/os_other.go @@ -1,4 +1,4 @@ -//go:build linux && (baremetal || nintendoswitch || wasi || wasm_unknown) +//go:build linux && (baremetal || nintendoswitch || wasm_unknown) // Other systems that aren't operating systems supported by the Go toolchain // need to pretend to be an existing operating system. Linux seems like a good diff --git a/src/runtime/os_wasip2.go b/src/runtime/os_wasip2.go new file mode 100644 index 0000000000..baecfb3ab9 --- /dev/null +++ b/src/runtime/os_wasip2.go @@ -0,0 +1,5 @@ +//go:build wasip2 + +package runtime + +const GOOS = "wasip2" diff --git a/src/runtime/runtime_tinygowasm.go b/src/runtime/runtime_tinygowasm.go index 3998603539..b69a91539a 100644 --- a/src/runtime/runtime_tinygowasm.go +++ b/src/runtime/runtime_tinygowasm.go @@ -1,4 +1,4 @@ -//go:build tinygo.wasm && !wasm_unknown +//go:build tinygo.wasm && !wasm_unknown && !wasip2 package runtime diff --git a/src/runtime/runtime_tinygowasmp2.go b/src/runtime/runtime_tinygowasmp2.go new file mode 100644 index 0000000000..96d7db0ff2 --- /dev/null +++ b/src/runtime/runtime_tinygowasmp2.go @@ -0,0 +1,76 @@ +//go:build wasip2 + +package runtime + +import ( + exit "internal/wasi/cli/v0.2.0/exit" + stdout "internal/wasi/cli/v0.2.0/stdout" + monotonicclock "internal/wasi/clocks/v0.2.0/monotonic-clock" + wallclock "internal/wasi/clocks/v0.2.0/wall-clock" + random "internal/wasi/random/v0.2.0/random" + + "github.com/ydnar/wasm-tools-go/cm" +) + +const putcharBufferSize = 120 + +// Using global variables to avoid heap allocation. +var ( + putcharStdout = stdout.GetStdout() + putcharBuffer = [putcharBufferSize]byte{} + putcharPosition uint = 0 +) + +func putchar(c byte) { + putcharBuffer[putcharPosition] = c + putcharPosition++ + if c == '\n' || putcharPosition >= putcharBufferSize { + list := cm.NewList(&putcharBuffer[0], putcharPosition) + putcharStdout.BlockingWriteAndFlush(list) // error return ignored; can't do anything anyways + putcharPosition = 0 + } +} + +func getchar() byte { + // dummy, TODO + return 0 +} + +func buffered() int { + // dummy, TODO + return 0 +} + +//go:linkname now time.now +func now() (sec int64, nsec int32, mono int64) { + now := wallclock.Now() + sec = int64(now.Seconds) + nsec = int32(now.Nanoseconds) + mono = int64(monotonicclock.Now()) + return +} + +// Abort executes the wasm 'unreachable' instruction. +func abort() { + trap() +} + +//go:linkname syscall_Exit syscall.Exit +func syscall_Exit(code int) { + exit.Exit(code != 0) +} + +// TinyGo does not yet support any form of parallelism on WebAssembly, so these +// can be left empty. + +//go:linkname procPin sync/atomic.runtime_procPin +func procPin() { +} + +//go:linkname procUnpin sync/atomic.runtime_procUnpin +func procUnpin() { +} + +func hardwareRand() (n uint64, ok bool) { + return random.GetRandomU64(), true +} diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index 323c8909ac..50eb11dead 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -1,4 +1,4 @@ -//go:build (darwin || (linux && !baremetal && !wasip1 && !wasm_unknown)) && !nintendoswitch +//go:build (darwin || (linux && !baremetal && !wasip1 && !wasm_unknown && !wasip2)) && !nintendoswitch package runtime diff --git a/src/runtime/runtime_wasm_js_scheduler.go b/src/runtime/runtime_wasm_js_scheduler.go index d5e1012de4..0edde29eb5 100644 --- a/src/runtime/runtime_wasm_js_scheduler.go +++ b/src/runtime/runtime_wasm_js_scheduler.go @@ -1,4 +1,4 @@ -//go:build wasm && !wasi && !scheduler.none && !wasip1 && !wasm_unknown +//go:build wasm && !wasi && !scheduler.none && !wasip1 && !wasip2 && !wasm_unknown package runtime diff --git a/src/runtime/runtime_wasm_wasip2.go b/src/runtime/runtime_wasm_wasip2.go new file mode 100644 index 0000000000..ca189aad64 --- /dev/null +++ b/src/runtime/runtime_wasm_wasip2.go @@ -0,0 +1,61 @@ +//go:build wasip2 + +package runtime + +import ( + "unsafe" + + "internal/wasi/cli/v0.2.0/environment" + monotonicclock "internal/wasi/clocks/v0.2.0/monotonic-clock" +) + +type timeUnit int64 + +//export wasi:cli/run@0.2.0#run +func __wasi_cli_run_run() uint32 { + _start() + return 0 +} + +//export _start +func _start() { + // These need to be initialized early so that the heap can be initialized. + heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) + heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) + run() +} + +func init() { +} + +var args []string + +//go:linkname os_runtime_args os.runtime_args +func os_runtime_args() []string { + if args == nil { + args = environment.GetArguments().Slice() + } + return args +} + +//export cabi_realloc +func cabi_realloc(ptr, oldsize, align, newsize unsafe.Pointer) unsafe.Pointer { + return realloc(ptr, uintptr(newsize)) +} + +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) +} + +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns) +} + +func sleepTicks(d timeUnit) { + p := monotonicclock.SubscribeDuration(monotonicclock.Duration(d)) + p.Block() +} + +func ticks() timeUnit { + return timeUnit(monotonicclock.Now()) +} diff --git a/src/syscall/env_libc.go b/src/syscall/env_libc.go new file mode 100644 index 0000000000..f558901cf4 --- /dev/null +++ b/src/syscall/env_libc.go @@ -0,0 +1,59 @@ +//go:build darwin || nintendoswitch || wasip1 + +package syscall + +import ( + "unsafe" +) + +func Environ() []string { + + // This function combines all the environment into a single allocation. + // While this optimizes for memory usage and garbage collector + // overhead, it does run the risk of potentially pinning a "large" + // allocation if a user holds onto a single environment variable or + // value. Having each variable be its own allocation would make the + // trade-off in the other direction. + + // calculate total memory required + var length uintptr + var vars int + for environ := libc_environ; *environ != nil; { + length += libc_strlen(*environ) + vars++ + environ = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(environ), unsafe.Sizeof(environ))) + } + + // allocate our backing slice for the strings + b := make([]byte, length) + // and the slice we're going to return + envs := make([]string, 0, vars) + + // loop over the environment again, this time copying over the data to the backing slice + for environ := libc_environ; *environ != nil; { + length = libc_strlen(*environ) + // construct a Go string pointing at the libc-allocated environment variable data + var envVar string + rawEnvVar := (*struct { + ptr unsafe.Pointer + length uintptr + })(unsafe.Pointer(&envVar)) + rawEnvVar.ptr = *environ + rawEnvVar.length = length + // pull off the number of bytes we need for this environment variable + var bs []byte + bs, b = b[:length], b[length:] + // copy over the bytes to the Go heap + copy(bs, envVar) + // convert trimmed slice to string + s := *(*string)(unsafe.Pointer(&bs)) + // add s to our list of environment variables + envs = append(envs, s) + // environ++ + environ = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(environ), unsafe.Sizeof(environ))) + } + return envs +} + +//go:extern environ +var libc_environ *unsafe.Pointer diff --git a/src/syscall/env_wasip2.go b/src/syscall/env_wasip2.go new file mode 100644 index 0000000000..970400d644 --- /dev/null +++ b/src/syscall/env_wasip2.go @@ -0,0 +1,11 @@ +//go:build wasip2 + +package syscall + +func Environ() []string { + var env []string + for k, v := range libc_envs { + env = append(env, k+"="+v) + } + return env +} diff --git a/src/syscall/errno_other.go b/src/syscall/errno_other.go index 839b5f4357..b988fbc1e0 100644 --- a/src/syscall/errno_other.go +++ b/src/syscall/errno_other.go @@ -1,4 +1,4 @@ -//go:build !wasip1 && !darwin +//go:build !wasip1 && !wasip2 && !darwin package syscall diff --git a/src/syscall/errno_wasip1.go b/src/syscall/errno_wasip1.go new file mode 100644 index 0000000000..c494d7da09 --- /dev/null +++ b/src/syscall/errno_wasip1.go @@ -0,0 +1,8 @@ +//go:build wasip1 + +package syscall + +// Use a go:extern definition to access the errno from wasi-libc +// +//go:extern errno +var libcErrno Errno diff --git a/src/syscall/errno_wasip2.go b/src/syscall/errno_wasip2.go new file mode 100644 index 0000000000..39f1f8b403 --- /dev/null +++ b/src/syscall/errno_wasip2.go @@ -0,0 +1,7 @@ +//go:build wasip2 + +package syscall + +// The errno for libc_wasip2.go + +var libcErrno Errno diff --git a/src/syscall/file_emulated.go b/src/syscall/file_emulated.go index 8dd3bc14fc..7b50d4f7e8 100644 --- a/src/syscall/file_emulated.go +++ b/src/syscall/file_emulated.go @@ -1,4 +1,4 @@ -//go:build baremetal || (wasm && !wasip1) || wasm_unknown +//go:build baremetal || (wasm && !wasip1 && !wasip2) || wasm_unknown // This file emulates some file-related functions that are only available // under a real operating system. diff --git a/src/syscall/file_hosted.go b/src/syscall/file_hosted.go index f448f018d7..a079f400fb 100644 --- a/src/syscall/file_hosted.go +++ b/src/syscall/file_hosted.go @@ -1,4 +1,4 @@ -//go:build !(baremetal || (wasm && !wasip1) || wasm_unknown) +//go:build !(baremetal || (wasm && !wasip1 && !wasip2) || wasm_unknown) // This file assumes there is a libc available that runs on a real operating // system. diff --git a/src/syscall/libc_wasip2.go b/src/syscall/libc_wasip2.go new file mode 100644 index 0000000000..ea3d3327da --- /dev/null +++ b/src/syscall/libc_wasip2.go @@ -0,0 +1,1356 @@ +//go:build wasip2 + +// mini libc wrapping wasi preview2 calls in a libc api + +package syscall + +import ( + "unsafe" + + "internal/wasi/cli/v0.2.0/environment" + "internal/wasi/cli/v0.2.0/stderr" + "internal/wasi/cli/v0.2.0/stdin" + "internal/wasi/cli/v0.2.0/stdout" + wallclock "internal/wasi/clocks/v0.2.0/wall-clock" + "internal/wasi/filesystem/v0.2.0/preopens" + "internal/wasi/filesystem/v0.2.0/types" + ioerror "internal/wasi/io/v0.2.0/error" + "internal/wasi/io/v0.2.0/streams" + "internal/wasi/random/v0.2.0/random" + + "github.com/ydnar/wasm-tools-go/cm" +) + +func goString(cstr *byte) string { + return unsafe.String(cstr, strlen(cstr)) +} + +//export strlen +func strlen(cstr *byte) uintptr { + if cstr == nil { + return 0 + } + ptr := unsafe.Pointer(cstr) + var i uintptr + for p := (*byte)(ptr); *p != 0; p = (*byte)(unsafe.Add(unsafe.Pointer(p), 1)) { + i++ + } + return i +} + +// ssize_t write(int fd, const void *buf, size_t count) +// +//export write +func write(fd int32, buf *byte, count uint) int { + if stream, ok := wasiStreams[fd]; ok { + return writeStream(stream, buf, count, 0) + } + + stream, ok := wasiFiles[fd] + if !ok { + libcErrno = EBADF + return -1 + } + if stream.d == cm.ResourceNone { + libcErrno = EBADF + return -1 + } + + n := pwrite(fd, buf, count, int64(stream.offset)) + if n == -1 { + return -1 + } + stream.offset += int64(n) + return int(n) +} + +// ssize_t read(int fd, void *buf, size_t count); +// +//export read +func read(fd int32, buf *byte, count uint) int { + if stream, ok := wasiStreams[fd]; ok { + return readStream(stream, buf, count, 0) + } + + stream, ok := wasiFiles[fd] + if !ok { + libcErrno = EBADF + return -1 + } + if stream.d == cm.ResourceNone { + libcErrno = EBADF + return -1 + } + + n := pread(fd, buf, count, int64(stream.offset)) + if n == -1 { + // error during pread + return -1 + } + stream.offset += int64(n) + return int(n) +} + +// At the moment, each time we have a file read or write we create a new stream. Future implementations +// could change the current in or out file stream lazily. We could do this by tracking input and output +// offsets individually, and if they don't match the current main offset, reopen the file stream at that location. + +type wasiFile struct { + d types.Descriptor + oflag int32 // orignal open flags: O_RDONLY, O_WRONLY, O_RDWR + offset int64 // current fd offset; updated with each read/write + refs int +} + +// Need to figure out which system calls we're using: +// stdin/stdout/stderr want streams, so we use stream read/write +// but for regular files we can use the descriptor and explicitly write a buffer to the offset? +// The mismatch comes from trying to combine these. + +var wasiFiles map[int32]*wasiFile = make(map[int32]*wasiFile) + +func findFreeFD() int32 { + var newfd int32 + for wasiStreams[newfd] != nil || wasiFiles[newfd] != nil { + newfd++ + } + return newfd +} + +var wasiErrno ioerror.Error + +type wasiStream struct { + in *streams.InputStream + out *streams.OutputStream + refs int +} + +// This holds entries for stdin/stdout/stderr. + +var wasiStreams map[int32]*wasiStream + +func init() { + sin := stdin.GetStdin() + sout := stdout.GetStdout() + serr := stderr.GetStderr() + wasiStreams = map[int32]*wasiStream{ + 0: &wasiStream{ + in: &sin, + refs: 1, + }, + 1: &wasiStream{ + out: &sout, + refs: 1, + }, + 2: &wasiStream{ + out: &serr, + refs: 1, + }, + } +} + +func readStream(stream *wasiStream, buf *byte, count uint, offset int64) int { + if stream.in == nil { + // not a stream we can read from + libcErrno = EBADF + return -1 + } + + if offset != 0 { + libcErrno = EINVAL + return -1 + } + + libcErrno = 0 + result := stream.in.BlockingRead(uint64(count)) + if err := result.Err(); err != nil { + if err.Closed() { + libcErrno = 0 + return 0 + } else if err := err.LastOperationFailed(); err != nil { + wasiErrno = *err + libcErrno = EWASIERROR + } + return -1 + } + + dst := unsafe.Slice(buf, count) + list := result.OK() + copy(dst, list.Slice()) + return int(list.Len()) +} + +func writeStream(stream *wasiStream, buf *byte, count uint, offset int64) int { + if stream.out == nil { + // not a stream we can write to + libcErrno = EBADF + return -1 + } + + if offset != 0 { + libcErrno = EINVAL + return -1 + } + + src := unsafe.Slice(buf, count) + var remaining = count + + // The blocking-write-and-flush call allows a maximum of 4096 bytes at a time. + // We loop here by instead of doing subscribe/check-write/poll-one/write by hand. + for remaining > 0 { + len := uint(4096) + if len > remaining { + len = remaining + } + result := stream.out.BlockingWriteAndFlush(cm.ToList(src[:len])) + if err := result.Err(); err != nil { + if err.Closed() { + libcErrno = 0 + return 0 + } else if err := err.LastOperationFailed(); err != nil { + wasiErrno = *err + libcErrno = EWASIERROR + } + return -1 + } + remaining -= len + } + + return int(count) +} + +//go:linkname memcpy runtime.memcpy +func memcpy(dst, src unsafe.Pointer, size uintptr) + +// ssize_t pread(int fd, void *buf, size_t count, off_t offset); +// +//export pread +func pread(fd int32, buf *byte, count uint, offset int64) int { + // TODO(dgryski): Need to be consistent about all these checks; EBADF/EINVAL/... ? + + if stream, ok := wasiStreams[fd]; ok { + return readStream(stream, buf, count, offset) + + } + + streams, ok := wasiFiles[fd] + if !ok { + // TODO(dgryski): EINVAL? + libcErrno = EBADF + return -1 + } + if streams.d == cm.ResourceNone { + libcErrno = EBADF + return -1 + } + if streams.oflag&O_RDONLY == 0 { + libcErrno = EBADF + return -1 + } + + result := streams.d.Read(types.FileSize(count), types.FileSize(offset)) + if err := result.Err(); err != nil { + libcErrno = errorCodeToErrno(*err) + return -1 + } + + list := result.OK().F0 + copy(unsafe.Slice(buf, count), list.Slice()) + + // TODO(dgryski): EOF bool is ignored? + return int(list.Len()) +} + +// ssize_t pwrite(int fd, void *buf, size_t count, off_t offset); +// +//export pwrite +func pwrite(fd int32, buf *byte, count uint, offset int64) int { + // TODO(dgryski): Need to be consistent about all these checks; EBADF/EINVAL/... ? + if stream, ok := wasiStreams[fd]; ok { + return writeStream(stream, buf, count, 0) + } + + streams, ok := wasiFiles[fd] + if !ok { + // TODO(dgryski): EINVAL? + libcErrno = EBADF + return -1 + } + if streams.d == cm.ResourceNone { + libcErrno = EBADF + return -1 + } + if streams.oflag&O_WRONLY == 0 { + libcErrno = EBADF + return -1 + } + + result := streams.d.Write(cm.NewList(buf, count), types.FileSize(offset)) + if err := result.Err(); err != nil { + // TODO(dgryski): + libcErrno = errorCodeToErrno(*err) + return -1 + } + + return int(*result.OK()) +} + +// ssize_t lseek(int fd, off_t offset, int whence); +// +//export lseek +func lseek(fd int32, offset int64, whence int) int64 { + if _, ok := wasiStreams[fd]; ok { + // can't lseek a stream + libcErrno = EBADF + return -1 + } + + stream, ok := wasiFiles[fd] + if !ok { + libcErrno = EBADF + return -1 + } + if stream.d == cm.ResourceNone { + libcErrno = EBADF + return -1 + } + + switch whence { + case 0: // SEEK_SET + stream.offset = offset + case 1: // SEEK_CUR + stream.offset += offset + case 2: // SEEK_END + result := stream.d.Stat() + if err := result.Err(); err != nil { + libcErrno = errorCodeToErrno(*err) + return -1 + } + stream.offset = int64(result.OK().Size) + offset + } + + return int64(stream.offset) +} + +// int close(int fd) +// +//export close +func close(fd int32) int32 { + if streams, ok := wasiStreams[fd]; ok { + if streams.out != nil { + // ignore any error + streams.out.BlockingFlush() + } + + if streams.refs--; streams.refs == 0 { + if streams.out != nil { + streams.out.ResourceDrop() + streams.out = nil + } + if streams.in != nil { + streams.in.ResourceDrop() + streams.in = nil + } + } + + delete(wasiStreams, fd) + return 0 + } + + streams, ok := wasiFiles[fd] + if !ok { + libcErrno = EBADF + return -1 + } + if streams.refs--; streams.refs == 0 && streams.d != cm.ResourceNone { + streams.d.ResourceDrop() + streams.d = 0 + } + delete(wasiFiles, fd) + + return 0 +} + +// int dup(int fd) +// +//export dup +func dup(fd int32) int32 { + // is fd a stream? + if stream, ok := wasiStreams[fd]; ok { + newfd := findFreeFD() + stream.refs++ + wasiStreams[newfd] = stream + return newfd + } + + // is fd a file? + if file, ok := wasiFiles[fd]; ok { + // scan for first free file descriptor + newfd := findFreeFD() + file.refs++ + wasiFiles[newfd] = file + return newfd + } + + // unknown file descriptor + libcErrno = EBADF + return -1 +} + +// void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); +// +//export mmap +func mmap(addr unsafe.Pointer, length uintptr, prot, flags, fd int32, offset uintptr) unsafe.Pointer { + libcErrno = ENOSYS + return unsafe.Pointer(^uintptr(0)) +} + +// int munmap(void *addr, size_t length); +// +//export munmap +func munmap(addr unsafe.Pointer, length uintptr) int32 { + libcErrno = ENOSYS + return -1 +} + +// int mprotect(void *addr, size_t len, int prot); +// +//export mprotect +func mprotect(addr unsafe.Pointer, len uintptr, prot int32) int32 { + libcErrno = ENOSYS + return -1 +} + +// int chmod(const char *pathname, mode_t mode); +// +//export chmod +func chmod(pathname *byte, mode uint32) int32 { + return 0 +} + +// int mkdir(const char *pathname, mode_t mode); +// +//export mkdir +func mkdir(pathname *byte, mode uint32) int32 { + + path := goString(pathname) + dir, relPath := findPreopenForPath(path) + if dir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + result := dir.d.CreateDirectoryAt(relPath) + if err := result.Err(); err != nil { + libcErrno = errorCodeToErrno(*err) + return -1 + } + + return 0 +} + +// int rmdir(const char *pathname); +// +//export rmdir +func rmdir(pathname *byte) int32 { + path := goString(pathname) + dir, relPath := findPreopenForPath(path) + if dir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + result := dir.d.RemoveDirectoryAt(relPath) + if err := result.Err(); err != nil { + libcErrno = errorCodeToErrno(*err) + return -1 + } + + return 0 +} + +// int rename(const char *from, *to); +// +//export rename +func rename(from, to *byte) int32 { + fromPath := goString(from) + fromDir, fromRelPath := findPreopenForPath(fromPath) + if fromDir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + toPath := goString(to) + toDir, toRelPath := findPreopenForPath(toPath) + if toDir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + result := fromDir.d.RenameAt(fromRelPath, toDir.d, toRelPath) + if err := result.Err(); err != nil { + libcErrno = errorCodeToErrno(*err) + return -1 + } + + return 0 +} + +// int symlink(const char *from, *to); +// +//export symlink +func symlink(from, to *byte) int32 { + fromPath := goString(from) + fromDir, fromRelPath := findPreopenForPath(fromPath) + if fromDir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + toPath := goString(to) + toDir, toRelPath := findPreopenForPath(toPath) + if toDir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + if fromDir.d != toDir.d { + libcErrno = EACCES + return -1 + } + + // TODO(dgryski): check fromDir == toDir? + + result := fromDir.d.SymlinkAt(fromRelPath, toRelPath) + if err := result.Err(); err != nil { + libcErrno = errorCodeToErrno(*err) + return -1 + } + + return 0 +} + +// int link(const char *from, *to); +// +//export link +func link(from, to *byte) int32 { + fromPath := goString(from) + fromDir, fromRelPath := findPreopenForPath(fromPath) + if fromDir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + toPath := goString(to) + toDir, toRelPath := findPreopenForPath(toPath) + if toDir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + if fromDir.d != toDir.d { + libcErrno = EACCES + return -1 + } + + // TODO(dgryski): check fromDir == toDir? + + result := fromDir.d.LinkAt(0, fromRelPath, toDir.d, toRelPath) + if err := result.Err(); err != nil { + libcErrno = errorCodeToErrno(*err) + return -1 + } + + return 0 +} + +// int fsync(int fd); +// +//export fsync +func fsync(fd int32) int32 { + if _, ok := wasiStreams[fd]; ok { + // can't sync a stream + libcErrno = EBADF + return -1 + } + + streams, ok := wasiFiles[fd] + if !ok { + libcErrno = EBADF + return -1 + } + if streams.d == cm.ResourceNone { + libcErrno = EBADF + return -1 + } + if streams.oflag&O_WRONLY == 0 { + libcErrno = EBADF + return -1 + } + + result := streams.d.SyncData() + if err := result.Err(); err != nil { + libcErrno = errorCodeToErrno(*err) + return -1 + } + + return 0 +} + +// ssize_t readlink(const char *path, void *buf, size_t count); +// +//export readlink +func readlink(pathname *byte, buf *byte, count uint) int { + path := goString(pathname) + dir, relPath := findPreopenForPath(path) + if dir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + result := dir.d.ReadLinkAt(relPath) + if err := result.Err(); err != nil { + libcErrno = errorCodeToErrno(*err) + return -1 + } + + s := *result.OK() + size := uintptr(count) + if size > uintptr(len(s)) { + size = uintptr(len(s)) + } + + memcpy(unsafe.Pointer(buf), unsafe.Pointer(unsafe.StringData(s)), size) + return int(size) +} + +// int unlink(const char *pathname); +// +//export unlink +func unlink(pathname *byte) int32 { + path := goString(pathname) + dir, relPath := findPreopenForPath(path) + if dir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + result := dir.d.UnlinkFileAt(relPath) + if err := result.Err(); err != nil { + libcErrno = errorCodeToErrno(*err) + return -1 + } + + return 0 +} + +// int getpagesize(void); +// +//export getpagesize +func getpagesize() int { + return 65536 +} + +// int stat(const char *path, struct stat * buf); +// +//export stat +func stat(pathname *byte, dst *Stat_t) int32 { + path := goString(pathname) + dir, relPath := findPreopenForPath(path) + if dir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + result := dir.d.StatAt(0, relPath) + if err := result.Err(); err != nil { + libcErrno = errorCodeToErrno(*err) + return -1 + } + + setStatFromWASIStat(dst, result.OK()) + + return 0 +} + +// int fstat(int fd, struct stat * buf); +// +//export fstat +func fstat(fd int32, dst *Stat_t) int32 { + if _, ok := wasiStreams[fd]; ok { + // TODO(dgryski): fill in stat buffer for stdin etc + return -1 + } + + stream, ok := wasiFiles[fd] + if !ok { + libcErrno = EBADF + return -1 + } + if stream.d == cm.ResourceNone { + libcErrno = EBADF + return -1 + } + result := stream.d.Stat() + if err := result.Err(); err != nil { + libcErrno = errorCodeToErrno(*err) + return -1 + } + + setStatFromWASIStat(dst, result.OK()) + + return 0 +} + +func setStatFromWASIStat(sstat *Stat_t, wstat *types.DescriptorStat) { + // This will cause problems for people who want to compare inodes + sstat.Dev = 0 + sstat.Ino = 0 + sstat.Rdev = 0 + + sstat.Nlink = uint64(wstat.LinkCount) + + sstat.Mode = p2fileTypeToStatType(wstat.Type) + + // No uid/gid + sstat.Uid = 0 + sstat.Gid = 0 + sstat.Size = int64(wstat.Size) + + // made up numbers + sstat.Blksize = 512 + sstat.Blocks = (sstat.Size + 511) / int64(sstat.Blksize) + + setOptTime := func(t *Timespec, o *wallclock.DateTime) { + t.Sec = 0 + t.Nsec = 0 + if o != nil { + t.Sec = int32(o.Seconds) + t.Nsec = int64(o.Nanoseconds) + } + } + + setOptTime(&sstat.Atim, wstat.DataAccessTimestamp.Some()) + setOptTime(&sstat.Mtim, wstat.DataModificationTimestamp.Some()) + setOptTime(&sstat.Ctim, wstat.StatusChangeTimestamp.Some()) +} + +// int lstat(const char *path, struct stat * buf); +// +//export lstat +func lstat(pathname *byte, dst *Stat_t) int32 { + path := goString(pathname) + dir, relPath := findPreopenForPath(path) + if dir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + result := dir.d.StatAt(0, relPath) + if err := result.Err(); err != nil { + libcErrno = errorCodeToErrno(*err) + return -1 + } + + setStatFromWASIStat(dst, result.OK()) + + return 0 +} + +func init() { + populateEnvironment() + populatePreopens() +} + +type wasiDir struct { + d types.Descriptor // wasip2 descriptor + root string // root path for this descriptor + rel string // relative path under root +} + +var libcCWD wasiDir + +var wasiPreopens map[string]types.Descriptor + +func populatePreopens() { + + var cwd string + + // find CWD + result := environment.InitialCWD() + if s := result.Some(); s != nil { + cwd = *s + } else if s, _ := Getenv("PWD"); s != "" { + cwd = s + } + + dirs := preopens.GetDirectories().Slice() + preopens := make(map[string]types.Descriptor, len(dirs)) + for _, tup := range dirs { + desc, path := tup.F0, tup.F1 + if path == cwd { + libcCWD.d = desc + libcCWD.root = path + libcCWD.rel = "" + } + preopens[path] = desc + } + wasiPreopens = preopens +} + +// -- BEGIN fs_wasip1.go -- +// The following section has been taken from upstream Go with the following copyright: +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:nosplit +func appendCleanPath(buf []byte, path string, lookupParent bool) ([]byte, bool) { + i := 0 + for i < len(path) { + for i < len(path) && path[i] == '/' { + i++ + } + + j := i + for j < len(path) && path[j] != '/' { + j++ + } + + s := path[i:j] + i = j + + switch s { + case "": + continue + case ".": + continue + case "..": + if !lookupParent { + k := len(buf) + for k > 0 && buf[k-1] != '/' { + k-- + } + for k > 1 && buf[k-1] == '/' { + k-- + } + buf = buf[:k] + if k == 0 { + lookupParent = true + } else { + s = "" + continue + } + } + default: + lookupParent = false + } + + if len(buf) > 0 && buf[len(buf)-1] != '/' { + buf = append(buf, '/') + } + buf = append(buf, s...) + } + return buf, lookupParent +} + +// joinPath concatenates dir and file paths, producing a cleaned path where +// "." and ".." have been removed, unless dir is relative and the references +// to parent directories in file represented a location relative to a parent +// of dir. +// +// This function is used for path resolution of all wasi functions expecting +// a path argument; the returned string is heap allocated, which we may want +// to optimize in the future. Instead of returning a string, the function +// could append the result to an output buffer that the functions in this +// file can manage to have allocated on the stack (e.g. initializing to a +// fixed capacity). Since it will significantly increase code complexity, +// we prefer to optimize for readability and maintainability at this time. +func joinPath(dir, file string) string { + buf := make([]byte, 0, len(dir)+len(file)+1) + if isAbs(dir) { + buf = append(buf, '/') + } + + buf, lookupParent := appendCleanPath(buf, dir, true) + buf, _ = appendCleanPath(buf, file, lookupParent) + // The appendCleanPath function cleans the path so it does not inject + // references to the current directory. If both the dir and file args + // were ".", this results in the output buffer being empty so we handle + // this condition here. + if len(buf) == 0 { + buf = append(buf, '.') + } + // If the file ended with a '/' we make sure that the output also ends + // with a '/'. This is needed to ensure that programs have a mechanism + // to represent dereferencing symbolic links pointing to directories. + if buf[len(buf)-1] != '/' && isDir(file) { + buf = append(buf, '/') + } + return unsafe.String(&buf[0], len(buf)) +} + +func isAbs(path string) bool { + return hasPrefix(path, "/") +} + +func isDir(path string) bool { + return hasSuffix(path, "/") +} + +func hasPrefix(s, p string) bool { + return len(s) >= len(p) && s[:len(p)] == p +} + +func hasSuffix(s, x string) bool { + return len(s) >= len(x) && s[len(s)-len(x):] == x +} + +// findPreopenForPath finds which preopen it relates to and return that descriptor/root and the path relative to that directory descriptor/root +func findPreopenForPath(path string) (wasiDir, string) { + dir := "/" + var wasidir wasiDir + + if !isAbs(path) { + dir = libcCWD.root + wasidir = libcCWD + if libcCWD.rel != "" && libcCWD.rel != "." && libcCWD.rel != "./" { + path = libcCWD.rel + "/" + path + } + } + path = joinPath(dir, path) + + var best string + for k, v := range wasiPreopens { + if len(k) > len(best) && hasPrefix(path, k) { + wasidir = wasiDir{d: v, root: k} + best = wasidir.root + } + } + + if hasPrefix(path, wasidir.root) { + path = path[len(wasidir.root):] + } + for isAbs(path) { + path = path[1:] + } + if len(path) == 0 { + path = "." + } + + return wasidir, path +} + +// -- END fs_wasip1.go -- + +// int open(const char *pathname, int flags, mode_t mode); +// +//export open +func open(pathname *byte, flags int32, mode uint32) int32 { + path := goString(pathname) + dir, relPath := findPreopenForPath(path) + if dir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + var dflags types.DescriptorFlags + if (flags & O_RDONLY) == O_RDONLY { + dflags |= types.DescriptorFlagsRead + } + if (flags & O_WRONLY) == O_WRONLY { + dflags |= types.DescriptorFlagsWrite + } + + var oflags types.OpenFlags + if flags&O_CREAT == O_CREAT { + oflags |= types.OpenFlagsCreate + } + if flags&O_DIRECTORY == O_DIRECTORY { + oflags |= types.OpenFlagsDirectory + } + if flags&O_EXCL == O_EXCL { + oflags |= types.OpenFlagsExclusive + } + if flags&O_TRUNC == O_TRUNC { + oflags |= types.OpenFlagsTruncate + } + + // By default, follow symlinks for open() unless O_NOFOLLOW was passed + var pflags types.PathFlags = types.PathFlagsSymlinkFollow + if flags&O_NOFOLLOW == O_NOFOLLOW { + // O_NOFOLLOW was passed, so turn off SymlinkFollow + pflags &^= types.PathFlagsSymlinkFollow + } + + result := dir.d.OpenAt(pflags, relPath, oflags, dflags) + if err := result.Err(); err != nil { + libcErrno = errorCodeToErrno(*err) + return -1 + } + + stream := wasiFile{ + d: *result.OK(), + oflag: flags, + refs: 1, + } + + if flags&(O_WRONLY|O_APPEND) == (O_WRONLY | O_APPEND) { + result := stream.d.Stat() + if err := result.Err(); err != nil { + libcErrno = errorCodeToErrno(*err) + return -1 + } + stream.offset = int64(result.OK().Size) + } + + libcfd := findFreeFD() + + wasiFiles[libcfd] = &stream + + return int32(libcfd) +} + +func errorCodeToErrno(err types.ErrorCode) Errno { + switch err { + case types.ErrorCodeAccess: + return EACCES + case types.ErrorCodeWouldBlock: + return EAGAIN + case types.ErrorCodeAlready: + return EALREADY + case types.ErrorCodeBadDescriptor: + return EBADF + case types.ErrorCodeBusy: + return EBUSY + case types.ErrorCodeDeadlock: + return EDEADLK + case types.ErrorCodeQuota: + return EDQUOT + case types.ErrorCodeExist: + return EEXIST + case types.ErrorCodeFileTooLarge: + return EFBIG + case types.ErrorCodeIllegalByteSequence: + return EILSEQ + case types.ErrorCodeInProgress: + return EINPROGRESS + case types.ErrorCodeInterrupted: + return EINTR + case types.ErrorCodeInvalid: + return EINVAL + case types.ErrorCodeIO: + return EIO + case types.ErrorCodeIsDirectory: + return EISDIR + case types.ErrorCodeLoop: + return ELOOP + case types.ErrorCodeTooManyLinks: + return EMLINK + case types.ErrorCodeMessageSize: + return EMSGSIZE + case types.ErrorCodeNameTooLong: + return ENAMETOOLONG + case types.ErrorCodeNoDevice: + return ENODEV + case types.ErrorCodeNoEntry: + return ENOENT + case types.ErrorCodeNoLock: + return ENOLCK + case types.ErrorCodeInsufficientMemory: + return ENOMEM + case types.ErrorCodeInsufficientSpace: + return ENOSPC + case types.ErrorCodeNotDirectory: + return ENOTDIR + case types.ErrorCodeNotEmpty: + return ENOTEMPTY + case types.ErrorCodeNotRecoverable: + return ENOTRECOVERABLE + case types.ErrorCodeUnsupported: + return ENOSYS + case types.ErrorCodeNoTTY: + return ENOTTY + case types.ErrorCodeNoSuchDevice: + return ENXIO + case types.ErrorCodeOverflow: + return EOVERFLOW + case types.ErrorCodeNotPermitted: + return EPERM + case types.ErrorCodePipe: + return EPIPE + case types.ErrorCodeReadOnly: + return EROFS + case types.ErrorCodeInvalidSeek: + return ESPIPE + case types.ErrorCodeTextFileBusy: + return ETXTBSY + case types.ErrorCodeCrossDevice: + return EXDEV + } + return Errno(err) +} + +type libc_DIR struct { + d types.DirectoryEntryStream +} + +// DIR *fdopendir(int); +// +//export fdopendir +func fdopendir(fd int32) unsafe.Pointer { + if _, ok := wasiStreams[fd]; ok { + libcErrno = EBADF + return nil + } + + stream, ok := wasiFiles[fd] + if !ok { + libcErrno = EBADF + return nil + } + if stream.d == cm.ResourceNone { + libcErrno = EBADF + return nil + } + + result := stream.d.ReadDirectory() + if err := result.Err(); err != nil { + libcErrno = errorCodeToErrno(*err) + return nil + } + + return unsafe.Pointer(&libc_DIR{d: *result.OK()}) +} + +// int fdclosedir(DIR *); +// +//export fdclosedir +func fdclosedir(dirp unsafe.Pointer) int32 { + if dirp == nil { + return 0 + + } + dir := (*libc_DIR)(dirp) + if dir.d == cm.ResourceNone { + return 0 + } + + dir.d.ResourceDrop() + dir.d = cm.ResourceNone + + return 0 +} + +// struct dirent *readdir(DIR *); +// +//export readdir +func readdir(dirp unsafe.Pointer) *Dirent { + if dirp == nil { + return nil + + } + dir := (*libc_DIR)(dirp) + if dir.d == cm.ResourceNone { + return nil + } + + result := dir.d.ReadDirectoryEntry() + if err := result.Err(); err != nil { + libcErrno = errorCodeToErrno(*err) + return nil + } + + entry := result.OK().Some() + if entry == nil { + libcErrno = 0 + return nil + } + + // The dirent C struct uses a flexible array member to indicate that the + // directory name is laid out in memory right after the struct data: + // + // struct dirent { + // ino_t d_ino; + // unsigned char d_type; + // char d_name[]; + // }; + buf := make([]byte, unsafe.Sizeof(Dirent{})+uintptr(len(entry.Name))) + dirent := (*Dirent)((unsafe.Pointer)(&buf[0])) + + // No inodes in wasi + dirent.Ino = 0 + dirent.Type = p2fileTypeToDirentType(entry.Type) + copy(buf[unsafe.Offsetof(dirent.Type)+1:], entry.Name) + + return dirent +} + +func p2fileTypeToDirentType(t types.DescriptorType) uint8 { + switch t { + case types.DescriptorTypeUnknown: + return DT_UNKNOWN + case types.DescriptorTypeBlockDevice: + return DT_BLK + case types.DescriptorTypeCharacterDevice: + return DT_CHR + case types.DescriptorTypeDirectory: + return DT_DIR + case types.DescriptorTypeFIFO: + return DT_FIFO + case types.DescriptorTypeSymbolicLink: + return DT_LNK + case types.DescriptorTypeRegularFile: + return DT_REG + case types.DescriptorTypeSocket: + return DT_FIFO + } + + return DT_UNKNOWN +} + +func p2fileTypeToStatType(t types.DescriptorType) uint32 { + switch t { + case types.DescriptorTypeUnknown: + return 0 + case types.DescriptorTypeBlockDevice: + return S_IFBLK + case types.DescriptorTypeCharacterDevice: + return S_IFCHR + case types.DescriptorTypeDirectory: + return S_IFDIR + case types.DescriptorTypeFIFO: + return S_IFIFO + case types.DescriptorTypeSymbolicLink: + return S_IFLNK + case types.DescriptorTypeRegularFile: + return S_IFREG + case types.DescriptorTypeSocket: + return S_IFSOCK + } + + return 0 +} + +var libc_envs map[string]string + +func populateEnvironment() { + libc_envs = make(map[string]string) + for _, kv := range environment.GetEnvironment().Slice() { + libc_envs[kv[0]] = kv[1] + } +} + +// char * getenv(const char *name); +// +//export getenv +func getenv(key *byte) *byte { + k := goString(key) + + v, ok := libc_envs[k] + if !ok { + return nil + } + + // The new allocation is zero-filled; allocating an extra byte and then + // copying the data over will leave the last byte untouched, + // null-terminating the string. + vbytes := make([]byte, len(v)+1) + copy(vbytes, v) + return unsafe.SliceData(vbytes) +} + +// int setenv(const char *name, const char *value, int overwrite); +// +//export setenv +func setenv(key, value *byte, overwrite int) int { + k := goString(key) + if _, ok := libc_envs[k]; ok && overwrite == 0 { + return 0 + } + + v := goString(value) + libc_envs[k] = v + + return 0 +} + +// int unsetenv(const char *name); +// +//export unsetenv +func unsetenv(key *byte) int { + k := goString(key) + delete(libc_envs, k) + return 0 +} + +// void arc4random_buf (void *, size_t); +// +//export arc4random_buf +func arc4random_buf(p unsafe.Pointer, l uint) { + result := random.GetRandomBytes(uint64(l)) + s := result.Slice() + memcpy(unsafe.Pointer(p), unsafe.Pointer(unsafe.SliceData(s)), uintptr(l)) +} + +// int chdir(char *name) +// +//export chdir +func chdir(name *byte) int { + path := goString(name) + "/" + + if !isAbs(path) { + path = joinPath(libcCWD.root+"/"+libcCWD.rel+"/", path) + } + + if path == "." { + return 0 + } + + dir, rel := findPreopenForPath(path) + if dir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + result := dir.d.OpenAt(types.PathFlagsSymlinkFollow, rel, types.OpenFlagsDirectory, types.DescriptorFlagsRead) + if err := result.Err(); err != nil { + libcErrno = errorCodeToErrno(*err) + return -1 + } + + libcCWD = dir + // keep the same cwd base but update "rel" to point to new base path + libcCWD.rel = rel + + return 0 +} + +// char *getcwd(char *buf, size_t size) +// +//export getcwd +func getcwd(buf *byte, size uint) *byte { + + cwd := libcCWD.root + if libcCWD.rel != "" && libcCWD.rel != "." && libcCWD.rel != "./" { + cwd += libcCWD.rel + } + + if buf == nil { + b := make([]byte, len(cwd)+1) + buf = unsafe.SliceData(b) + } else if size == 0 { + libcErrno = EINVAL + return nil + } + + if size < uint(len(cwd)+1) { + libcErrno = ERANGE + return nil + } + + s := unsafe.Slice(buf, size) + s[size-1] = 0 // Enforce NULL termination + copy(s, cwd) + return buf +} diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index fb2e23968e..eed18cbd9b 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -1,4 +1,4 @@ -//go:build darwin || nintendoswitch || wasi || wasip1 +//go:build darwin || nintendoswitch || wasip1 || wasip2 package syscall @@ -260,55 +260,6 @@ func Mprotect(b []byte, prot int) (err error) { return } -func Environ() []string { - - // This function combines all the environment into a single allocation. - // While this optimizes for memory usage and garbage collector - // overhead, it does run the risk of potentially pinning a "large" - // allocation if a user holds onto a single environment variable or - // value. Having each variable be its own allocation would make the - // trade-off in the other direction. - - // calculate total memory required - var length uintptr - var vars int - for environ := libc_environ; *environ != nil; { - length += libc_strlen(*environ) - vars++ - environ = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(environ), unsafe.Sizeof(environ))) - } - - // allocate our backing slice for the strings - b := make([]byte, length) - // and the slice we're going to return - envs := make([]string, 0, vars) - - // loop over the environment again, this time copying over the data to the backing slice - for environ := libc_environ; *environ != nil; { - length = libc_strlen(*environ) - // construct a Go string pointing at the libc-allocated environment variable data - var envVar string - rawEnvVar := (*struct { - ptr unsafe.Pointer - length uintptr - })(unsafe.Pointer(&envVar)) - rawEnvVar.ptr = *environ - rawEnvVar.length = length - // pull off the number of bytes we need for this environment variable - var bs []byte - bs, b = b[:length], b[length:] - // copy over the bytes to the Go heap - copy(bs, envVar) - // convert trimmed slice to string - s := *(*string)(unsafe.Pointer(&bs)) - // add s to our list of environment variables - envs = append(envs, s) - // environ++ - environ = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(environ), unsafe.Sizeof(environ))) - } - return envs -} - // BytePtrFromString returns a pointer to a NUL-terminated array of // bytes containing the text of s. If s contains a NUL byte at any // location, it returns (nil, EINVAL). @@ -445,6 +396,3 @@ func libc_readlink(path *byte, buf *byte, count uint) int // //export unlink func libc_unlink(pathname *byte) int32 - -//go:extern environ -var libc_environ *unsafe.Pointer diff --git a/src/syscall/syscall_libc_wasip1.go b/src/syscall/syscall_libc_wasi.go similarity index 95% rename from src/syscall/syscall_libc_wasip1.go rename to src/syscall/syscall_libc_wasi.go index 42a5b096d9..2d83a87147 100644 --- a/src/syscall/syscall_libc_wasip1.go +++ b/src/syscall/syscall_libc_wasi.go @@ -1,4 +1,4 @@ -//go:build wasip1 +//go:build wasip1 || wasip2 package syscall @@ -70,9 +70,10 @@ const ( __WASI_FILETYPE_SYMBOLIC_LINK = 7 // ../../lib/wasi-libc/libc-bottom-half/headers/public/__header_fcntl.h - O_RDONLY = 0x04000000 - O_WRONLY = 0x10000000 - O_RDWR = O_RDONLY | O_WRONLY + O_NOFOLLOW = 0x01000000 + O_RDONLY = 0x04000000 + O_WRONLY = 0x10000000 + O_RDWR = O_RDONLY | O_WRONLY O_CREAT = __WASI_OFLAGS_CREAT << 12 O_TRUNC = __WASI_OFLAGS_TRUNC << 12 @@ -118,11 +119,9 @@ const ( PATH_MAX = 4096 ) -//go:extern errno -var libcErrno uintptr - func getErrno() error { - return Errno(libcErrno) + // libcErrno is the errno from wasi-libc for wasip1 and the errno for libc_wasip2 for wasip2 + return libcErrno } func (e Errno) Is(target error) bool { @@ -195,6 +194,7 @@ const ( ENOTCONN Errno = 53 /* Socket is not connected */ ENOTDIR Errno = 54 /* Not a directory */ ENOTEMPTY Errno = 55 /* Directory not empty */ + ENOTRECOVERABLE Errno = 56 /* State not recoverable */ ENOTSOCK Errno = 57 /* Socket operation on non-socket */ ESOCKTNOSUPPORT Errno = 58 /* Socket type not supported */ EOPNOTSUPP Errno = 58 /* Operation not supported on transport endpoint */ @@ -213,10 +213,15 @@ const ( ESRCH Errno = 71 /* No such process */ ESTALE Errno = 72 ETIMEDOUT Errno = 73 /* Connection timed out */ + ETXTBSY Errno = 74 /* Text file busy */ EXDEV Errno = 75 /* Cross-device link */ ENOTCAPABLE Errno = 76 /* Extension: Capabilities insufficient. */ + + EWASIERROR Errno = 255 /* Unknown WASI error */ ) +// TODO(ydnar): remove Timespec for WASI Preview 2 (seconds is uint64). +// // https://github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/headers/public/__struct_timespec.h type Timespec struct { Sec int32 @@ -411,6 +416,11 @@ type Utsname struct { Domainname [65]int8 } +//go:linkname faccessat syscall.Faccessat +func faccessat(dirfd int, path string, mode uint32, flags int) (err error) { + return ENOSYS +} + // Stub Utsname, needed because WASI pretends to be linux/arm. func Uname(buf *Utsname) (err error) diff --git a/src/testing/testing_test.go b/src/testing/testing_test.go index 9f5249b0c3..eecba519af 100644 --- a/src/testing/testing_test.go +++ b/src/testing/testing_test.go @@ -26,7 +26,7 @@ func TestMain(m *testing.M) { } func TestTempDirInCleanup(t *testing.T) { - if runtime.GOOS == "wasip1" { + if runtime.GOOS == "wasip1" || runtime.GOOS == "wasip2" { t.Log("Skipping. TODO: implement RemoveAll for wasi") return } @@ -63,7 +63,7 @@ func TestTempDirInBenchmark(t *testing.T) { } func TestTempDir(t *testing.T) { - if runtime.GOOS == "wasip1" { + if runtime.GOOS == "wasip1" || runtime.GOOS == "wasip2" { t.Log("Skipping. TODO: implement RemoveAll for wasi") return } diff --git a/src/vendor/github.com/ydnar/wasm-tools-go b/src/vendor/github.com/ydnar/wasm-tools-go new file mode 160000 index 0000000000..ca8d06b46f --- /dev/null +++ b/src/vendor/github.com/ydnar/wasm-tools-go @@ -0,0 +1 @@ +Subproject commit ca8d06b46fa45bdc90b76060fc9bbf7ab9af5eed diff --git a/targets/wasip2.json b/targets/wasip2.json new file mode 100644 index 0000000000..3d6f68c6ec --- /dev/null +++ b/targets/wasip2.json @@ -0,0 +1,28 @@ +{ + "llvm-target": "wasm32-unknown-wasi", + "cpu": "generic", + "features": "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext", + "build-tags": ["tinygo.wasm", "wasip2"], + "goos": "linux", + "goarch": "arm", + "linker": "wasm-ld", + "libc": "wasmbuiltins", + "rtlib": "compiler-rt", + "scheduler": "asyncify", + "default-stack-size": 65536, + "cflags": [ + "-mbulk-memory", + "-mnontrapping-fptoint", + "-msign-ext" + ], + "ldflags": [ + "--stack-first", + "--no-demangle" + ], + "extra-files": [ + "src/runtime/asm_tinygowasm.S" + ], + "emulator": "wasmtime --wasm component-model --dir={tmpDir}::/tmp {}", + "wit-package": "{root}/lib/wasi-cli/wit/", + "wit-world": "wasi:cli/command" +} diff --git a/tools/tgtestjson.sh b/tools/tgtestjson.sh new file mode 100755 index 0000000000..169da5852c --- /dev/null +++ b/tools/tgtestjson.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +# Run tests and convert output to json with go tool test2json. +# This is a workaround for the lack of -json output in tinygo test. +# Some variables must be set in the environment beforehand. +# TODO: let's just add -json support to tinygo test. + +TINYGO="${TINYGO:-tinygo}" +PACKAGES="${PACKAGES:-"./tests"}" +TARGET="${TARGET:-wasip2}" +TESTOPTS="${TESTOPTS:-"-x -work"}" + +# go clean -testcache +for pkg in $PACKAGES; do + # Example invocation with test2json in BigGo: + # go test -test.v=test2json ./$pkg 2>&1 | go tool test2json -p $pkg + + # Uncomment to see resolved commands in output + # >&2 echo "${TINYGO} test -v -target $TARGET $TESTOPTS $pkg 2>&1 | go tool test2json -p $pkg" + "${TINYGO}" test -v -target $TARGET $TESTOPTS $pkg 2>&1 | go tool test2json -p $pkg + +done From 571447c7c14774dd4fe4342a35eee024aca2578a Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 27 Jun 2024 20:25:33 +0200 Subject: [PATCH 083/444] compiler: add 'align' attribute to runtime.alloc calls This adds something like 'align 4' to the runtime.alloc calls, so that the compiler knows the alignment of the allocation and can optimize accordingly. The optimization is very small, only 0.01% in my small test. However, my plan is to read the value in the heap-to-stack transform pass to make it more correct and let it produce slightly more optimal code. --- compiler/compiler.go | 4 +++ compiler/llvm.go | 2 ++ compiler/testdata/gc.ll | 32 +++++++++---------- .../testdata/goroutine-cortex-m-qemu-tasks.ll | 8 ++--- compiler/testdata/goroutine-wasm-asyncify.ll | 8 ++--- compiler/testdata/slice.ll | 12 +++---- 6 files changed, 36 insertions(+), 30 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index 004dfcaa31..e9697fae27 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -2017,6 +2017,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { sizeValue := llvm.ConstInt(b.uintptrType, size, false) layoutValue := b.createObjectLayout(typ, expr.Pos()) buf := b.createRuntimeCall("alloc", []llvm.Value{sizeValue, layoutValue}, expr.Comment) + align := b.targetData.ABITypeAlignment(typ) + buf.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align))) return buf, nil } else { buf := llvmutil.CreateEntryBlockAlloca(b.Builder, typ, expr.Comment) @@ -2223,6 +2225,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { sliceType := expr.Type().Underlying().(*types.Slice) llvmElemType := b.getLLVMType(sliceType.Elem()) elemSize := b.targetData.TypeAllocSize(llvmElemType) + elemAlign := b.targetData.ABITypeAlignment(llvmElemType) elemSizeValue := llvm.ConstInt(b.uintptrType, elemSize, false) maxSize := b.maxSliceSize(llvmElemType) @@ -2246,6 +2249,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap") layoutValue := b.createObjectLayout(llvmElemType, expr.Pos()) slicePtr := b.createRuntimeCall("alloc", []llvm.Value{sliceSize, layoutValue}, "makeslice.buf") + slicePtr.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(elemAlign))) // Extend or truncate if necessary. This is safe as we've already done // the bounds check. diff --git a/compiler/llvm.go b/compiler/llvm.go index d693f6ed73..5d4eaaa64b 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -127,12 +127,14 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value { // Packed data is bigger than a pointer, so allocate it on the heap. sizeValue := llvm.ConstInt(b.uintptrType, size, false) + align := b.targetData.ABITypeAlignment(packedType) alloc := b.mod.NamedFunction("runtime.alloc") packedAlloc := b.CreateCall(alloc.GlobalValueType(), alloc, []llvm.Value{ sizeValue, llvm.ConstNull(b.dataPtrType), llvm.Undef(b.dataPtrType), // unused context parameter }, "") + packedAlloc.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align))) if b.NeedsStackObjects { b.trackPointer(packedAlloc) } diff --git a/compiler/testdata/gc.ll b/compiler/testdata/gc.ll index a696ee409f..82260fbf45 100644 --- a/compiler/testdata/gc.ll +++ b/compiler/testdata/gc.ll @@ -39,16 +39,16 @@ entry: define hidden void @main.newScalar(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %new = call dereferenceable(1) ptr @runtime.alloc(i32 1, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %new = call align 1 dereferenceable(1) ptr @runtime.alloc(i32 1, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new, ptr @main.scalar1, align 4 - %new1 = call dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %new1 = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new1, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new1, ptr @main.scalar2, align 4 - %new2 = call dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %new2 = call align 8 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new2, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new2, ptr @main.scalar3, align 4 - %new3 = call dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %new3 = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new3, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new3, ptr @main.scalar4, align 4 ret void @@ -58,13 +58,13 @@ entry: define hidden void @main.newArray(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %new = call dereferenceable(3) ptr @runtime.alloc(i32 3, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %new = call align 1 dereferenceable(3) ptr @runtime.alloc(i32 3, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new, ptr @main.array1, align 4 - %new1 = call dereferenceable(71) ptr @runtime.alloc(i32 71, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %new1 = call align 1 dereferenceable(71) ptr @runtime.alloc(i32 71, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new1, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new1, ptr @main.array2, align 4 - %new2 = call dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 67 to ptr), ptr undef) #3 + %new2 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 67 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new2, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new2, ptr @main.array3, align 4 ret void @@ -74,16 +74,16 @@ entry: define hidden void @main.newStruct(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %new = call ptr @runtime.alloc(i32 0, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %new = call align 1 ptr @runtime.alloc(i32 0, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new, ptr @main.struct1, align 4 - %new1 = call dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %new1 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new1, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new1, ptr @main.struct2, align 4 - %new2 = call dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-2000000000000001", ptr undef) #3 + %new2 = call align 4 dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-2000000000000001", ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new2, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new2, ptr @main.struct3, align 4 - %new3 = call dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-0001", ptr undef) #3 + %new3 = call align 4 dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-0001", ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new3, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new3, ptr @main.struct4, align 4 ret void @@ -93,7 +93,7 @@ entry: define hidden ptr @main.newFuncValue(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %new = call dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 197 to ptr), ptr undef) #3 + %new = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 197 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3 ret ptr %new } @@ -102,17 +102,17 @@ entry: define hidden void @main.makeSlice(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %makeslice = call dereferenceable(5) ptr @runtime.alloc(i32 5, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %makeslice = call align 1 dereferenceable(5) ptr @runtime.alloc(i32 5, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %makeslice, ptr nonnull %stackalloc, ptr undef) #3 store ptr %makeslice, ptr @main.slice1, align 4 store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice1, i32 0, i32 1), align 4 store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice1, i32 0, i32 2), align 4 - %makeslice1 = call dereferenceable(20) ptr @runtime.alloc(i32 20, ptr nonnull inttoptr (i32 67 to ptr), ptr undef) #3 + %makeslice1 = call align 4 dereferenceable(20) ptr @runtime.alloc(i32 20, ptr nonnull inttoptr (i32 67 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %makeslice1, ptr nonnull %stackalloc, ptr undef) #3 store ptr %makeslice1, ptr @main.slice2, align 4 store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice2, i32 0, i32 1), align 4 store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice2, i32 0, i32 2), align 4 - %makeslice3 = call dereferenceable(60) ptr @runtime.alloc(i32 60, ptr nonnull inttoptr (i32 71 to ptr), ptr undef) #3 + %makeslice3 = call align 4 dereferenceable(60) ptr @runtime.alloc(i32 60, ptr nonnull inttoptr (i32 71 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %makeslice3, ptr nonnull %stackalloc, ptr undef) #3 store ptr %makeslice3, ptr @main.slice3, align 4 store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice3, i32 0, i32 1), align 4 @@ -124,7 +124,7 @@ entry: define hidden %runtime._interface @main.makeInterface(double %v.r, double %v.i, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %0 = call dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #3 + %0 = call align 8 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #3 store double %v.r, ptr %0, align 8 %.repack1 = getelementptr inbounds { double, double }, ptr %0, i32 0, i32 1 diff --git a/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll b/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll index 6918e7be66..f0a692c0f2 100644 --- a/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll +++ b/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll @@ -63,9 +63,9 @@ entry: ; Function Attrs: nounwind define hidden void @main.closureFunctionGoroutine(ptr %context) unnamed_addr #1 { entry: - %n = call dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 + %n = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 store i32 3, ptr %n, align 4 - %0 = call dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #9 + %0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #9 store i32 5, ptr %0, align 4 %1 = getelementptr inbounds { i32, ptr }, ptr %0, i32 0, i32 1 store ptr %n, ptr %1, align 4 @@ -98,7 +98,7 @@ declare void @runtime.printint32(i32, ptr) #2 ; Function Attrs: nounwind define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #1 { entry: - %0 = call dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #9 + %0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #9 store i32 5, ptr %0, align 4 %1 = getelementptr inbounds { i32, ptr, ptr }, ptr %0, i32 0, i32 1 store ptr %fn.context, ptr %1, align 4 @@ -148,7 +148,7 @@ declare void @runtime.chanClose(ptr dereferenceable_or_null(32), ptr) #2 ; Function Attrs: nounwind define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 { entry: - %0 = call dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #9 + %0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #9 store ptr %itf.value, ptr %0, align 4 %1 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 1 store ptr @"main$string", ptr %1, align 4 diff --git a/compiler/testdata/goroutine-wasm-asyncify.ll b/compiler/testdata/goroutine-wasm-asyncify.ll index db7ce187f7..1281857d31 100644 --- a/compiler/testdata/goroutine-wasm-asyncify.ll +++ b/compiler/testdata/goroutine-wasm-asyncify.ll @@ -66,12 +66,12 @@ entry: define hidden void @main.closureFunctionGoroutine(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %n = call dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 + %n = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 call void @runtime.trackPointer(ptr nonnull %n, ptr nonnull %stackalloc, ptr undef) #9 store i32 3, ptr %n, align 4 call void @runtime.trackPointer(ptr nonnull %n, ptr nonnull %stackalloc, ptr undef) #9 call void @runtime.trackPointer(ptr nonnull @"main.closureFunctionGoroutine$1", ptr nonnull %stackalloc, ptr undef) #9 - %0 = call dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #9 + %0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #9 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #9 store i32 5, ptr %0, align 4 %1 = getelementptr inbounds { i32, ptr }, ptr %0, i32 0, i32 1 @@ -106,7 +106,7 @@ declare void @runtime.printint32(i32, ptr) #1 define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %0 = call dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #9 + %0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #9 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #9 store i32 5, ptr %0, align 4 %1 = getelementptr inbounds { i32, ptr, ptr }, ptr %0, i32 0, i32 1 @@ -158,7 +158,7 @@ declare void @runtime.chanClose(ptr dereferenceable_or_null(32), ptr) #1 define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %0 = call dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #9 + %0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #9 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #9 store ptr %itf.value, ptr %0, align 4 %1 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 1 diff --git a/compiler/testdata/slice.ll b/compiler/testdata/slice.ll index 29d5ed8a41..092dabbff4 100644 --- a/compiler/testdata/slice.ll +++ b/compiler/testdata/slice.ll @@ -48,7 +48,7 @@ declare void @runtime.lookupPanic(ptr) #1 define hidden { ptr, i32, i32 } @main.sliceAppendValues(ptr %ints.data, i32 %ints.len, i32 %ints.cap, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %varargs = call dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %varargs = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %varargs, ptr nonnull %stackalloc, ptr undef) #3 store i32 1, ptr %varargs, align 4 %0 = getelementptr inbounds [3 x i32], ptr %varargs, i32 0, i32 1 @@ -100,7 +100,7 @@ entry: br i1 %slice.maxcap, label %slice.throw, label %slice.next slice.next: ; preds = %entry - %makeslice.buf = call ptr @runtime.alloc(i32 %len, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %makeslice.buf = call align 1 ptr @runtime.alloc(i32 %len, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %0 = insertvalue { ptr, i32, i32 } undef, ptr %makeslice.buf, 0 %1 = insertvalue { ptr, i32, i32 } %0, i32 %len, 1 %2 = insertvalue { ptr, i32, i32 } %1, i32 %len, 2 @@ -123,7 +123,7 @@ entry: slice.next: ; preds = %entry %makeslice.cap = shl nuw i32 %len, 1 - %makeslice.buf = call ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %makeslice.buf = call align 2 ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %0 = insertvalue { ptr, i32, i32 } undef, ptr %makeslice.buf, 0 %1 = insertvalue { ptr, i32, i32 } %0, i32 %len, 1 %2 = insertvalue { ptr, i32, i32 } %1, i32 %len, 2 @@ -144,7 +144,7 @@ entry: slice.next: ; preds = %entry %makeslice.cap = mul i32 %len, 3 - %makeslice.buf = call ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %makeslice.buf = call align 1 ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %0 = insertvalue { ptr, i32, i32 } undef, ptr %makeslice.buf, 0 %1 = insertvalue { ptr, i32, i32 } %0, i32 %len, 1 %2 = insertvalue { ptr, i32, i32 } %1, i32 %len, 2 @@ -165,7 +165,7 @@ entry: slice.next: ; preds = %entry %makeslice.cap = shl nuw i32 %len, 2 - %makeslice.buf = call ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %makeslice.buf = call align 4 ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %0 = insertvalue { ptr, i32, i32 } undef, ptr %makeslice.buf, 0 %1 = insertvalue { ptr, i32, i32 } %0, i32 %len, 1 %2 = insertvalue { ptr, i32, i32 } %1, i32 %len, 2 @@ -216,7 +216,7 @@ declare void @runtime.sliceToArrayPointerPanic(ptr) #1 define hidden ptr @main.SliceToArrayConst(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %makeslice = call dereferenceable(24) ptr @runtime.alloc(i32 24, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %makeslice = call align 4 dereferenceable(24) ptr @runtime.alloc(i32 24, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %makeslice, ptr nonnull %stackalloc, ptr undef) #3 br i1 false, label %slicetoarray.throw, label %slicetoarray.next From e6caa3fe9ef0a68779953b7fc9c3cfce3c8158ca Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 25 Jun 2024 20:14:05 +0200 Subject: [PATCH 084/444] transform: fix incorrect alignment of heap-to-stack transform It assumed the maximum alignment was equal to sizeof(void*), which is definitely not the case. So this only worked more or less by accident previously. It now uses the alignment as specified by the frontend, or else `unsafe.Alignof(complex128)` which is typically the maximum alignment of a given platform (though this shouldn't really happen in practice: the optimizer should keep the 'align' attribute in place). --- go.mod | 2 +- go.sum | 4 ++-- transform/allocs.go | 31 ++++++++++++++----------------- transform/testdata/allocs.ll | 31 +++++++++++++++++++++++-------- transform/testdata/allocs.out.ll | 25 ++++++++++++++++++++++--- 5 files changed, 62 insertions(+), 31 deletions(-) diff --git a/go.mod b/go.mod index d17f62e3f7..b054e77b63 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( golang.org/x/sys v0.21.0 golang.org/x/tools v0.22.1-0.20240621165957-db513b091504 gopkg.in/yaml.v2 v2.4.0 - tinygo.org/x/go-llvm v0.0.0-20240518103902-697964f2a9dc + tinygo.org/x/go-llvm v0.0.0-20240627184919-3b50c76783a8 ) require ( diff --git a/go.sum b/go.sum index c093ee07ca..8d6dc516ca 100644 --- a/go.sum +++ b/go.sum @@ -106,5 +106,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -tinygo.org/x/go-llvm v0.0.0-20240518103902-697964f2a9dc h1:TCzibFa4oLu+njEP3fnRUmZ+QQeb8BjtOwctgcjzL0k= -tinygo.org/x/go-llvm v0.0.0-20240518103902-697964f2a9dc/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0= +tinygo.org/x/go-llvm v0.0.0-20240627184919-3b50c76783a8 h1:bLsZXRUBavt++CJlMN7sppNziqu3LyamESLhFJcpqFQ= +tinygo.org/x/go-llvm v0.0.0-20240627184919-3b50c76783a8/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0= diff --git a/transform/allocs.go b/transform/allocs.go index 67ca1ea435..870faa5b75 100644 --- a/transform/allocs.go +++ b/transform/allocs.go @@ -29,10 +29,14 @@ func OptimizeAllocs(mod llvm.Module, printAllocs *regexp.Regexp, maxStackAlloc u targetData := llvm.NewTargetData(mod.DataLayout()) defer targetData.Dispose() - ptrType := llvm.PointerType(mod.Context().Int8Type(), 0) - builder := mod.Context().NewBuilder() + ctx := mod.Context() + builder := ctx.NewBuilder() defer builder.Dispose() + // Determine the maximum alignment on this platform. + complex128Type := ctx.StructType([]llvm.Type{ctx.DoubleType(), ctx.DoubleType()}, false) + maxAlign := int64(targetData.ABITypeAlignment(complex128Type)) + for _, heapalloc := range getUses(allocator) { logAllocs := printAllocs != nil && printAllocs.MatchString(heapalloc.InstructionParent().Parent().Name()) if heapalloc.Operand(0).IsAConstantInt().IsNil() { @@ -90,21 +94,14 @@ func OptimizeAllocs(mod llvm.Module, printAllocs *regexp.Regexp, maxStackAlloc u } // The pointer value does not escape. - // Determine the appropriate alignment of the alloca. The size of the - // allocation gives us a hint what the alignment should be. - var alignment int - if size%2 != 0 { - alignment = 1 - } else if size%4 != 0 { - alignment = 2 - } else if size%8 != 0 { - alignment = 4 - } else { - alignment = 8 - } - if pointerAlignment := targetData.ABITypeAlignment(ptrType); pointerAlignment < alignment { - // Use min(alignment, alignof(void*)) as the alignment. - alignment = pointerAlignment + // Determine the appropriate alignment of the alloca. + attr := heapalloc.GetCallSiteEnumAttribute(0, llvm.AttributeKindID("align")) + alignment := int(maxAlign) + if !attr.IsNil() { + // 'align' return value attribute is set, so use it. + // This is basically always the case, but to be sure we'll default + // to maxAlign if it isn't. + alignment = int(attr.GetEnumValue()) } // Insert alloca in the entry block. Do it here so that mem2reg can diff --git a/transform/testdata/allocs.ll b/transform/testdata/allocs.ll index 1c2fdd5aa4..4f6960ef99 100644 --- a/transform/testdata/allocs.ll +++ b/transform/testdata/allocs.ll @@ -7,7 +7,7 @@ declare nonnull ptr @runtime.alloc(i32, ptr) ; Test allocating a single int (i32) that should be allocated on the stack. define void @testInt() { - %alloc = call ptr @runtime.alloc(i32 4, ptr null) + %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null) store i32 5, ptr %alloc ret void } @@ -15,7 +15,7 @@ define void @testInt() { ; Test allocating an array of 3 i16 values that should be allocated on the ; stack. define i16 @testArray() { - %alloc = call ptr @runtime.alloc(i32 6, ptr null) + %alloc = call align 2 ptr @runtime.alloc(i32 6, ptr null) %alloc.1 = getelementptr i16, ptr %alloc, i32 1 store i16 5, ptr %alloc.1 %alloc.2 = getelementptr i16, ptr %alloc, i32 2 @@ -23,30 +23,45 @@ define i16 @testArray() { ret i16 %val } +; Test allocating objects with an unknown alignment. +define void @testUnknownAlign() { + %alloc32 = call ptr @runtime.alloc(i32 32, ptr null) + store i8 5, ptr %alloc32 + %alloc24 = call ptr @runtime.alloc(i32 24, ptr null) + store i16 5, ptr %alloc24 + %alloc12 = call ptr @runtime.alloc(i32 12, ptr null) + store i16 5, ptr %alloc12 + %alloc6 = call ptr @runtime.alloc(i32 6, ptr null) + store i16 5, ptr %alloc6 + %alloc3 = call ptr @runtime.alloc(i32 3, ptr null) + store i16 5, ptr %alloc3 + ret void +} + ; Call a function that will let the pointer escape, so the heap-to-stack ; transform shouldn't be applied. define void @testEscapingCall() { - %alloc = call ptr @runtime.alloc(i32 4, ptr null) + %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null) %val = call ptr @escapeIntPtr(ptr %alloc) ret void } define void @testEscapingCall2() { - %alloc = call ptr @runtime.alloc(i32 4, ptr null) + %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null) %val = call ptr @escapeIntPtrSometimes(ptr %alloc, ptr %alloc) ret void } ; Call a function that doesn't let the pointer escape. define void @testNonEscapingCall() { - %alloc = call ptr @runtime.alloc(i32 4, ptr null) + %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null) %val = call ptr @noescapeIntPtr(ptr %alloc) ret void } ; Return the allocated value, which lets it escape. define ptr @testEscapingReturn() { - %alloc = call ptr @runtime.alloc(i32 4, ptr null) + %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null) ret ptr %alloc } @@ -55,7 +70,7 @@ define void @testNonEscapingLoop() { entry: br label %loop loop: - %alloc = call ptr @runtime.alloc(i32 4, ptr null) + %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null) %ptr = call ptr @noescapeIntPtr(ptr %alloc) %result = icmp eq ptr null, %ptr br i1 %result, label %loop, label %end @@ -65,7 +80,7 @@ end: ; Test a zero-sized allocation. define void @testZeroSizedAlloc() { - %alloc = call ptr @runtime.alloc(i32 0, ptr null) + %alloc = call align 1 ptr @runtime.alloc(i32 0, ptr null) %ptr = call ptr @noescapeIntPtr(ptr %alloc) ret void } diff --git a/transform/testdata/allocs.out.ll b/transform/testdata/allocs.out.ll index b3c8bf8f39..e4a5e4f7e9 100644 --- a/transform/testdata/allocs.out.ll +++ b/transform/testdata/allocs.out.ll @@ -22,14 +22,33 @@ define i16 @testArray() { ret i16 %val } +define void @testUnknownAlign() { + %stackalloc4 = alloca [32 x i8], align 8 + %stackalloc3 = alloca [24 x i8], align 8 + %stackalloc2 = alloca [12 x i8], align 8 + %stackalloc1 = alloca [6 x i8], align 8 + %stackalloc = alloca [3 x i8], align 8 + store [32 x i8] zeroinitializer, ptr %stackalloc4, align 8 + store i8 5, ptr %stackalloc4, align 1 + store [24 x i8] zeroinitializer, ptr %stackalloc3, align 8 + store i16 5, ptr %stackalloc3, align 2 + store [12 x i8] zeroinitializer, ptr %stackalloc2, align 8 + store i16 5, ptr %stackalloc2, align 2 + store [6 x i8] zeroinitializer, ptr %stackalloc1, align 8 + store i16 5, ptr %stackalloc1, align 2 + store [3 x i8] zeroinitializer, ptr %stackalloc, align 8 + store i16 5, ptr %stackalloc, align 2 + ret void +} + define void @testEscapingCall() { - %alloc = call ptr @runtime.alloc(i32 4, ptr null) + %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null) %val = call ptr @escapeIntPtr(ptr %alloc) ret void } define void @testEscapingCall2() { - %alloc = call ptr @runtime.alloc(i32 4, ptr null) + %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null) %val = call ptr @escapeIntPtrSometimes(ptr %alloc, ptr %alloc) ret void } @@ -42,7 +61,7 @@ define void @testNonEscapingCall() { } define ptr @testEscapingReturn() { - %alloc = call ptr @runtime.alloc(i32 4, ptr null) + %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null) ret ptr %alloc } From 5ca3e4a2da72a2cb981b9af061a14c1128870957 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Mon, 8 Jul 2024 22:09:30 +0200 Subject: [PATCH 085/444] runtime: implement dummy getAuxv to satisfy golang.org/x/sys/cpu (#4325) Fixes the program package main import _ "golang.org/x/sys/cpu" func main() { } --- src/runtime/runtime.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go index a93260ba85..bb937f0447 100644 --- a/src/runtime/runtime.go +++ b/src/runtime/runtime.go @@ -122,3 +122,8 @@ func write(fd uintptr, p unsafe.Pointer, n int32) int32 { } return 0 } + +// getAuxv is linknamed from golang.org/x/sys/cpu. +func getAuxv() []uintptr { + return nil +} From 2d85fc6a6435f3bbdb8edaba6d41e6ec014f57f2 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Mon, 8 Jul 2024 22:09:47 +0200 Subject: [PATCH 086/444] internal/wasi: update to wasm-tools-go@v0.1.2 (#4326) * internal/wasi: update to wasm-tools-go@v0.1.1 See https://github.com/ydnar/wasm-tools-go/releases/tag/v0.1.1 for additional information. * internal/wasi: update to wasm-tools-go@v0.1.2 * internal/wasi: regenerate with wasm-tools-go@v0.1.3 --- src/internal/wasi/cli/v0.2.0/exit/exit.wit.go | 4 +- .../wasi/cli/v0.2.0/run/run.exports.go | 2 +- src/internal/wasi/cli/v0.2.0/run/run.wit.go | 2 +- .../wasi/filesystem/v0.2.0/types/abi.go | 28 ++- .../wasi/filesystem/v0.2.0/types/types.wit.go | 187 ++++++++++++------ src/internal/wasi/io/v0.2.0/streams/abi.go | 14 ++ .../wasi/io/v0.2.0/streams/streams.wit.go | 54 ++--- .../wasi/sockets/v0.2.0/ip-name-lookup/abi.go | 16 ++ .../ip-name-lookup/ip-name-lookup.wit.go | 8 +- .../wasi/sockets/v0.2.0/network/abi.go | 14 ++ .../sockets/v0.2.0/network/network.wit.go | 41 +++- .../tcp-create-socket.wit.go | 4 +- src/internal/wasi/sockets/v0.2.0/tcp/abi.go | 19 +- .../wasi/sockets/v0.2.0/tcp/tcp.wit.go | 111 ++++++----- .../udp-create-socket.wit.go | 4 +- src/internal/wasi/sockets/v0.2.0/udp/abi.go | 13 +- .../wasi/sockets/v0.2.0/udp/udp.wit.go | 56 +++--- src/vendor/github.com/ydnar/wasm-tools-go | 2 +- 18 files changed, 401 insertions(+), 178 deletions(-) create mode 100644 src/internal/wasi/io/v0.2.0/streams/abi.go create mode 100644 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go create mode 100644 src/internal/wasi/sockets/v0.2.0/network/abi.go diff --git a/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go b/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go index 36af67d244..d10b734c6d 100644 --- a/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go +++ b/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go @@ -16,8 +16,8 @@ import ( // exit: func(status: result) // //go:nosplit -func Exit(status cm.Result) { - status0 := cm.LowerResult(status) +func Exit(status cm.BoolResult) { + status0 := cm.BoolToU32(status) wasmimport_Exit((uint32)(status0)) return } diff --git a/src/internal/wasi/cli/v0.2.0/run/run.exports.go b/src/internal/wasi/cli/v0.2.0/run/run.exports.go index 267e75a081..3d0edcc802 100644 --- a/src/internal/wasi/cli/v0.2.0/run/run.exports.go +++ b/src/internal/wasi/cli/v0.2.0/run/run.exports.go @@ -15,5 +15,5 @@ var Exports struct { // Run the program. // // run: func() -> result - Run func() (result cm.Result) + Run func() (result cm.BoolResult) } diff --git a/src/internal/wasi/cli/v0.2.0/run/run.wit.go b/src/internal/wasi/cli/v0.2.0/run/run.wit.go index 728d0292ad..47e00180c4 100644 --- a/src/internal/wasi/cli/v0.2.0/run/run.wit.go +++ b/src/internal/wasi/cli/v0.2.0/run/run.wit.go @@ -13,6 +13,6 @@ import ( //export wasi:cli/run@0.2.0#run func wasmexport_Run() (result0 uint32) { result := Exports.Run() - result0 = cm.LowerResult(result) + result0 = cm.BoolToU32(result) return } diff --git a/src/internal/wasi/filesystem/v0.2.0/types/abi.go b/src/internal/wasi/filesystem/v0.2.0/types/abi.go index 288ef2e227..41f6848ecb 100644 --- a/src/internal/wasi/filesystem/v0.2.0/types/abi.go +++ b/src/internal/wasi/filesystem/v0.2.0/types/abi.go @@ -7,8 +7,24 @@ package types import ( "github.com/ydnar/wasm-tools-go/cm" wallclock "internal/wasi/clocks/v0.2.0/wall-clock" + "unsafe" ) +// DateTimeShape is used for storage in variant or result types. +type DateTimeShape struct { + shape [unsafe.Sizeof(wallclock.DateTime{})]byte +} + +// MetadataHashValueShape is used for storage in variant or result types. +type MetadataHashValueShape struct { + shape [unsafe.Sizeof(MetadataHashValue{})]byte +} + +// TupleListU8BoolShape is used for storage in variant or result types. +type TupleListU8BoolShape struct { + shape [unsafe.Sizeof(cm.Tuple[cm.List[uint8], bool]{})]byte +} + func lower_DateTime(v wallclock.DateTime) (f0 uint64, f1 uint32) { f0 = (uint64)(v.Seconds) f1 = (uint32)(v.Nanoseconds) @@ -16,7 +32,7 @@ func lower_DateTime(v wallclock.DateTime) (f0 uint64, f1 uint32) { } func lower_NewTimestamp(v NewTimestamp) (f0 uint32, f1 uint64, f2 uint32) { - f0 = (uint32)(cm.Tag(&v)) + f0 = (uint32)(v.Tag()) switch f0 { case 2: // timestamp v1, v2 := lower_DateTime(*v.Timestamp()) @@ -25,3 +41,13 @@ func lower_NewTimestamp(v NewTimestamp) (f0 uint32, f1 uint64, f2 uint32) { } return } + +// DescriptorStatShape is used for storage in variant or result types. +type DescriptorStatShape struct { + shape [unsafe.Sizeof(DescriptorStat{})]byte +} + +// OptionDirectoryEntryShape is used for storage in variant or result types. +type OptionDirectoryEntryShape struct { + shape [unsafe.Sizeof(cm.Option[DirectoryEntry]{})]byte +} diff --git a/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go b/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go index 387426d1b6..226edabd78 100644 --- a/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go +++ b/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go @@ -89,6 +89,22 @@ const ( DescriptorTypeSocket ) +var stringsDescriptorType = [8]string{ + "unknown", + "block-device", + "character-device", + "directory", + "fifo", + "symbolic-link", + "regular-file", + "socket", +} + +// String implements [fmt.Stringer], returning the enum case name of e. +func (e DescriptorType) String() string { + return stringsDescriptorType[e] +} + // DescriptorFlags represents the flags "wasi:filesystem/types@0.2.0#descriptor-flags". // // Descriptor flags. @@ -263,7 +279,7 @@ func NewTimestampNoChange() NewTimestamp { // NoChange returns true if [NewTimestamp] represents the variant case "no-change". func (self *NewTimestamp) NoChange() bool { - return cm.Tag(self) == 0 + return self.Tag() == 0 } // NewTimestampNow returns a [NewTimestamp] of case "now". @@ -277,7 +293,7 @@ func NewTimestampNow() NewTimestamp { // Now returns true if [NewTimestamp] represents the variant case "now". func (self *NewTimestamp) Now() bool { - return cm.Tag(self) == 1 + return self.Tag() == 1 } // NewTimestampTimestamp returns a [NewTimestamp] of case "timestamp". @@ -470,6 +486,51 @@ const ( ErrorCodeCrossDevice ) +var stringsErrorCode = [37]string{ + "access", + "would-block", + "already", + "bad-descriptor", + "busy", + "deadlock", + "quota", + "exist", + "file-too-large", + "illegal-byte-sequence", + "in-progress", + "interrupted", + "invalid", + "io", + "is-directory", + "loop", + "too-many-links", + "message-size", + "name-too-long", + "no-device", + "no-entry", + "no-lock", + "insufficient-memory", + "insufficient-space", + "not-directory", + "not-empty", + "not-recoverable", + "unsupported", + "no-tty", + "no-such-device", + "overflow", + "not-permitted", + "pipe", + "read-only", + "invalid-seek", + "text-file-busy", + "cross-device", +} + +// String implements [fmt.Stringer], returning the enum case name of e. +func (e ErrorCode) String() string { + return stringsErrorCode[e] +} + // Advice represents the enum "wasi:filesystem/types@0.2.0#advice". // // File or memory access pattern advisory information. @@ -510,6 +571,20 @@ const ( AdviceNoReuse ) +var stringsAdvice = [6]string{ + "normal", + "sequential", + "random", + "will-need", + "dont-need", + "no-reuse", +} + +// String implements [fmt.Stringer], returning the enum case name of e. +func (e Advice) String() string { + return stringsAdvice[e] +} + // MetadataHashValue represents the record "wasi:filesystem/types@0.2.0#metadata-hash-value". // // A 128-bit hash value, split into parts because wasm doesn't have a @@ -560,7 +635,7 @@ func wasmimport_DescriptorResourceDrop(self0 uint32) // advise: func(offset: filesize, length: filesize, advice: advice) -> result<_, error-code> // //go:nosplit -func (self Descriptor) Advise(offset FileSize, length FileSize, advice Advice) (result cm.ErrResult[struct{}, ErrorCode]) { +func (self Descriptor) Advise(offset FileSize, length FileSize, advice Advice) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) offset0 := (uint64)(offset) length0 := (uint64)(length) @@ -571,7 +646,7 @@ func (self Descriptor) Advise(offset FileSize, length FileSize, advice Advice) ( //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.advise //go:noescape -func wasmimport_DescriptorAdvise(self0 uint32, offset0 uint64, length0 uint64, advice0 uint32, result *cm.ErrResult[struct{}, ErrorCode]) +func wasmimport_DescriptorAdvise(self0 uint32, offset0 uint64, length0 uint64, advice0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) // AppendViaStream represents the imported method "append-via-stream". // @@ -585,7 +660,7 @@ func wasmimport_DescriptorAdvise(self0 uint32, offset0 uint64, length0 uint64, a // append-via-stream: func() -> result // //go:nosplit -func (self Descriptor) AppendViaStream() (result cm.OKResult[streams.OutputStream, ErrorCode]) { +func (self Descriptor) AppendViaStream() (result cm.Result[streams.OutputStream, streams.OutputStream, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_DescriptorAppendViaStream((uint32)(self0), &result) return @@ -593,7 +668,7 @@ func (self Descriptor) AppendViaStream() (result cm.OKResult[streams.OutputStrea //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.append-via-stream //go:noescape -func wasmimport_DescriptorAppendViaStream(self0 uint32, result *cm.OKResult[streams.OutputStream, ErrorCode]) +func wasmimport_DescriptorAppendViaStream(self0 uint32, result *cm.Result[streams.OutputStream, streams.OutputStream, ErrorCode]) // CreateDirectoryAt represents the imported method "create-directory-at". // @@ -604,7 +679,7 @@ func wasmimport_DescriptorAppendViaStream(self0 uint32, result *cm.OKResult[stre // create-directory-at: func(path: string) -> result<_, error-code> // //go:nosplit -func (self Descriptor) CreateDirectoryAt(path string) (result cm.ErrResult[struct{}, ErrorCode]) { +func (self Descriptor) CreateDirectoryAt(path string) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) path0, path1 := cm.LowerString(path) wasmimport_DescriptorCreateDirectoryAt((uint32)(self0), (*uint8)(path0), (uint32)(path1), &result) @@ -613,7 +688,7 @@ func (self Descriptor) CreateDirectoryAt(path string) (result cm.ErrResult[struc //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.create-directory-at //go:noescape -func wasmimport_DescriptorCreateDirectoryAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.ErrResult[struct{}, ErrorCode]) +func wasmimport_DescriptorCreateDirectoryAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) // GetFlags represents the imported method "get-flags". // @@ -627,7 +702,7 @@ func wasmimport_DescriptorCreateDirectoryAt(self0 uint32, path0 *uint8, path1 ui // get-flags: func() -> result // //go:nosplit -func (self Descriptor) GetFlags() (result cm.OKResult[DescriptorFlags, ErrorCode]) { +func (self Descriptor) GetFlags() (result cm.Result[DescriptorFlags, DescriptorFlags, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_DescriptorGetFlags((uint32)(self0), &result) return @@ -635,7 +710,7 @@ func (self Descriptor) GetFlags() (result cm.OKResult[DescriptorFlags, ErrorCode //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.get-flags //go:noescape -func wasmimport_DescriptorGetFlags(self0 uint32, result *cm.OKResult[DescriptorFlags, ErrorCode]) +func wasmimport_DescriptorGetFlags(self0 uint32, result *cm.Result[DescriptorFlags, DescriptorFlags, ErrorCode]) // GetType represents the imported method "get-type". // @@ -653,7 +728,7 @@ func wasmimport_DescriptorGetFlags(self0 uint32, result *cm.OKResult[DescriptorF // get-type: func() -> result // //go:nosplit -func (self Descriptor) GetType() (result cm.OKResult[DescriptorType, ErrorCode]) { +func (self Descriptor) GetType() (result cm.Result[DescriptorType, DescriptorType, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_DescriptorGetType((uint32)(self0), &result) return @@ -661,7 +736,7 @@ func (self Descriptor) GetType() (result cm.OKResult[DescriptorType, ErrorCode]) //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.get-type //go:noescape -func wasmimport_DescriptorGetType(self0 uint32, result *cm.OKResult[DescriptorType, ErrorCode]) +func wasmimport_DescriptorGetType(self0 uint32, result *cm.Result[DescriptorType, DescriptorType, ErrorCode]) // IsSameObject represents the imported method "is-same-object". // @@ -697,7 +772,7 @@ func wasmimport_DescriptorIsSameObject(self0 uint32, other0 uint32) (result0 uin // new-path: string) -> result<_, error-code> // //go:nosplit -func (self Descriptor) LinkAt(oldPathFlags PathFlags, oldPath string, newDescriptor Descriptor, newPath string) (result cm.ErrResult[struct{}, ErrorCode]) { +func (self Descriptor) LinkAt(oldPathFlags PathFlags, oldPath string, newDescriptor Descriptor, newPath string) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) oldPathFlags0 := (uint32)(oldPathFlags) oldPath0, oldPath1 := cm.LowerString(oldPath) @@ -709,7 +784,7 @@ func (self Descriptor) LinkAt(oldPathFlags PathFlags, oldPath string, newDescrip //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.link-at //go:noescape -func wasmimport_DescriptorLinkAt(self0 uint32, oldPathFlags0 uint32, oldPath0 *uint8, oldPath1 uint32, newDescriptor0 uint32, newPath0 *uint8, newPath1 uint32, result *cm.ErrResult[struct{}, ErrorCode]) +func wasmimport_DescriptorLinkAt(self0 uint32, oldPathFlags0 uint32, oldPath0 *uint8, oldPath1 uint32, newDescriptor0 uint32, newPath0 *uint8, newPath1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) // MetadataHash represents the imported method "metadata-hash". // @@ -736,7 +811,7 @@ func wasmimport_DescriptorLinkAt(self0 uint32, oldPathFlags0 uint32, oldPath0 *u // metadata-hash: func() -> result // //go:nosplit -func (self Descriptor) MetadataHash() (result cm.OKResult[MetadataHashValue, ErrorCode]) { +func (self Descriptor) MetadataHash() (result cm.Result[MetadataHashValueShape, MetadataHashValue, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_DescriptorMetadataHash((uint32)(self0), &result) return @@ -744,7 +819,7 @@ func (self Descriptor) MetadataHash() (result cm.OKResult[MetadataHashValue, Err //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.metadata-hash //go:noescape -func wasmimport_DescriptorMetadataHash(self0 uint32, result *cm.OKResult[MetadataHashValue, ErrorCode]) +func wasmimport_DescriptorMetadataHash(self0 uint32, result *cm.Result[MetadataHashValueShape, MetadataHashValue, ErrorCode]) // MetadataHashAt represents the imported method "metadata-hash-at". // @@ -757,7 +832,7 @@ func wasmimport_DescriptorMetadataHash(self0 uint32, result *cm.OKResult[Metadat // error-code> // //go:nosplit -func (self Descriptor) MetadataHashAt(pathFlags PathFlags, path string) (result cm.OKResult[MetadataHashValue, ErrorCode]) { +func (self Descriptor) MetadataHashAt(pathFlags PathFlags, path string) (result cm.Result[MetadataHashValueShape, MetadataHashValue, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) pathFlags0 := (uint32)(pathFlags) path0, path1 := cm.LowerString(path) @@ -767,7 +842,7 @@ func (self Descriptor) MetadataHashAt(pathFlags PathFlags, path string) (result //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.metadata-hash-at //go:noescape -func wasmimport_DescriptorMetadataHashAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, result *cm.OKResult[MetadataHashValue, ErrorCode]) +func wasmimport_DescriptorMetadataHashAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, result *cm.Result[MetadataHashValueShape, MetadataHashValue, ErrorCode]) // OpenAt represents the imported method "open-at". // @@ -794,7 +869,7 @@ func wasmimport_DescriptorMetadataHashAt(self0 uint32, pathFlags0 uint32, path0 // descriptor-flags) -> result // //go:nosplit -func (self Descriptor) OpenAt(pathFlags PathFlags, path string, openFlags OpenFlags, flags DescriptorFlags) (result cm.OKResult[Descriptor, ErrorCode]) { +func (self Descriptor) OpenAt(pathFlags PathFlags, path string, openFlags OpenFlags, flags DescriptorFlags) (result cm.Result[Descriptor, Descriptor, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) pathFlags0 := (uint32)(pathFlags) path0, path1 := cm.LowerString(path) @@ -806,7 +881,7 @@ func (self Descriptor) OpenAt(pathFlags PathFlags, path string, openFlags OpenFl //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.open-at //go:noescape -func wasmimport_DescriptorOpenAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, openFlags0 uint32, flags0 uint32, result *cm.OKResult[Descriptor, ErrorCode]) +func wasmimport_DescriptorOpenAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, openFlags0 uint32, flags0 uint32, result *cm.Result[Descriptor, Descriptor, ErrorCode]) // Read represents the imported method "read". // @@ -826,7 +901,7 @@ func wasmimport_DescriptorOpenAt(self0 uint32, pathFlags0 uint32, path0 *uint8, // error-code> // //go:nosplit -func (self Descriptor) Read(length FileSize, offset FileSize) (result cm.OKResult[cm.Tuple[cm.List[uint8], bool], ErrorCode]) { +func (self Descriptor) Read(length FileSize, offset FileSize) (result cm.Result[TupleListU8BoolShape, cm.Tuple[cm.List[uint8], bool], ErrorCode]) { self0 := cm.Reinterpret[uint32](self) length0 := (uint64)(length) offset0 := (uint64)(offset) @@ -836,7 +911,7 @@ func (self Descriptor) Read(length FileSize, offset FileSize) (result cm.OKResul //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.read //go:noescape -func wasmimport_DescriptorRead(self0 uint32, length0 uint64, offset0 uint64, result *cm.OKResult[cm.Tuple[cm.List[uint8], bool], ErrorCode]) +func wasmimport_DescriptorRead(self0 uint32, length0 uint64, offset0 uint64, result *cm.Result[TupleListU8BoolShape, cm.Tuple[cm.List[uint8], bool], ErrorCode]) // ReadDirectory represents the imported method "read-directory". // @@ -853,7 +928,7 @@ func wasmimport_DescriptorRead(self0 uint32, length0 uint64, offset0 uint64, res // read-directory: func() -> result // //go:nosplit -func (self Descriptor) ReadDirectory() (result cm.OKResult[DirectoryEntryStream, ErrorCode]) { +func (self Descriptor) ReadDirectory() (result cm.Result[DirectoryEntryStream, DirectoryEntryStream, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_DescriptorReadDirectory((uint32)(self0), &result) return @@ -861,7 +936,7 @@ func (self Descriptor) ReadDirectory() (result cm.OKResult[DirectoryEntryStream, //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.read-directory //go:noescape -func wasmimport_DescriptorReadDirectory(self0 uint32, result *cm.OKResult[DirectoryEntryStream, ErrorCode]) +func wasmimport_DescriptorReadDirectory(self0 uint32, result *cm.Result[DirectoryEntryStream, DirectoryEntryStream, ErrorCode]) // ReadViaStream represents the imported method "read-via-stream". // @@ -877,7 +952,7 @@ func wasmimport_DescriptorReadDirectory(self0 uint32, result *cm.OKResult[Direct // read-via-stream: func(offset: filesize) -> result // //go:nosplit -func (self Descriptor) ReadViaStream(offset FileSize) (result cm.OKResult[streams.InputStream, ErrorCode]) { +func (self Descriptor) ReadViaStream(offset FileSize) (result cm.Result[streams.InputStream, streams.InputStream, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) offset0 := (uint64)(offset) wasmimport_DescriptorReadViaStream((uint32)(self0), (uint64)(offset0), &result) @@ -886,7 +961,7 @@ func (self Descriptor) ReadViaStream(offset FileSize) (result cm.OKResult[stream //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.read-via-stream //go:noescape -func wasmimport_DescriptorReadViaStream(self0 uint32, offset0 uint64, result *cm.OKResult[streams.InputStream, ErrorCode]) +func wasmimport_DescriptorReadViaStream(self0 uint32, offset0 uint64, result *cm.Result[streams.InputStream, streams.InputStream, ErrorCode]) // ReadLinkAt represents the imported method "readlink-at". // @@ -900,7 +975,7 @@ func wasmimport_DescriptorReadViaStream(self0 uint32, offset0 uint64, result *cm // readlink-at: func(path: string) -> result // //go:nosplit -func (self Descriptor) ReadLinkAt(path string) (result cm.OKResult[string, ErrorCode]) { +func (self Descriptor) ReadLinkAt(path string) (result cm.Result[string, string, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) path0, path1 := cm.LowerString(path) wasmimport_DescriptorReadLinkAt((uint32)(self0), (*uint8)(path0), (uint32)(path1), &result) @@ -909,7 +984,7 @@ func (self Descriptor) ReadLinkAt(path string) (result cm.OKResult[string, Error //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.readlink-at //go:noescape -func wasmimport_DescriptorReadLinkAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.OKResult[string, ErrorCode]) +func wasmimport_DescriptorReadLinkAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.Result[string, string, ErrorCode]) // RemoveDirectoryAt represents the imported method "remove-directory-at". // @@ -922,7 +997,7 @@ func wasmimport_DescriptorReadLinkAt(self0 uint32, path0 *uint8, path1 uint32, r // remove-directory-at: func(path: string) -> result<_, error-code> // //go:nosplit -func (self Descriptor) RemoveDirectoryAt(path string) (result cm.ErrResult[struct{}, ErrorCode]) { +func (self Descriptor) RemoveDirectoryAt(path string) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) path0, path1 := cm.LowerString(path) wasmimport_DescriptorRemoveDirectoryAt((uint32)(self0), (*uint8)(path0), (uint32)(path1), &result) @@ -931,7 +1006,7 @@ func (self Descriptor) RemoveDirectoryAt(path string) (result cm.ErrResult[struc //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.remove-directory-at //go:noescape -func wasmimport_DescriptorRemoveDirectoryAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.ErrResult[struct{}, ErrorCode]) +func wasmimport_DescriptorRemoveDirectoryAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) // RenameAt represents the imported method "rename-at". // @@ -943,7 +1018,7 @@ func wasmimport_DescriptorRemoveDirectoryAt(self0 uint32, path0 *uint8, path1 ui // string) -> result<_, error-code> // //go:nosplit -func (self Descriptor) RenameAt(oldPath string, newDescriptor Descriptor, newPath string) (result cm.ErrResult[struct{}, ErrorCode]) { +func (self Descriptor) RenameAt(oldPath string, newDescriptor Descriptor, newPath string) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) oldPath0, oldPath1 := cm.LowerString(oldPath) newDescriptor0 := cm.Reinterpret[uint32](newDescriptor) @@ -954,7 +1029,7 @@ func (self Descriptor) RenameAt(oldPath string, newDescriptor Descriptor, newPat //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.rename-at //go:noescape -func wasmimport_DescriptorRenameAt(self0 uint32, oldPath0 *uint8, oldPath1 uint32, newDescriptor0 uint32, newPath0 *uint8, newPath1 uint32, result *cm.ErrResult[struct{}, ErrorCode]) +func wasmimport_DescriptorRenameAt(self0 uint32, oldPath0 *uint8, oldPath1 uint32, newDescriptor0 uint32, newPath0 *uint8, newPath1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) // SetSize represents the imported method "set-size". // @@ -966,7 +1041,7 @@ func wasmimport_DescriptorRenameAt(self0 uint32, oldPath0 *uint8, oldPath1 uint3 // set-size: func(size: filesize) -> result<_, error-code> // //go:nosplit -func (self Descriptor) SetSize(size FileSize) (result cm.ErrResult[struct{}, ErrorCode]) { +func (self Descriptor) SetSize(size FileSize) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) size0 := (uint64)(size) wasmimport_DescriptorSetSize((uint32)(self0), (uint64)(size0), &result) @@ -975,7 +1050,7 @@ func (self Descriptor) SetSize(size FileSize) (result cm.ErrResult[struct{}, Err //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.set-size //go:noescape -func wasmimport_DescriptorSetSize(self0 uint32, size0 uint64, result *cm.ErrResult[struct{}, ErrorCode]) +func wasmimport_DescriptorSetSize(self0 uint32, size0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) // SetTimes represents the imported method "set-times". // @@ -989,7 +1064,7 @@ func wasmimport_DescriptorSetSize(self0 uint32, size0 uint64, result *cm.ErrResu // new-timestamp) -> result<_, error-code> // //go:nosplit -func (self Descriptor) SetTimes(dataAccessTimestamp NewTimestamp, dataModificationTimestamp NewTimestamp) (result cm.ErrResult[struct{}, ErrorCode]) { +func (self Descriptor) SetTimes(dataAccessTimestamp NewTimestamp, dataModificationTimestamp NewTimestamp) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) dataAccessTimestamp0, dataAccessTimestamp1, dataAccessTimestamp2 := lower_NewTimestamp(dataAccessTimestamp) dataModificationTimestamp0, dataModificationTimestamp1, dataModificationTimestamp2 := lower_NewTimestamp(dataModificationTimestamp) @@ -999,7 +1074,7 @@ func (self Descriptor) SetTimes(dataAccessTimestamp NewTimestamp, dataModificati //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.set-times //go:noescape -func wasmimport_DescriptorSetTimes(self0 uint32, dataAccessTimestamp0 uint32, dataAccessTimestamp1 uint64, dataAccessTimestamp2 uint32, dataModificationTimestamp0 uint32, dataModificationTimestamp1 uint64, dataModificationTimestamp2 uint32, result *cm.ErrResult[struct{}, ErrorCode]) +func wasmimport_DescriptorSetTimes(self0 uint32, dataAccessTimestamp0 uint32, dataAccessTimestamp1 uint64, dataAccessTimestamp2 uint32, dataModificationTimestamp0 uint32, dataModificationTimestamp1 uint64, dataModificationTimestamp2 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) // SetTimesAt represents the imported method "set-times-at". // @@ -1014,7 +1089,7 @@ func wasmimport_DescriptorSetTimes(self0 uint32, dataAccessTimestamp0 uint32, da // new-timestamp, data-modification-timestamp: new-timestamp) -> result<_, error-code> // //go:nosplit -func (self Descriptor) SetTimesAt(pathFlags PathFlags, path string, dataAccessTimestamp NewTimestamp, dataModificationTimestamp NewTimestamp) (result cm.ErrResult[struct{}, ErrorCode]) { +func (self Descriptor) SetTimesAt(pathFlags PathFlags, path string, dataAccessTimestamp NewTimestamp, dataModificationTimestamp NewTimestamp) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) pathFlags0 := (uint32)(pathFlags) path0, path1 := cm.LowerString(path) @@ -1026,7 +1101,7 @@ func (self Descriptor) SetTimesAt(pathFlags PathFlags, path string, dataAccessTi //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.set-times-at //go:noescape -func wasmimport_DescriptorSetTimesAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, dataAccessTimestamp0 uint32, dataAccessTimestamp1 uint64, dataAccessTimestamp2 uint32, dataModificationTimestamp0 uint32, dataModificationTimestamp1 uint64, dataModificationTimestamp2 uint32, result *cm.ErrResult[struct{}, ErrorCode]) +func wasmimport_DescriptorSetTimesAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, dataAccessTimestamp0 uint32, dataAccessTimestamp1 uint64, dataAccessTimestamp2 uint32, dataModificationTimestamp0 uint32, dataModificationTimestamp1 uint64, dataModificationTimestamp2 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) // Stat represents the imported method "stat". // @@ -1043,7 +1118,7 @@ func wasmimport_DescriptorSetTimesAt(self0 uint32, pathFlags0 uint32, path0 *uin // stat: func() -> result // //go:nosplit -func (self Descriptor) Stat() (result cm.OKResult[DescriptorStat, ErrorCode]) { +func (self Descriptor) Stat() (result cm.Result[DescriptorStatShape, DescriptorStat, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_DescriptorStat((uint32)(self0), &result) return @@ -1051,7 +1126,7 @@ func (self Descriptor) Stat() (result cm.OKResult[DescriptorStat, ErrorCode]) { //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.stat //go:noescape -func wasmimport_DescriptorStat(self0 uint32, result *cm.OKResult[DescriptorStat, ErrorCode]) +func wasmimport_DescriptorStat(self0 uint32, result *cm.Result[DescriptorStatShape, DescriptorStat, ErrorCode]) // StatAt represents the imported method "stat-at". // @@ -1067,7 +1142,7 @@ func wasmimport_DescriptorStat(self0 uint32, result *cm.OKResult[DescriptorStat, // error-code> // //go:nosplit -func (self Descriptor) StatAt(pathFlags PathFlags, path string) (result cm.OKResult[DescriptorStat, ErrorCode]) { +func (self Descriptor) StatAt(pathFlags PathFlags, path string) (result cm.Result[DescriptorStatShape, DescriptorStat, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) pathFlags0 := (uint32)(pathFlags) path0, path1 := cm.LowerString(path) @@ -1077,7 +1152,7 @@ func (self Descriptor) StatAt(pathFlags PathFlags, path string) (result cm.OKRes //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.stat-at //go:noescape -func wasmimport_DescriptorStatAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, result *cm.OKResult[DescriptorStat, ErrorCode]) +func wasmimport_DescriptorStatAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, result *cm.Result[DescriptorStatShape, DescriptorStat, ErrorCode]) // SymlinkAt represents the imported method "symlink-at". // @@ -1091,7 +1166,7 @@ func wasmimport_DescriptorStatAt(self0 uint32, pathFlags0 uint32, path0 *uint8, // symlink-at: func(old-path: string, new-path: string) -> result<_, error-code> // //go:nosplit -func (self Descriptor) SymlinkAt(oldPath string, newPath string) (result cm.ErrResult[struct{}, ErrorCode]) { +func (self Descriptor) SymlinkAt(oldPath string, newPath string) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) oldPath0, oldPath1 := cm.LowerString(oldPath) newPath0, newPath1 := cm.LowerString(newPath) @@ -1101,7 +1176,7 @@ func (self Descriptor) SymlinkAt(oldPath string, newPath string) (result cm.ErrR //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.symlink-at //go:noescape -func wasmimport_DescriptorSymlinkAt(self0 uint32, oldPath0 *uint8, oldPath1 uint32, newPath0 *uint8, newPath1 uint32, result *cm.ErrResult[struct{}, ErrorCode]) +func wasmimport_DescriptorSymlinkAt(self0 uint32, oldPath0 *uint8, oldPath1 uint32, newPath0 *uint8, newPath1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) // Sync represents the imported method "sync". // @@ -1115,7 +1190,7 @@ func wasmimport_DescriptorSymlinkAt(self0 uint32, oldPath0 *uint8, oldPath1 uint // sync: func() -> result<_, error-code> // //go:nosplit -func (self Descriptor) Sync() (result cm.ErrResult[struct{}, ErrorCode]) { +func (self Descriptor) Sync() (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_DescriptorSync((uint32)(self0), &result) return @@ -1123,7 +1198,7 @@ func (self Descriptor) Sync() (result cm.ErrResult[struct{}, ErrorCode]) { //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.sync //go:noescape -func wasmimport_DescriptorSync(self0 uint32, result *cm.ErrResult[struct{}, ErrorCode]) +func wasmimport_DescriptorSync(self0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) // SyncData represents the imported method "sync-data". // @@ -1137,7 +1212,7 @@ func wasmimport_DescriptorSync(self0 uint32, result *cm.ErrResult[struct{}, Erro // sync-data: func() -> result<_, error-code> // //go:nosplit -func (self Descriptor) SyncData() (result cm.ErrResult[struct{}, ErrorCode]) { +func (self Descriptor) SyncData() (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_DescriptorSyncData((uint32)(self0), &result) return @@ -1145,7 +1220,7 @@ func (self Descriptor) SyncData() (result cm.ErrResult[struct{}, ErrorCode]) { //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.sync-data //go:noescape -func wasmimport_DescriptorSyncData(self0 uint32, result *cm.ErrResult[struct{}, ErrorCode]) +func wasmimport_DescriptorSyncData(self0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) // UnlinkFileAt represents the imported method "unlink-file-at". // @@ -1157,7 +1232,7 @@ func wasmimport_DescriptorSyncData(self0 uint32, result *cm.ErrResult[struct{}, // unlink-file-at: func(path: string) -> result<_, error-code> // //go:nosplit -func (self Descriptor) UnlinkFileAt(path string) (result cm.ErrResult[struct{}, ErrorCode]) { +func (self Descriptor) UnlinkFileAt(path string) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) path0, path1 := cm.LowerString(path) wasmimport_DescriptorUnlinkFileAt((uint32)(self0), (*uint8)(path0), (uint32)(path1), &result) @@ -1166,7 +1241,7 @@ func (self Descriptor) UnlinkFileAt(path string) (result cm.ErrResult[struct{}, //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.unlink-file-at //go:noescape -func wasmimport_DescriptorUnlinkFileAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.ErrResult[struct{}, ErrorCode]) +func wasmimport_DescriptorUnlinkFileAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) // Write represents the imported method "write". // @@ -1183,7 +1258,7 @@ func wasmimport_DescriptorUnlinkFileAt(self0 uint32, path0 *uint8, path1 uint32, // write: func(buffer: list, offset: filesize) -> result // //go:nosplit -func (self Descriptor) Write(buffer cm.List[uint8], offset FileSize) (result cm.OKResult[FileSize, ErrorCode]) { +func (self Descriptor) Write(buffer cm.List[uint8], offset FileSize) (result cm.Result[uint64, FileSize, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) buffer0, buffer1 := cm.LowerList(buffer) offset0 := (uint64)(offset) @@ -1193,7 +1268,7 @@ func (self Descriptor) Write(buffer cm.List[uint8], offset FileSize) (result cm. //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.write //go:noescape -func wasmimport_DescriptorWrite(self0 uint32, buffer0 *uint8, buffer1 uint32, offset0 uint64, result *cm.OKResult[FileSize, ErrorCode]) +func wasmimport_DescriptorWrite(self0 uint32, buffer0 *uint8, buffer1 uint32, offset0 uint64, result *cm.Result[uint64, FileSize, ErrorCode]) // WriteViaStream represents the imported method "write-via-stream". // @@ -1207,7 +1282,7 @@ func wasmimport_DescriptorWrite(self0 uint32, buffer0 *uint8, buffer1 uint32, of // write-via-stream: func(offset: filesize) -> result // //go:nosplit -func (self Descriptor) WriteViaStream(offset FileSize) (result cm.OKResult[streams.OutputStream, ErrorCode]) { +func (self Descriptor) WriteViaStream(offset FileSize) (result cm.Result[streams.OutputStream, streams.OutputStream, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) offset0 := (uint64)(offset) wasmimport_DescriptorWriteViaStream((uint32)(self0), (uint64)(offset0), &result) @@ -1216,7 +1291,7 @@ func (self Descriptor) WriteViaStream(offset FileSize) (result cm.OKResult[strea //go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.write-via-stream //go:noescape -func wasmimport_DescriptorWriteViaStream(self0 uint32, offset0 uint64, result *cm.OKResult[streams.OutputStream, ErrorCode]) +func wasmimport_DescriptorWriteViaStream(self0 uint32, offset0 uint64, result *cm.Result[streams.OutputStream, streams.OutputStream, ErrorCode]) // DirectoryEntryStream represents the imported resource "wasi:filesystem/types@0.2.0#directory-entry-stream". // @@ -1247,7 +1322,7 @@ func wasmimport_DirectoryEntryStreamResourceDrop(self0 uint32) // read-directory-entry: func() -> result, error-code> // //go:nosplit -func (self DirectoryEntryStream) ReadDirectoryEntry() (result cm.OKResult[cm.Option[DirectoryEntry], ErrorCode]) { +func (self DirectoryEntryStream) ReadDirectoryEntry() (result cm.Result[OptionDirectoryEntryShape, cm.Option[DirectoryEntry], ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_DirectoryEntryStreamReadDirectoryEntry((uint32)(self0), &result) return @@ -1255,7 +1330,7 @@ func (self DirectoryEntryStream) ReadDirectoryEntry() (result cm.OKResult[cm.Opt //go:wasmimport wasi:filesystem/types@0.2.0 [method]directory-entry-stream.read-directory-entry //go:noescape -func wasmimport_DirectoryEntryStreamReadDirectoryEntry(self0 uint32, result *cm.OKResult[cm.Option[DirectoryEntry], ErrorCode]) +func wasmimport_DirectoryEntryStreamReadDirectoryEntry(self0 uint32, result *cm.Result[OptionDirectoryEntryShape, cm.Option[DirectoryEntry], ErrorCode]) // FilesystemErrorCode represents the imported function "filesystem-error-code". // diff --git a/src/internal/wasi/io/v0.2.0/streams/abi.go b/src/internal/wasi/io/v0.2.0/streams/abi.go new file mode 100644 index 0000000000..8d7592070c --- /dev/null +++ b/src/internal/wasi/io/v0.2.0/streams/abi.go @@ -0,0 +1,14 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +package streams + +import ( + "unsafe" +) + +// StreamErrorShape is used for storage in variant or result types. +type StreamErrorShape struct { + shape [unsafe.Sizeof(StreamError{})]byte +} diff --git a/src/internal/wasi/io/v0.2.0/streams/streams.wit.go b/src/internal/wasi/io/v0.2.0/streams/streams.wit.go index 8983d5ac0c..f3c1e3c355 100644 --- a/src/internal/wasi/io/v0.2.0/streams/streams.wit.go +++ b/src/internal/wasi/io/v0.2.0/streams/streams.wit.go @@ -53,7 +53,7 @@ func StreamErrorClosed() StreamError { // Closed returns true if [StreamError] represents the variant case "closed". func (self *StreamError) Closed() bool { - return cm.Tag(self) == 1 + return self.Tag() == 1 } // InputStream represents the imported resource "wasi:io/streams@0.2.0#input-stream". @@ -93,7 +93,7 @@ func wasmimport_InputStreamResourceDrop(self0 uint32) // blocking-read: func(len: u64) -> result, stream-error> // //go:nosplit -func (self InputStream) BlockingRead(len_ uint64) (result cm.OKResult[cm.List[uint8], StreamError]) { +func (self InputStream) BlockingRead(len_ uint64) (result cm.Result[cm.List[uint8], cm.List[uint8], StreamError]) { self0 := cm.Reinterpret[uint32](self) len0 := (uint64)(len_) wasmimport_InputStreamBlockingRead((uint32)(self0), (uint64)(len0), &result) @@ -102,7 +102,7 @@ func (self InputStream) BlockingRead(len_ uint64) (result cm.OKResult[cm.List[ui //go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.blocking-read //go:noescape -func wasmimport_InputStreamBlockingRead(self0 uint32, len0 uint64, result *cm.OKResult[cm.List[uint8], StreamError]) +func wasmimport_InputStreamBlockingRead(self0 uint32, len0 uint64, result *cm.Result[cm.List[uint8], cm.List[uint8], StreamError]) // BlockingSkip represents the imported method "blocking-skip". // @@ -112,7 +112,7 @@ func wasmimport_InputStreamBlockingRead(self0 uint32, len0 uint64, result *cm.OK // blocking-skip: func(len: u64) -> result // //go:nosplit -func (self InputStream) BlockingSkip(len_ uint64) (result cm.OKResult[uint64, StreamError]) { +func (self InputStream) BlockingSkip(len_ uint64) (result cm.Result[uint64, uint64, StreamError]) { self0 := cm.Reinterpret[uint32](self) len0 := (uint64)(len_) wasmimport_InputStreamBlockingSkip((uint32)(self0), (uint64)(len0), &result) @@ -121,7 +121,7 @@ func (self InputStream) BlockingSkip(len_ uint64) (result cm.OKResult[uint64, St //go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.blocking-skip //go:noescape -func wasmimport_InputStreamBlockingSkip(self0 uint32, len0 uint64, result *cm.OKResult[uint64, StreamError]) +func wasmimport_InputStreamBlockingSkip(self0 uint32, len0 uint64, result *cm.Result[uint64, uint64, StreamError]) // Read represents the imported method "read". // @@ -155,7 +155,7 @@ func wasmimport_InputStreamBlockingSkip(self0 uint32, len0 uint64, result *cm.OK // read: func(len: u64) -> result, stream-error> // //go:nosplit -func (self InputStream) Read(len_ uint64) (result cm.OKResult[cm.List[uint8], StreamError]) { +func (self InputStream) Read(len_ uint64) (result cm.Result[cm.List[uint8], cm.List[uint8], StreamError]) { self0 := cm.Reinterpret[uint32](self) len0 := (uint64)(len_) wasmimport_InputStreamRead((uint32)(self0), (uint64)(len0), &result) @@ -164,7 +164,7 @@ func (self InputStream) Read(len_ uint64) (result cm.OKResult[cm.List[uint8], St //go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.read //go:noescape -func wasmimport_InputStreamRead(self0 uint32, len0 uint64, result *cm.OKResult[cm.List[uint8], StreamError]) +func wasmimport_InputStreamRead(self0 uint32, len0 uint64, result *cm.Result[cm.List[uint8], cm.List[uint8], StreamError]) // Skip represents the imported method "skip". // @@ -176,7 +176,7 @@ func wasmimport_InputStreamRead(self0 uint32, len0 uint64, result *cm.OKResult[c // skip: func(len: u64) -> result // //go:nosplit -func (self InputStream) Skip(len_ uint64) (result cm.OKResult[uint64, StreamError]) { +func (self InputStream) Skip(len_ uint64) (result cm.Result[uint64, uint64, StreamError]) { self0 := cm.Reinterpret[uint32](self) len0 := (uint64)(len_) wasmimport_InputStreamSkip((uint32)(self0), (uint64)(len0), &result) @@ -185,7 +185,7 @@ func (self InputStream) Skip(len_ uint64) (result cm.OKResult[uint64, StreamErro //go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.skip //go:noescape -func wasmimport_InputStreamSkip(self0 uint32, len0 uint64, result *cm.OKResult[uint64, StreamError]) +func wasmimport_InputStreamSkip(self0 uint32, len0 uint64, result *cm.Result[uint64, uint64, StreamError]) // Subscribe represents the imported method "subscribe". // @@ -247,7 +247,7 @@ func wasmimport_OutputStreamResourceDrop(self0 uint32) // blocking-flush: func() -> result<_, stream-error> // //go:nosplit -func (self OutputStream) BlockingFlush() (result cm.ErrResult[struct{}, StreamError]) { +func (self OutputStream) BlockingFlush() (result cm.Result[StreamError, struct{}, StreamError]) { self0 := cm.Reinterpret[uint32](self) wasmimport_OutputStreamBlockingFlush((uint32)(self0), &result) return @@ -255,7 +255,7 @@ func (self OutputStream) BlockingFlush() (result cm.ErrResult[struct{}, StreamEr //go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-flush //go:noescape -func wasmimport_OutputStreamBlockingFlush(self0 uint32, result *cm.ErrResult[struct{}, StreamError]) +func wasmimport_OutputStreamBlockingFlush(self0 uint32, result *cm.Result[StreamError, struct{}, StreamError]) // BlockingSplice represents the imported method "blocking-splice". // @@ -268,7 +268,7 @@ func wasmimport_OutputStreamBlockingFlush(self0 uint32, result *cm.ErrResult[str // blocking-splice: func(src: borrow, len: u64) -> result // //go:nosplit -func (self OutputStream) BlockingSplice(src InputStream, len_ uint64) (result cm.OKResult[uint64, StreamError]) { +func (self OutputStream) BlockingSplice(src InputStream, len_ uint64) (result cm.Result[uint64, uint64, StreamError]) { self0 := cm.Reinterpret[uint32](self) src0 := cm.Reinterpret[uint32](src) len0 := (uint64)(len_) @@ -278,7 +278,7 @@ func (self OutputStream) BlockingSplice(src InputStream, len_ uint64) (result cm //go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-splice //go:noescape -func wasmimport_OutputStreamBlockingSplice(self0 uint32, src0 uint32, len0 uint64, result *cm.OKResult[uint64, StreamError]) +func wasmimport_OutputStreamBlockingSplice(self0 uint32, src0 uint32, len0 uint64, result *cm.Result[uint64, uint64, StreamError]) // BlockingWriteAndFlush represents the imported method "blocking-write-and-flush". // @@ -308,7 +308,7 @@ func wasmimport_OutputStreamBlockingSplice(self0 uint32, src0 uint32, len0 uint6 // blocking-write-and-flush: func(contents: list) -> result<_, stream-error> // //go:nosplit -func (self OutputStream) BlockingWriteAndFlush(contents cm.List[uint8]) (result cm.ErrResult[struct{}, StreamError]) { +func (self OutputStream) BlockingWriteAndFlush(contents cm.List[uint8]) (result cm.Result[StreamError, struct{}, StreamError]) { self0 := cm.Reinterpret[uint32](self) contents0, contents1 := cm.LowerList(contents) wasmimport_OutputStreamBlockingWriteAndFlush((uint32)(self0), (*uint8)(contents0), (uint32)(contents1), &result) @@ -317,7 +317,7 @@ func (self OutputStream) BlockingWriteAndFlush(contents cm.List[uint8]) (result //go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-write-and-flush //go:noescape -func wasmimport_OutputStreamBlockingWriteAndFlush(self0 uint32, contents0 *uint8, contents1 uint32, result *cm.ErrResult[struct{}, StreamError]) +func wasmimport_OutputStreamBlockingWriteAndFlush(self0 uint32, contents0 *uint8, contents1 uint32, result *cm.Result[StreamError, struct{}, StreamError]) // BlockingWriteZeroesAndFlush represents the imported method "blocking-write-zeroes-and-flush". // @@ -347,7 +347,7 @@ func wasmimport_OutputStreamBlockingWriteAndFlush(self0 uint32, contents0 *uint8 // blocking-write-zeroes-and-flush: func(len: u64) -> result<_, stream-error> // //go:nosplit -func (self OutputStream) BlockingWriteZeroesAndFlush(len_ uint64) (result cm.ErrResult[struct{}, StreamError]) { +func (self OutputStream) BlockingWriteZeroesAndFlush(len_ uint64) (result cm.Result[StreamError, struct{}, StreamError]) { self0 := cm.Reinterpret[uint32](self) len0 := (uint64)(len_) wasmimport_OutputStreamBlockingWriteZeroesAndFlush((uint32)(self0), (uint64)(len0), &result) @@ -356,7 +356,7 @@ func (self OutputStream) BlockingWriteZeroesAndFlush(len_ uint64) (result cm.Err //go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-write-zeroes-and-flush //go:noescape -func wasmimport_OutputStreamBlockingWriteZeroesAndFlush(self0 uint32, len0 uint64, result *cm.ErrResult[struct{}, StreamError]) +func wasmimport_OutputStreamBlockingWriteZeroesAndFlush(self0 uint32, len0 uint64, result *cm.Result[StreamError, struct{}, StreamError]) // CheckWrite represents the imported method "check-write". // @@ -373,7 +373,7 @@ func wasmimport_OutputStreamBlockingWriteZeroesAndFlush(self0 uint32, len0 uint6 // check-write: func() -> result // //go:nosplit -func (self OutputStream) CheckWrite() (result cm.OKResult[uint64, StreamError]) { +func (self OutputStream) CheckWrite() (result cm.Result[uint64, uint64, StreamError]) { self0 := cm.Reinterpret[uint32](self) wasmimport_OutputStreamCheckWrite((uint32)(self0), &result) return @@ -381,7 +381,7 @@ func (self OutputStream) CheckWrite() (result cm.OKResult[uint64, StreamError]) //go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.check-write //go:noescape -func wasmimport_OutputStreamCheckWrite(self0 uint32, result *cm.OKResult[uint64, StreamError]) +func wasmimport_OutputStreamCheckWrite(self0 uint32, result *cm.Result[uint64, uint64, StreamError]) // Flush represents the imported method "flush". // @@ -399,7 +399,7 @@ func wasmimport_OutputStreamCheckWrite(self0 uint32, result *cm.OKResult[uint64, // flush: func() -> result<_, stream-error> // //go:nosplit -func (self OutputStream) Flush() (result cm.ErrResult[struct{}, StreamError]) { +func (self OutputStream) Flush() (result cm.Result[StreamError, struct{}, StreamError]) { self0 := cm.Reinterpret[uint32](self) wasmimport_OutputStreamFlush((uint32)(self0), &result) return @@ -407,7 +407,7 @@ func (self OutputStream) Flush() (result cm.ErrResult[struct{}, StreamError]) { //go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.flush //go:noescape -func wasmimport_OutputStreamFlush(self0 uint32, result *cm.ErrResult[struct{}, StreamError]) +func wasmimport_OutputStreamFlush(self0 uint32, result *cm.Result[StreamError, struct{}, StreamError]) // Splice represents the imported method "splice". // @@ -428,7 +428,7 @@ func wasmimport_OutputStreamFlush(self0 uint32, result *cm.ErrResult[struct{}, S // splice: func(src: borrow, len: u64) -> result // //go:nosplit -func (self OutputStream) Splice(src InputStream, len_ uint64) (result cm.OKResult[uint64, StreamError]) { +func (self OutputStream) Splice(src InputStream, len_ uint64) (result cm.Result[uint64, uint64, StreamError]) { self0 := cm.Reinterpret[uint32](self) src0 := cm.Reinterpret[uint32](src) len0 := (uint64)(len_) @@ -438,7 +438,7 @@ func (self OutputStream) Splice(src InputStream, len_ uint64) (result cm.OKResul //go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.splice //go:noescape -func wasmimport_OutputStreamSplice(self0 uint32, src0 uint32, len0 uint64, result *cm.OKResult[uint64, StreamError]) +func wasmimport_OutputStreamSplice(self0 uint32, src0 uint32, len0 uint64, result *cm.Result[uint64, uint64, StreamError]) // Subscribe represents the imported method "subscribe". // @@ -486,7 +486,7 @@ func wasmimport_OutputStreamSubscribe(self0 uint32) (result0 uint32) // write: func(contents: list) -> result<_, stream-error> // //go:nosplit -func (self OutputStream) Write(contents cm.List[uint8]) (result cm.ErrResult[struct{}, StreamError]) { +func (self OutputStream) Write(contents cm.List[uint8]) (result cm.Result[StreamError, struct{}, StreamError]) { self0 := cm.Reinterpret[uint32](self) contents0, contents1 := cm.LowerList(contents) wasmimport_OutputStreamWrite((uint32)(self0), (*uint8)(contents0), (uint32)(contents1), &result) @@ -495,7 +495,7 @@ func (self OutputStream) Write(contents cm.List[uint8]) (result cm.ErrResult[str //go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.write //go:noescape -func wasmimport_OutputStreamWrite(self0 uint32, contents0 *uint8, contents1 uint32, result *cm.ErrResult[struct{}, StreamError]) +func wasmimport_OutputStreamWrite(self0 uint32, contents0 *uint8, contents1 uint32, result *cm.Result[StreamError, struct{}, StreamError]) // WriteZeroes represents the imported method "write-zeroes". // @@ -509,7 +509,7 @@ func wasmimport_OutputStreamWrite(self0 uint32, contents0 *uint8, contents1 uint // write-zeroes: func(len: u64) -> result<_, stream-error> // //go:nosplit -func (self OutputStream) WriteZeroes(len_ uint64) (result cm.ErrResult[struct{}, StreamError]) { +func (self OutputStream) WriteZeroes(len_ uint64) (result cm.Result[StreamError, struct{}, StreamError]) { self0 := cm.Reinterpret[uint32](self) len0 := (uint64)(len_) wasmimport_OutputStreamWriteZeroes((uint32)(self0), (uint64)(len0), &result) @@ -518,4 +518,4 @@ func (self OutputStream) WriteZeroes(len_ uint64) (result cm.ErrResult[struct{}, //go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.write-zeroes //go:noescape -func wasmimport_OutputStreamWriteZeroes(self0 uint32, len0 uint64, result *cm.ErrResult[struct{}, StreamError]) +func wasmimport_OutputStreamWriteZeroes(self0 uint32, len0 uint64, result *cm.Result[StreamError, struct{}, StreamError]) diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go new file mode 100644 index 0000000000..065c935e16 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go @@ -0,0 +1,16 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +package ipnamelookup + +import ( + "github.com/ydnar/wasm-tools-go/cm" + "internal/wasi/sockets/v0.2.0/network" + "unsafe" +) + +// OptionIPAddressShape is used for storage in variant or result types. +type OptionIPAddressShape struct { + shape [unsafe.Sizeof(cm.Option[network.IPAddress]{})]byte +} diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go index fc81b36ac8..34ca3a6a0b 100644 --- a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go @@ -53,7 +53,7 @@ func wasmimport_ResolveAddressStreamResourceDrop(self0 uint32) // resolve-next-address: func() -> result, error-code> // //go:nosplit -func (self ResolveAddressStream) ResolveNextAddress() (result cm.OKResult[cm.Option[network.IPAddress], network.ErrorCode]) { +func (self ResolveAddressStream) ResolveNextAddress() (result cm.Result[OptionIPAddressShape, cm.Option[network.IPAddress], network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_ResolveAddressStreamResolveNextAddress((uint32)(self0), &result) return @@ -61,7 +61,7 @@ func (self ResolveAddressStream) ResolveNextAddress() (result cm.OKResult[cm.Opt //go:wasmimport wasi:sockets/ip-name-lookup@0.2.0 [method]resolve-address-stream.resolve-next-address //go:noescape -func wasmimport_ResolveAddressStreamResolveNextAddress(self0 uint32, result *cm.OKResult[cm.Option[network.IPAddress], network.ErrorCode]) +func wasmimport_ResolveAddressStreamResolveNextAddress(self0 uint32, result *cm.Result[OptionIPAddressShape, cm.Option[network.IPAddress], network.ErrorCode]) // Subscribe represents the imported method "subscribe". // @@ -111,7 +111,7 @@ func wasmimport_ResolveAddressStreamSubscribe(self0 uint32) (result0 uint32) // error-code> // //go:nosplit -func ResolveAddresses(network_ network.Network, name string) (result cm.OKResult[ResolveAddressStream, network.ErrorCode]) { +func ResolveAddresses(network_ network.Network, name string) (result cm.Result[ResolveAddressStream, ResolveAddressStream, network.ErrorCode]) { network0 := cm.Reinterpret[uint32](network_) name0, name1 := cm.LowerString(name) wasmimport_ResolveAddresses((uint32)(network0), (*uint8)(name0), (uint32)(name1), &result) @@ -120,4 +120,4 @@ func ResolveAddresses(network_ network.Network, name string) (result cm.OKResult //go:wasmimport wasi:sockets/ip-name-lookup@0.2.0 resolve-addresses //go:noescape -func wasmimport_ResolveAddresses(network0 uint32, name0 *uint8, name1 uint32, result *cm.OKResult[ResolveAddressStream, network.ErrorCode]) +func wasmimport_ResolveAddresses(network0 uint32, name0 *uint8, name1 uint32, result *cm.Result[ResolveAddressStream, ResolveAddressStream, network.ErrorCode]) diff --git a/src/internal/wasi/sockets/v0.2.0/network/abi.go b/src/internal/wasi/sockets/v0.2.0/network/abi.go new file mode 100644 index 0000000000..ef9fc1e8dc --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/network/abi.go @@ -0,0 +1,14 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +//go:build !wasip1 + +package network + +import ( + "unsafe" +) + +// IPv6SocketAddressShape is used for storage in variant or result types. +type IPv6SocketAddressShape struct { + shape [unsafe.Sizeof(IPv6SocketAddress{})]byte +} diff --git a/src/internal/wasi/sockets/v0.2.0/network/network.wit.go b/src/internal/wasi/sockets/v0.2.0/network/network.wit.go index 4cca93aca0..7108f6d97b 100644 --- a/src/internal/wasi/sockets/v0.2.0/network/network.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/network/network.wit.go @@ -159,6 +159,35 @@ const ( ErrorCodePermanentResolverFailure ) +var stringsErrorCode = [21]string{ + "unknown", + "access-denied", + "not-supported", + "invalid-argument", + "out-of-memory", + "timeout", + "concurrency-conflict", + "not-in-progress", + "would-block", + "invalid-state", + "new-socket-limit", + "address-not-bindable", + "address-in-use", + "remote-unreachable", + "connection-refused", + "connection-reset", + "connection-aborted", + "datagram-too-large", + "name-unresolvable", + "temporary-resolver-failure", + "permanent-resolver-failure", +} + +// String implements [fmt.Stringer], returning the enum case name of e. +func (e ErrorCode) String() string { + return stringsErrorCode[e] +} + // IPAddressFamily represents the enum "wasi:sockets/network@0.2.0#ip-address-family". // // enum ip-address-family { @@ -175,6 +204,16 @@ const ( IPAddressFamilyIPv6 ) +var stringsIPAddressFamily = [2]string{ + "ipv4", + "ipv6", +} + +// String implements [fmt.Stringer], returning the enum case name of e. +func (e IPAddressFamily) String() string { + return stringsIPAddressFamily[e] +} + // IPv4Address represents the tuple "wasi:sockets/network@0.2.0#ipv4-address". // // type ipv4-address = tuple @@ -255,7 +294,7 @@ type IPv6SocketAddress struct { // ipv4(ipv4-socket-address), // ipv6(ipv6-socket-address), // } -type IPSocketAddress cm.Variant[uint8, IPv6SocketAddress, IPv6SocketAddress] +type IPSocketAddress cm.Variant[uint8, IPv6SocketAddressShape, IPv6SocketAddress] // IPSocketAddressIPv4 returns a [IPSocketAddress] of case "ipv4". func IPSocketAddressIPv4(data IPv4SocketAddress) IPSocketAddress { diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go index 906128abea..06de81f964 100644 --- a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go @@ -43,7 +43,7 @@ import ( // error-code> // //go:nosplit -func CreateTCPSocket(addressFamily network.IPAddressFamily) (result cm.OKResult[tcp.TCPSocket, network.ErrorCode]) { +func CreateTCPSocket(addressFamily network.IPAddressFamily) (result cm.Result[tcp.TCPSocket, tcp.TCPSocket, network.ErrorCode]) { addressFamily0 := (uint32)(addressFamily) wasmimport_CreateTCPSocket((uint32)(addressFamily0), &result) return @@ -51,4 +51,4 @@ func CreateTCPSocket(addressFamily network.IPAddressFamily) (result cm.OKResult[ //go:wasmimport wasi:sockets/tcp-create-socket@0.2.0 create-tcp-socket //go:noescape -func wasmimport_CreateTCPSocket(addressFamily0 uint32, result *cm.OKResult[tcp.TCPSocket, network.ErrorCode]) +func wasmimport_CreateTCPSocket(addressFamily0 uint32, result *cm.Result[tcp.TCPSocket, tcp.TCPSocket, network.ErrorCode]) diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/abi.go b/src/internal/wasi/sockets/v0.2.0/tcp/abi.go index fecab79fdf..39ccd2f62c 100644 --- a/src/internal/wasi/sockets/v0.2.0/tcp/abi.go +++ b/src/internal/wasi/sockets/v0.2.0/tcp/abi.go @@ -6,9 +6,26 @@ package tcp import ( "github.com/ydnar/wasm-tools-go/cm" + "internal/wasi/io/v0.2.0/streams" "internal/wasi/sockets/v0.2.0/network" + "unsafe" ) +// TupleTCPSocketInputStreamOutputStreamShape is used for storage in variant or result types. +type TupleTCPSocketInputStreamOutputStreamShape struct { + shape [unsafe.Sizeof(cm.Tuple3[TCPSocket, streams.InputStream, streams.OutputStream]{})]byte +} + +// TupleInputStreamOutputStreamShape is used for storage in variant or result types. +type TupleInputStreamOutputStreamShape struct { + shape [unsafe.Sizeof(cm.Tuple[streams.InputStream, streams.OutputStream]{})]byte +} + +// IPSocketAddressShape is used for storage in variant or result types. +type IPSocketAddressShape struct { + shape [unsafe.Sizeof(network.IPSocketAddress{})]byte +} + func lower_IPv4Address(v network.IPv4Address) (f0 uint32, f1 uint32, f2 uint32, f3 uint32) { f0 = (uint32)(v[0]) f1 = (uint32)(v[1]) @@ -44,7 +61,7 @@ func lower_IPv6SocketAddress(v network.IPv6SocketAddress) (f0 uint32, f1 uint32, } func lower_IPSocketAddress(v network.IPSocketAddress) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32, f8 uint32, f9 uint32, f10 uint32, f11 uint32) { - f0 = (uint32)(cm.Tag(&v)) + f0 = (uint32)(v.Tag()) switch f0 { case 0: // ipv4 v1, v2, v3, v4, v5 := lower_IPv4SocketAddress(*v.IPv4()) diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go index f390849cba..c306afef94 100644 --- a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go @@ -33,6 +33,17 @@ const ( ShutdownTypeBoth ) +var stringsShutdownType = [3]string{ + "receive", + "send", + "both", +} + +// String implements [fmt.Stringer], returning the enum case name of e. +func (e ShutdownType) String() string { + return stringsShutdownType[e] +} + // TCPSocket represents the imported resource "wasi:sockets/tcp@0.2.0#tcp-socket". // // A TCP socket resource. @@ -111,7 +122,7 @@ func wasmimport_TCPSocketResourceDrop(self0 uint32) // accept: func() -> result, error-code> // //go:nosplit -func (self TCPSocket) Accept() (result cm.OKResult[cm.Tuple3[TCPSocket, streams.InputStream, streams.OutputStream], network.ErrorCode]) { +func (self TCPSocket) Accept() (result cm.Result[TupleTCPSocketInputStreamOutputStreamShape, cm.Tuple3[TCPSocket, streams.InputStream, streams.OutputStream], network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketAccept((uint32)(self0), &result) return @@ -119,7 +130,7 @@ func (self TCPSocket) Accept() (result cm.OKResult[cm.Tuple3[TCPSocket, streams. //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.accept //go:noescape -func wasmimport_TCPSocketAccept(self0 uint32, result *cm.OKResult[cm.Tuple3[TCPSocket, streams.InputStream, streams.OutputStream], network.ErrorCode]) +func wasmimport_TCPSocketAccept(self0 uint32, result *cm.Result[TupleTCPSocketInputStreamOutputStreamShape, cm.Tuple3[TCPSocket, streams.InputStream, streams.OutputStream], network.ErrorCode]) // AddressFamily represents the imported method "address-family". // @@ -146,7 +157,7 @@ func wasmimport_TCPSocketAddressFamily(self0 uint32) (result0 uint32) // finish-bind: func() -> result<_, error-code> // //go:nosplit -func (self TCPSocket) FinishBind() (result cm.ErrResult[struct{}, network.ErrorCode]) { +func (self TCPSocket) FinishBind() (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketFinishBind((uint32)(self0), &result) return @@ -154,14 +165,14 @@ func (self TCPSocket) FinishBind() (result cm.ErrResult[struct{}, network.ErrorC //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.finish-bind //go:noescape -func wasmimport_TCPSocketFinishBind(self0 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) +func wasmimport_TCPSocketFinishBind(self0 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) // FinishConnect represents the imported method "finish-connect". // // finish-connect: func() -> result, error-code> // //go:nosplit -func (self TCPSocket) FinishConnect() (result cm.OKResult[cm.Tuple[streams.InputStream, streams.OutputStream], network.ErrorCode]) { +func (self TCPSocket) FinishConnect() (result cm.Result[TupleInputStreamOutputStreamShape, cm.Tuple[streams.InputStream, streams.OutputStream], network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketFinishConnect((uint32)(self0), &result) return @@ -169,14 +180,14 @@ func (self TCPSocket) FinishConnect() (result cm.OKResult[cm.Tuple[streams.Input //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.finish-connect //go:noescape -func wasmimport_TCPSocketFinishConnect(self0 uint32, result *cm.OKResult[cm.Tuple[streams.InputStream, streams.OutputStream], network.ErrorCode]) +func wasmimport_TCPSocketFinishConnect(self0 uint32, result *cm.Result[TupleInputStreamOutputStreamShape, cm.Tuple[streams.InputStream, streams.OutputStream], network.ErrorCode]) // FinishListen represents the imported method "finish-listen". // // finish-listen: func() -> result<_, error-code> // //go:nosplit -func (self TCPSocket) FinishListen() (result cm.ErrResult[struct{}, network.ErrorCode]) { +func (self TCPSocket) FinishListen() (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketFinishListen((uint32)(self0), &result) return @@ -184,7 +195,7 @@ func (self TCPSocket) FinishListen() (result cm.ErrResult[struct{}, network.Erro //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.finish-listen //go:noescape -func wasmimport_TCPSocketFinishListen(self0 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) +func wasmimport_TCPSocketFinishListen(self0 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) // HopLimit represents the imported method "hop-limit". // @@ -198,7 +209,7 @@ func wasmimport_TCPSocketFinishListen(self0 uint32, result *cm.ErrResult[struct{ // hop-limit: func() -> result // //go:nosplit -func (self TCPSocket) HopLimit() (result cm.OKResult[uint8, network.ErrorCode]) { +func (self TCPSocket) HopLimit() (result cm.Result[uint8, uint8, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketHopLimit((uint32)(self0), &result) return @@ -206,7 +217,7 @@ func (self TCPSocket) HopLimit() (result cm.OKResult[uint8, network.ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.hop-limit //go:noescape -func wasmimport_TCPSocketHopLimit(self0 uint32, result *cm.OKResult[uint8, network.ErrorCode]) +func wasmimport_TCPSocketHopLimit(self0 uint32, result *cm.Result[uint8, uint8, network.ErrorCode]) // IsListening represents the imported method "is-listening". // @@ -246,7 +257,7 @@ func wasmimport_TCPSocketIsListening(self0 uint32) (result0 uint32) // keep-alive-count: func() -> result // //go:nosplit -func (self TCPSocket) KeepAliveCount() (result cm.OKResult[uint32, network.ErrorCode]) { +func (self TCPSocket) KeepAliveCount() (result cm.Result[uint32, uint32, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketKeepAliveCount((uint32)(self0), &result) return @@ -254,7 +265,7 @@ func (self TCPSocket) KeepAliveCount() (result cm.OKResult[uint32, network.Error //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-count //go:noescape -func wasmimport_TCPSocketKeepAliveCount(self0 uint32, result *cm.OKResult[uint32, network.ErrorCode]) +func wasmimport_TCPSocketKeepAliveCount(self0 uint32, result *cm.Result[uint32, uint32, network.ErrorCode]) // KeepAliveEnabled represents the imported method "keep-alive-enabled". // @@ -272,7 +283,7 @@ func wasmimport_TCPSocketKeepAliveCount(self0 uint32, result *cm.OKResult[uint32 // keep-alive-enabled: func() -> result // //go:nosplit -func (self TCPSocket) KeepAliveEnabled() (result cm.OKResult[bool, network.ErrorCode]) { +func (self TCPSocket) KeepAliveEnabled() (result cm.Result[bool, bool, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketKeepAliveEnabled((uint32)(self0), &result) return @@ -280,7 +291,7 @@ func (self TCPSocket) KeepAliveEnabled() (result cm.OKResult[bool, network.Error //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-enabled //go:noescape -func wasmimport_TCPSocketKeepAliveEnabled(self0 uint32, result *cm.OKResult[bool, network.ErrorCode]) +func wasmimport_TCPSocketKeepAliveEnabled(self0 uint32, result *cm.Result[bool, bool, network.ErrorCode]) // KeepAliveIdleTime represents the imported method "keep-alive-idle-time". // @@ -301,7 +312,7 @@ func wasmimport_TCPSocketKeepAliveEnabled(self0 uint32, result *cm.OKResult[bool // keep-alive-idle-time: func() -> result // //go:nosplit -func (self TCPSocket) KeepAliveIdleTime() (result cm.OKResult[monotonicclock.Duration, network.ErrorCode]) { +func (self TCPSocket) KeepAliveIdleTime() (result cm.Result[uint64, monotonicclock.Duration, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketKeepAliveIdleTime((uint32)(self0), &result) return @@ -309,7 +320,7 @@ func (self TCPSocket) KeepAliveIdleTime() (result cm.OKResult[monotonicclock.Dur //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-idle-time //go:noescape -func wasmimport_TCPSocketKeepAliveIdleTime(self0 uint32, result *cm.OKResult[monotonicclock.Duration, network.ErrorCode]) +func wasmimport_TCPSocketKeepAliveIdleTime(self0 uint32, result *cm.Result[uint64, monotonicclock.Duration, network.ErrorCode]) // KeepAliveInterval represents the imported method "keep-alive-interval". // @@ -329,7 +340,7 @@ func wasmimport_TCPSocketKeepAliveIdleTime(self0 uint32, result *cm.OKResult[mon // keep-alive-interval: func() -> result // //go:nosplit -func (self TCPSocket) KeepAliveInterval() (result cm.OKResult[monotonicclock.Duration, network.ErrorCode]) { +func (self TCPSocket) KeepAliveInterval() (result cm.Result[uint64, monotonicclock.Duration, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketKeepAliveInterval((uint32)(self0), &result) return @@ -337,7 +348,7 @@ func (self TCPSocket) KeepAliveInterval() (result cm.OKResult[monotonicclock.Dur //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-interval //go:noescape -func wasmimport_TCPSocketKeepAliveInterval(self0 uint32, result *cm.OKResult[monotonicclock.Duration, network.ErrorCode]) +func wasmimport_TCPSocketKeepAliveInterval(self0 uint32, result *cm.Result[uint64, monotonicclock.Duration, network.ErrorCode]) // LocalAddress represents the imported method "local-address". // @@ -362,7 +373,7 @@ func wasmimport_TCPSocketKeepAliveInterval(self0 uint32, result *cm.OKResult[mon // local-address: func() -> result // //go:nosplit -func (self TCPSocket) LocalAddress() (result cm.OKResult[network.IPSocketAddress, network.ErrorCode]) { +func (self TCPSocket) LocalAddress() (result cm.Result[IPSocketAddressShape, network.IPSocketAddress, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketLocalAddress((uint32)(self0), &result) return @@ -370,7 +381,7 @@ func (self TCPSocket) LocalAddress() (result cm.OKResult[network.IPSocketAddress //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.local-address //go:noescape -func wasmimport_TCPSocketLocalAddress(self0 uint32, result *cm.OKResult[network.IPSocketAddress, network.ErrorCode]) +func wasmimport_TCPSocketLocalAddress(self0 uint32, result *cm.Result[IPSocketAddressShape, network.IPSocketAddress, network.ErrorCode]) // ReceiveBufferSize represents the imported method "receive-buffer-size". // @@ -390,7 +401,7 @@ func wasmimport_TCPSocketLocalAddress(self0 uint32, result *cm.OKResult[network. // receive-buffer-size: func() -> result // //go:nosplit -func (self TCPSocket) ReceiveBufferSize() (result cm.OKResult[uint64, network.ErrorCode]) { +func (self TCPSocket) ReceiveBufferSize() (result cm.Result[uint64, uint64, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketReceiveBufferSize((uint32)(self0), &result) return @@ -398,7 +409,7 @@ func (self TCPSocket) ReceiveBufferSize() (result cm.OKResult[uint64, network.Er //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.receive-buffer-size //go:noescape -func wasmimport_TCPSocketReceiveBufferSize(self0 uint32, result *cm.OKResult[uint64, network.ErrorCode]) +func wasmimport_TCPSocketReceiveBufferSize(self0 uint32, result *cm.Result[uint64, uint64, network.ErrorCode]) // RemoteAddress represents the imported method "remote-address". // @@ -416,7 +427,7 @@ func wasmimport_TCPSocketReceiveBufferSize(self0 uint32, result *cm.OKResult[uin // remote-address: func() -> result // //go:nosplit -func (self TCPSocket) RemoteAddress() (result cm.OKResult[network.IPSocketAddress, network.ErrorCode]) { +func (self TCPSocket) RemoteAddress() (result cm.Result[IPSocketAddressShape, network.IPSocketAddress, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketRemoteAddress((uint32)(self0), &result) return @@ -424,14 +435,14 @@ func (self TCPSocket) RemoteAddress() (result cm.OKResult[network.IPSocketAddres //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.remote-address //go:noescape -func wasmimport_TCPSocketRemoteAddress(self0 uint32, result *cm.OKResult[network.IPSocketAddress, network.ErrorCode]) +func wasmimport_TCPSocketRemoteAddress(self0 uint32, result *cm.Result[IPSocketAddressShape, network.IPSocketAddress, network.ErrorCode]) // SendBufferSize represents the imported method "send-buffer-size". // // send-buffer-size: func() -> result // //go:nosplit -func (self TCPSocket) SendBufferSize() (result cm.OKResult[uint64, network.ErrorCode]) { +func (self TCPSocket) SendBufferSize() (result cm.Result[uint64, uint64, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketSendBufferSize((uint32)(self0), &result) return @@ -439,14 +450,14 @@ func (self TCPSocket) SendBufferSize() (result cm.OKResult[uint64, network.Error //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.send-buffer-size //go:noescape -func wasmimport_TCPSocketSendBufferSize(self0 uint32, result *cm.OKResult[uint64, network.ErrorCode]) +func wasmimport_TCPSocketSendBufferSize(self0 uint32, result *cm.Result[uint64, uint64, network.ErrorCode]) // SetHopLimit represents the imported method "set-hop-limit". // // set-hop-limit: func(value: u8) -> result<_, error-code> // //go:nosplit -func (self TCPSocket) SetHopLimit(value uint8) (result cm.ErrResult[struct{}, network.ErrorCode]) { +func (self TCPSocket) SetHopLimit(value uint8) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint32)(value) wasmimport_TCPSocketSetHopLimit((uint32)(self0), (uint32)(value0), &result) @@ -455,14 +466,14 @@ func (self TCPSocket) SetHopLimit(value uint8) (result cm.ErrResult[struct{}, ne //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-hop-limit //go:noescape -func wasmimport_TCPSocketSetHopLimit(self0 uint32, value0 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) +func wasmimport_TCPSocketSetHopLimit(self0 uint32, value0 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) // SetKeepAliveCount represents the imported method "set-keep-alive-count". // // set-keep-alive-count: func(value: u32) -> result<_, error-code> // //go:nosplit -func (self TCPSocket) SetKeepAliveCount(value uint32) (result cm.ErrResult[struct{}, network.ErrorCode]) { +func (self TCPSocket) SetKeepAliveCount(value uint32) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint32)(value) wasmimport_TCPSocketSetKeepAliveCount((uint32)(self0), (uint32)(value0), &result) @@ -471,14 +482,14 @@ func (self TCPSocket) SetKeepAliveCount(value uint32) (result cm.ErrResult[struc //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-count //go:noescape -func wasmimport_TCPSocketSetKeepAliveCount(self0 uint32, value0 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) +func wasmimport_TCPSocketSetKeepAliveCount(self0 uint32, value0 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) // SetKeepAliveEnabled represents the imported method "set-keep-alive-enabled". // // set-keep-alive-enabled: func(value: bool) -> result<_, error-code> // //go:nosplit -func (self TCPSocket) SetKeepAliveEnabled(value bool) (result cm.ErrResult[struct{}, network.ErrorCode]) { +func (self TCPSocket) SetKeepAliveEnabled(value bool) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := cm.BoolToU32(value) wasmimport_TCPSocketSetKeepAliveEnabled((uint32)(self0), (uint32)(value0), &result) @@ -487,14 +498,14 @@ func (self TCPSocket) SetKeepAliveEnabled(value bool) (result cm.ErrResult[struc //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-enabled //go:noescape -func wasmimport_TCPSocketSetKeepAliveEnabled(self0 uint32, value0 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) +func wasmimport_TCPSocketSetKeepAliveEnabled(self0 uint32, value0 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) // SetKeepAliveIdleTime represents the imported method "set-keep-alive-idle-time". // // set-keep-alive-idle-time: func(value: duration) -> result<_, error-code> // //go:nosplit -func (self TCPSocket) SetKeepAliveIdleTime(value monotonicclock.Duration) (result cm.ErrResult[struct{}, network.ErrorCode]) { +func (self TCPSocket) SetKeepAliveIdleTime(value monotonicclock.Duration) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint64)(value) wasmimport_TCPSocketSetKeepAliveIdleTime((uint32)(self0), (uint64)(value0), &result) @@ -503,14 +514,14 @@ func (self TCPSocket) SetKeepAliveIdleTime(value monotonicclock.Duration) (resul //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-idle-time //go:noescape -func wasmimport_TCPSocketSetKeepAliveIdleTime(self0 uint32, value0 uint64, result *cm.ErrResult[struct{}, network.ErrorCode]) +func wasmimport_TCPSocketSetKeepAliveIdleTime(self0 uint32, value0 uint64, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) // SetKeepAliveInterval represents the imported method "set-keep-alive-interval". // // set-keep-alive-interval: func(value: duration) -> result<_, error-code> // //go:nosplit -func (self TCPSocket) SetKeepAliveInterval(value monotonicclock.Duration) (result cm.ErrResult[struct{}, network.ErrorCode]) { +func (self TCPSocket) SetKeepAliveInterval(value monotonicclock.Duration) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint64)(value) wasmimport_TCPSocketSetKeepAliveInterval((uint32)(self0), (uint64)(value0), &result) @@ -519,7 +530,7 @@ func (self TCPSocket) SetKeepAliveInterval(value monotonicclock.Duration) (resul //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-interval //go:noescape -func wasmimport_TCPSocketSetKeepAliveInterval(self0 uint32, value0 uint64, result *cm.ErrResult[struct{}, network.ErrorCode]) +func wasmimport_TCPSocketSetKeepAliveInterval(self0 uint32, value0 uint64, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) // SetListenBacklogSize represents the imported method "set-listen-backlog-size". // @@ -539,7 +550,7 @@ func wasmimport_TCPSocketSetKeepAliveInterval(self0 uint32, value0 uint64, resul // set-listen-backlog-size: func(value: u64) -> result<_, error-code> // //go:nosplit -func (self TCPSocket) SetListenBacklogSize(value uint64) (result cm.ErrResult[struct{}, network.ErrorCode]) { +func (self TCPSocket) SetListenBacklogSize(value uint64) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint64)(value) wasmimport_TCPSocketSetListenBacklogSize((uint32)(self0), (uint64)(value0), &result) @@ -548,14 +559,14 @@ func (self TCPSocket) SetListenBacklogSize(value uint64) (result cm.ErrResult[st //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-listen-backlog-size //go:noescape -func wasmimport_TCPSocketSetListenBacklogSize(self0 uint32, value0 uint64, result *cm.ErrResult[struct{}, network.ErrorCode]) +func wasmimport_TCPSocketSetListenBacklogSize(self0 uint32, value0 uint64, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) // SetReceiveBufferSize represents the imported method "set-receive-buffer-size". // // set-receive-buffer-size: func(value: u64) -> result<_, error-code> // //go:nosplit -func (self TCPSocket) SetReceiveBufferSize(value uint64) (result cm.ErrResult[struct{}, network.ErrorCode]) { +func (self TCPSocket) SetReceiveBufferSize(value uint64) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint64)(value) wasmimport_TCPSocketSetReceiveBufferSize((uint32)(self0), (uint64)(value0), &result) @@ -564,14 +575,14 @@ func (self TCPSocket) SetReceiveBufferSize(value uint64) (result cm.ErrResult[st //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-receive-buffer-size //go:noescape -func wasmimport_TCPSocketSetReceiveBufferSize(self0 uint32, value0 uint64, result *cm.ErrResult[struct{}, network.ErrorCode]) +func wasmimport_TCPSocketSetReceiveBufferSize(self0 uint32, value0 uint64, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) // SetSendBufferSize represents the imported method "set-send-buffer-size". // // set-send-buffer-size: func(value: u64) -> result<_, error-code> // //go:nosplit -func (self TCPSocket) SetSendBufferSize(value uint64) (result cm.ErrResult[struct{}, network.ErrorCode]) { +func (self TCPSocket) SetSendBufferSize(value uint64) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint64)(value) wasmimport_TCPSocketSetSendBufferSize((uint32)(self0), (uint64)(value0), &result) @@ -580,7 +591,7 @@ func (self TCPSocket) SetSendBufferSize(value uint64) (result cm.ErrResult[struc //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-send-buffer-size //go:noescape -func wasmimport_TCPSocketSetSendBufferSize(self0 uint32, value0 uint64, result *cm.ErrResult[struct{}, network.ErrorCode]) +func wasmimport_TCPSocketSetSendBufferSize(self0 uint32, value0 uint64, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) // Shutdown represents the imported method "shutdown". // @@ -611,7 +622,7 @@ func wasmimport_TCPSocketSetSendBufferSize(self0 uint32, value0 uint64, result * // shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code> // //go:nosplit -func (self TCPSocket) Shutdown(shutdownType ShutdownType) (result cm.ErrResult[struct{}, network.ErrorCode]) { +func (self TCPSocket) Shutdown(shutdownType ShutdownType) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) shutdownType0 := (uint32)(shutdownType) wasmimport_TCPSocketShutdown((uint32)(self0), (uint32)(shutdownType0), &result) @@ -620,7 +631,7 @@ func (self TCPSocket) Shutdown(shutdownType ShutdownType) (result cm.ErrResult[s //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.shutdown //go:noescape -func wasmimport_TCPSocketShutdown(self0 uint32, shutdownType0 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) +func wasmimport_TCPSocketShutdown(self0 uint32, shutdownType0 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) // StartBind represents the imported method "start-bind". // @@ -676,7 +687,7 @@ func wasmimport_TCPSocketShutdown(self0 uint32, shutdownType0 uint32, result *cm // result<_, error-code> // //go:nosplit -func (self TCPSocket) StartBind(network_ network.Network, localAddress network.IPSocketAddress) (result cm.ErrResult[struct{}, network.ErrorCode]) { +func (self TCPSocket) StartBind(network_ network.Network, localAddress network.IPSocketAddress) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) network0 := cm.Reinterpret[uint32](network_) localAddress0, localAddress1, localAddress2, localAddress3, localAddress4, localAddress5, localAddress6, localAddress7, localAddress8, localAddress9, localAddress10, localAddress11 := lower_IPSocketAddress(localAddress) @@ -686,7 +697,7 @@ func (self TCPSocket) StartBind(network_ network.Network, localAddress network.I //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.start-bind //go:noescape -func wasmimport_TCPSocketStartBind(self0 uint32, network0 uint32, localAddress0 uint32, localAddress1 uint32, localAddress2 uint32, localAddress3 uint32, localAddress4 uint32, localAddress5 uint32, localAddress6 uint32, localAddress7 uint32, localAddress8 uint32, localAddress9 uint32, localAddress10 uint32, localAddress11 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) +func wasmimport_TCPSocketStartBind(self0 uint32, network0 uint32, localAddress0 uint32, localAddress1 uint32, localAddress2 uint32, localAddress3 uint32, localAddress4 uint32, localAddress5 uint32, localAddress6 uint32, localAddress7 uint32, localAddress8 uint32, localAddress9 uint32, localAddress10 uint32, localAddress11 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) // StartConnect represents the imported method "start-connect". // @@ -748,7 +759,7 @@ func wasmimport_TCPSocketStartBind(self0 uint32, network0 uint32, localAddress0 // -> result<_, error-code> // //go:nosplit -func (self TCPSocket) StartConnect(network_ network.Network, remoteAddress network.IPSocketAddress) (result cm.ErrResult[struct{}, network.ErrorCode]) { +func (self TCPSocket) StartConnect(network_ network.Network, remoteAddress network.IPSocketAddress) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) network0 := cm.Reinterpret[uint32](network_) remoteAddress0, remoteAddress1, remoteAddress2, remoteAddress3, remoteAddress4, remoteAddress5, remoteAddress6, remoteAddress7, remoteAddress8, remoteAddress9, remoteAddress10, remoteAddress11 := lower_IPSocketAddress(remoteAddress) @@ -758,7 +769,7 @@ func (self TCPSocket) StartConnect(network_ network.Network, remoteAddress netwo //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.start-connect //go:noescape -func wasmimport_TCPSocketStartConnect(self0 uint32, network0 uint32, remoteAddress0 uint32, remoteAddress1 uint32, remoteAddress2 uint32, remoteAddress3 uint32, remoteAddress4 uint32, remoteAddress5 uint32, remoteAddress6 uint32, remoteAddress7 uint32, remoteAddress8 uint32, remoteAddress9 uint32, remoteAddress10 uint32, remoteAddress11 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) +func wasmimport_TCPSocketStartConnect(self0 uint32, network0 uint32, remoteAddress0 uint32, remoteAddress1 uint32, remoteAddress2 uint32, remoteAddress3 uint32, remoteAddress4 uint32, remoteAddress5 uint32, remoteAddress6 uint32, remoteAddress7 uint32, remoteAddress8 uint32, remoteAddress9 uint32, remoteAddress10 uint32, remoteAddress11 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) // StartListen represents the imported method "start-listen". // @@ -794,7 +805,7 @@ func wasmimport_TCPSocketStartConnect(self0 uint32, network0 uint32, remoteAddre // start-listen: func() -> result<_, error-code> // //go:nosplit -func (self TCPSocket) StartListen() (result cm.ErrResult[struct{}, network.ErrorCode]) { +func (self TCPSocket) StartListen() (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketStartListen((uint32)(self0), &result) return @@ -802,7 +813,7 @@ func (self TCPSocket) StartListen() (result cm.ErrResult[struct{}, network.Error //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.start-listen //go:noescape -func wasmimport_TCPSocketStartListen(self0 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) +func wasmimport_TCPSocketStartListen(self0 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) // Subscribe represents the imported method "subscribe". // diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go index a73695a9c4..b30bafaad4 100644 --- a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go @@ -43,7 +43,7 @@ import ( // error-code> // //go:nosplit -func CreateUDPSocket(addressFamily network.IPAddressFamily) (result cm.OKResult[udp.UDPSocket, network.ErrorCode]) { +func CreateUDPSocket(addressFamily network.IPAddressFamily) (result cm.Result[udp.UDPSocket, udp.UDPSocket, network.ErrorCode]) { addressFamily0 := (uint32)(addressFamily) wasmimport_CreateUDPSocket((uint32)(addressFamily0), &result) return @@ -51,4 +51,4 @@ func CreateUDPSocket(addressFamily network.IPAddressFamily) (result cm.OKResult[ //go:wasmimport wasi:sockets/udp-create-socket@0.2.0 create-udp-socket //go:noescape -func wasmimport_CreateUDPSocket(addressFamily0 uint32, result *cm.OKResult[udp.UDPSocket, network.ErrorCode]) +func wasmimport_CreateUDPSocket(addressFamily0 uint32, result *cm.Result[udp.UDPSocket, udp.UDPSocket, network.ErrorCode]) diff --git a/src/internal/wasi/sockets/v0.2.0/udp/abi.go b/src/internal/wasi/sockets/v0.2.0/udp/abi.go index c62d3322dd..47954d8724 100644 --- a/src/internal/wasi/sockets/v0.2.0/udp/abi.go +++ b/src/internal/wasi/sockets/v0.2.0/udp/abi.go @@ -7,8 +7,14 @@ package udp import ( "github.com/ydnar/wasm-tools-go/cm" "internal/wasi/sockets/v0.2.0/network" + "unsafe" ) +// IPSocketAddressShape is used for storage in variant or result types. +type IPSocketAddressShape struct { + shape [unsafe.Sizeof(network.IPSocketAddress{})]byte +} + func lower_IPv4Address(v network.IPv4Address) (f0 uint32, f1 uint32, f2 uint32, f3 uint32) { f0 = (uint32)(v[0]) f1 = (uint32)(v[1]) @@ -44,7 +50,7 @@ func lower_IPv6SocketAddress(v network.IPv6SocketAddress) (f0 uint32, f1 uint32, } func lower_IPSocketAddress(v network.IPSocketAddress) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32, f8 uint32, f9 uint32, f10 uint32, f11 uint32) { - f0 = (uint32)(cm.Tag(&v)) + f0 = (uint32)(v.Tag()) switch f0 { case 0: // ipv4 v1, v2, v3, v4, v5 := lower_IPv4SocketAddress(*v.IPv4()) @@ -70,6 +76,11 @@ func lower_IPSocketAddress(v network.IPSocketAddress) (f0 uint32, f1 uint32, f2 return } +// TupleIncomingDatagramStreamOutgoingDatagramStreamShape is used for storage in variant or result types. +type TupleIncomingDatagramStreamOutgoingDatagramStreamShape struct { + shape [unsafe.Sizeof(cm.Tuple[IncomingDatagramStream, OutgoingDatagramStream]{})]byte +} + func lower_OptionIPSocketAddress(v cm.Option[network.IPSocketAddress]) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32, f8 uint32, f9 uint32, f10 uint32, f11 uint32, f12 uint32) { some := v.Some() if some != nil { diff --git a/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go b/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go index 41b3b9ac0c..0757398769 100644 --- a/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go @@ -105,7 +105,7 @@ func wasmimport_UDPSocketAddressFamily(self0 uint32) (result0 uint32) // finish-bind: func() -> result<_, error-code> // //go:nosplit -func (self UDPSocket) FinishBind() (result cm.ErrResult[struct{}, network.ErrorCode]) { +func (self UDPSocket) FinishBind() (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_UDPSocketFinishBind((uint32)(self0), &result) return @@ -113,7 +113,7 @@ func (self UDPSocket) FinishBind() (result cm.ErrResult[struct{}, network.ErrorC //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.finish-bind //go:noescape -func wasmimport_UDPSocketFinishBind(self0 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) +func wasmimport_UDPSocketFinishBind(self0 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) // LocalAddress represents the imported method "local-address". // @@ -138,7 +138,7 @@ func wasmimport_UDPSocketFinishBind(self0 uint32, result *cm.ErrResult[struct{}, // local-address: func() -> result // //go:nosplit -func (self UDPSocket) LocalAddress() (result cm.OKResult[network.IPSocketAddress, network.ErrorCode]) { +func (self UDPSocket) LocalAddress() (result cm.Result[IPSocketAddressShape, network.IPSocketAddress, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_UDPSocketLocalAddress((uint32)(self0), &result) return @@ -146,7 +146,7 @@ func (self UDPSocket) LocalAddress() (result cm.OKResult[network.IPSocketAddress //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.local-address //go:noescape -func wasmimport_UDPSocketLocalAddress(self0 uint32, result *cm.OKResult[network.IPSocketAddress, network.ErrorCode]) +func wasmimport_UDPSocketLocalAddress(self0 uint32, result *cm.Result[IPSocketAddressShape, network.IPSocketAddress, network.ErrorCode]) // ReceiveBufferSize represents the imported method "receive-buffer-size". // @@ -166,7 +166,7 @@ func wasmimport_UDPSocketLocalAddress(self0 uint32, result *cm.OKResult[network. // receive-buffer-size: func() -> result // //go:nosplit -func (self UDPSocket) ReceiveBufferSize() (result cm.OKResult[uint64, network.ErrorCode]) { +func (self UDPSocket) ReceiveBufferSize() (result cm.Result[uint64, uint64, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_UDPSocketReceiveBufferSize((uint32)(self0), &result) return @@ -174,7 +174,7 @@ func (self UDPSocket) ReceiveBufferSize() (result cm.OKResult[uint64, network.Er //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.receive-buffer-size //go:noescape -func wasmimport_UDPSocketReceiveBufferSize(self0 uint32, result *cm.OKResult[uint64, network.ErrorCode]) +func wasmimport_UDPSocketReceiveBufferSize(self0 uint32, result *cm.Result[uint64, uint64, network.ErrorCode]) // RemoteAddress represents the imported method "remote-address". // @@ -192,7 +192,7 @@ func wasmimport_UDPSocketReceiveBufferSize(self0 uint32, result *cm.OKResult[uin // remote-address: func() -> result // //go:nosplit -func (self UDPSocket) RemoteAddress() (result cm.OKResult[network.IPSocketAddress, network.ErrorCode]) { +func (self UDPSocket) RemoteAddress() (result cm.Result[IPSocketAddressShape, network.IPSocketAddress, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_UDPSocketRemoteAddress((uint32)(self0), &result) return @@ -200,14 +200,14 @@ func (self UDPSocket) RemoteAddress() (result cm.OKResult[network.IPSocketAddres //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.remote-address //go:noescape -func wasmimport_UDPSocketRemoteAddress(self0 uint32, result *cm.OKResult[network.IPSocketAddress, network.ErrorCode]) +func wasmimport_UDPSocketRemoteAddress(self0 uint32, result *cm.Result[IPSocketAddressShape, network.IPSocketAddress, network.ErrorCode]) // SendBufferSize represents the imported method "send-buffer-size". // // send-buffer-size: func() -> result // //go:nosplit -func (self UDPSocket) SendBufferSize() (result cm.OKResult[uint64, network.ErrorCode]) { +func (self UDPSocket) SendBufferSize() (result cm.Result[uint64, uint64, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_UDPSocketSendBufferSize((uint32)(self0), &result) return @@ -215,14 +215,14 @@ func (self UDPSocket) SendBufferSize() (result cm.OKResult[uint64, network.Error //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.send-buffer-size //go:noescape -func wasmimport_UDPSocketSendBufferSize(self0 uint32, result *cm.OKResult[uint64, network.ErrorCode]) +func wasmimport_UDPSocketSendBufferSize(self0 uint32, result *cm.Result[uint64, uint64, network.ErrorCode]) // SetReceiveBufferSize represents the imported method "set-receive-buffer-size". // // set-receive-buffer-size: func(value: u64) -> result<_, error-code> // //go:nosplit -func (self UDPSocket) SetReceiveBufferSize(value uint64) (result cm.ErrResult[struct{}, network.ErrorCode]) { +func (self UDPSocket) SetReceiveBufferSize(value uint64) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint64)(value) wasmimport_UDPSocketSetReceiveBufferSize((uint32)(self0), (uint64)(value0), &result) @@ -231,14 +231,14 @@ func (self UDPSocket) SetReceiveBufferSize(value uint64) (result cm.ErrResult[st //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.set-receive-buffer-size //go:noescape -func wasmimport_UDPSocketSetReceiveBufferSize(self0 uint32, value0 uint64, result *cm.ErrResult[struct{}, network.ErrorCode]) +func wasmimport_UDPSocketSetReceiveBufferSize(self0 uint32, value0 uint64, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) // SetSendBufferSize represents the imported method "set-send-buffer-size". // // set-send-buffer-size: func(value: u64) -> result<_, error-code> // //go:nosplit -func (self UDPSocket) SetSendBufferSize(value uint64) (result cm.ErrResult[struct{}, network.ErrorCode]) { +func (self UDPSocket) SetSendBufferSize(value uint64) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint64)(value) wasmimport_UDPSocketSetSendBufferSize((uint32)(self0), (uint64)(value0), &result) @@ -247,14 +247,14 @@ func (self UDPSocket) SetSendBufferSize(value uint64) (result cm.ErrResult[struc //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.set-send-buffer-size //go:noescape -func wasmimport_UDPSocketSetSendBufferSize(self0 uint32, value0 uint64, result *cm.ErrResult[struct{}, network.ErrorCode]) +func wasmimport_UDPSocketSetSendBufferSize(self0 uint32, value0 uint64, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) // SetUnicastHopLimit represents the imported method "set-unicast-hop-limit". // // set-unicast-hop-limit: func(value: u8) -> result<_, error-code> // //go:nosplit -func (self UDPSocket) SetUnicastHopLimit(value uint8) (result cm.ErrResult[struct{}, network.ErrorCode]) { +func (self UDPSocket) SetUnicastHopLimit(value uint8) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint32)(value) wasmimport_UDPSocketSetUnicastHopLimit((uint32)(self0), (uint32)(value0), &result) @@ -263,7 +263,7 @@ func (self UDPSocket) SetUnicastHopLimit(value uint8) (result cm.ErrResult[struc //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.set-unicast-hop-limit //go:noescape -func wasmimport_UDPSocketSetUnicastHopLimit(self0 uint32, value0 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) +func wasmimport_UDPSocketSetUnicastHopLimit(self0 uint32, value0 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) // StartBind represents the imported method "start-bind". // @@ -303,7 +303,7 @@ func wasmimport_UDPSocketSetUnicastHopLimit(self0 uint32, value0 uint32, result // result<_, error-code> // //go:nosplit -func (self UDPSocket) StartBind(network_ network.Network, localAddress network.IPSocketAddress) (result cm.ErrResult[struct{}, network.ErrorCode]) { +func (self UDPSocket) StartBind(network_ network.Network, localAddress network.IPSocketAddress) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) network0 := cm.Reinterpret[uint32](network_) localAddress0, localAddress1, localAddress2, localAddress3, localAddress4, localAddress5, localAddress6, localAddress7, localAddress8, localAddress9, localAddress10, localAddress11 := lower_IPSocketAddress(localAddress) @@ -313,7 +313,7 @@ func (self UDPSocket) StartBind(network_ network.Network, localAddress network.I //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.start-bind //go:noescape -func wasmimport_UDPSocketStartBind(self0 uint32, network0 uint32, localAddress0 uint32, localAddress1 uint32, localAddress2 uint32, localAddress3 uint32, localAddress4 uint32, localAddress5 uint32, localAddress6 uint32, localAddress7 uint32, localAddress8 uint32, localAddress9 uint32, localAddress10 uint32, localAddress11 uint32, result *cm.ErrResult[struct{}, network.ErrorCode]) +func wasmimport_UDPSocketStartBind(self0 uint32, network0 uint32, localAddress0 uint32, localAddress1 uint32, localAddress2 uint32, localAddress3 uint32, localAddress4 uint32, localAddress5 uint32, localAddress6 uint32, localAddress7 uint32, localAddress8 uint32, localAddress9 uint32, localAddress10 uint32, localAddress11 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) // Stream represents the imported method "stream". // @@ -372,7 +372,7 @@ func wasmimport_UDPSocketStartBind(self0 uint32, network0 uint32, localAddress0 // outgoing-datagram-stream>, error-code> // //go:nosplit -func (self UDPSocket) Stream(remoteAddress cm.Option[network.IPSocketAddress]) (result cm.OKResult[cm.Tuple[IncomingDatagramStream, OutgoingDatagramStream], network.ErrorCode]) { +func (self UDPSocket) Stream(remoteAddress cm.Option[network.IPSocketAddress]) (result cm.Result[TupleIncomingDatagramStreamOutgoingDatagramStreamShape, cm.Tuple[IncomingDatagramStream, OutgoingDatagramStream], network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) remoteAddress0, remoteAddress1, remoteAddress2, remoteAddress3, remoteAddress4, remoteAddress5, remoteAddress6, remoteAddress7, remoteAddress8, remoteAddress9, remoteAddress10, remoteAddress11, remoteAddress12 := lower_OptionIPSocketAddress(remoteAddress) wasmimport_UDPSocketStream((uint32)(self0), (uint32)(remoteAddress0), (uint32)(remoteAddress1), (uint32)(remoteAddress2), (uint32)(remoteAddress3), (uint32)(remoteAddress4), (uint32)(remoteAddress5), (uint32)(remoteAddress6), (uint32)(remoteAddress7), (uint32)(remoteAddress8), (uint32)(remoteAddress9), (uint32)(remoteAddress10), (uint32)(remoteAddress11), (uint32)(remoteAddress12), &result) @@ -381,7 +381,7 @@ func (self UDPSocket) Stream(remoteAddress cm.Option[network.IPSocketAddress]) ( //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.stream //go:noescape -func wasmimport_UDPSocketStream(self0 uint32, remoteAddress0 uint32, remoteAddress1 uint32, remoteAddress2 uint32, remoteAddress3 uint32, remoteAddress4 uint32, remoteAddress5 uint32, remoteAddress6 uint32, remoteAddress7 uint32, remoteAddress8 uint32, remoteAddress9 uint32, remoteAddress10 uint32, remoteAddress11 uint32, remoteAddress12 uint32, result *cm.OKResult[cm.Tuple[IncomingDatagramStream, OutgoingDatagramStream], network.ErrorCode]) +func wasmimport_UDPSocketStream(self0 uint32, remoteAddress0 uint32, remoteAddress1 uint32, remoteAddress2 uint32, remoteAddress3 uint32, remoteAddress4 uint32, remoteAddress5 uint32, remoteAddress6 uint32, remoteAddress7 uint32, remoteAddress8 uint32, remoteAddress9 uint32, remoteAddress10 uint32, remoteAddress11 uint32, remoteAddress12 uint32, result *cm.Result[TupleIncomingDatagramStreamOutgoingDatagramStreamShape, cm.Tuple[IncomingDatagramStream, OutgoingDatagramStream], network.ErrorCode]) // Subscribe represents the imported method "subscribe". // @@ -416,7 +416,7 @@ func wasmimport_UDPSocketSubscribe(self0 uint32) (result0 uint32) // unicast-hop-limit: func() -> result // //go:nosplit -func (self UDPSocket) UnicastHopLimit() (result cm.OKResult[uint8, network.ErrorCode]) { +func (self UDPSocket) UnicastHopLimit() (result cm.Result[uint8, uint8, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_UDPSocketUnicastHopLimit((uint32)(self0), &result) return @@ -424,7 +424,7 @@ func (self UDPSocket) UnicastHopLimit() (result cm.OKResult[uint8, network.Error //go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.unicast-hop-limit //go:noescape -func wasmimport_UDPSocketUnicastHopLimit(self0 uint32, result *cm.OKResult[uint8, network.ErrorCode]) +func wasmimport_UDPSocketUnicastHopLimit(self0 uint32, result *cm.Result[uint8, uint8, network.ErrorCode]) // IncomingDatagramStream represents the imported resource "wasi:sockets/udp@0.2.0#incoming-datagram-stream". // @@ -477,7 +477,7 @@ func wasmimport_IncomingDatagramStreamResourceDrop(self0 uint32) // receive: func(max-results: u64) -> result, error-code> // //go:nosplit -func (self IncomingDatagramStream) Receive(maxResults uint64) (result cm.OKResult[cm.List[IncomingDatagram], network.ErrorCode]) { +func (self IncomingDatagramStream) Receive(maxResults uint64) (result cm.Result[cm.List[IncomingDatagram], cm.List[IncomingDatagram], network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) maxResults0 := (uint64)(maxResults) wasmimport_IncomingDatagramStreamReceive((uint32)(self0), (uint64)(maxResults0), &result) @@ -486,7 +486,7 @@ func (self IncomingDatagramStream) Receive(maxResults uint64) (result cm.OKResul //go:wasmimport wasi:sockets/udp@0.2.0 [method]incoming-datagram-stream.receive //go:noescape -func wasmimport_IncomingDatagramStreamReceive(self0 uint32, maxResults0 uint64, result *cm.OKResult[cm.List[IncomingDatagram], network.ErrorCode]) +func wasmimport_IncomingDatagramStreamReceive(self0 uint32, maxResults0 uint64, result *cm.Result[cm.List[IncomingDatagram], cm.List[IncomingDatagram], network.ErrorCode]) // Subscribe represents the imported method "subscribe". // @@ -546,7 +546,7 @@ func wasmimport_OutgoingDatagramStreamResourceDrop(self0 uint32) // check-send: func() -> result // //go:nosplit -func (self OutgoingDatagramStream) CheckSend() (result cm.OKResult[uint64, network.ErrorCode]) { +func (self OutgoingDatagramStream) CheckSend() (result cm.Result[uint64, uint64, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_OutgoingDatagramStreamCheckSend((uint32)(self0), &result) return @@ -554,7 +554,7 @@ func (self OutgoingDatagramStream) CheckSend() (result cm.OKResult[uint64, netwo //go:wasmimport wasi:sockets/udp@0.2.0 [method]outgoing-datagram-stream.check-send //go:noescape -func wasmimport_OutgoingDatagramStreamCheckSend(self0 uint32, result *cm.OKResult[uint64, network.ErrorCode]) +func wasmimport_OutgoingDatagramStreamCheckSend(self0 uint32, result *cm.Result[uint64, uint64, network.ErrorCode]) // Send represents the imported method "send". // @@ -610,7 +610,7 @@ func wasmimport_OutgoingDatagramStreamCheckSend(self0 uint32, result *cm.OKResul // send: func(datagrams: list) -> result // //go:nosplit -func (self OutgoingDatagramStream) Send(datagrams cm.List[OutgoingDatagram]) (result cm.OKResult[uint64, network.ErrorCode]) { +func (self OutgoingDatagramStream) Send(datagrams cm.List[OutgoingDatagram]) (result cm.Result[uint64, uint64, network.ErrorCode]) { self0 := cm.Reinterpret[uint32](self) datagrams0, datagrams1 := cm.LowerList(datagrams) wasmimport_OutgoingDatagramStreamSend((uint32)(self0), (*OutgoingDatagram)(datagrams0), (uint32)(datagrams1), &result) @@ -619,7 +619,7 @@ func (self OutgoingDatagramStream) Send(datagrams cm.List[OutgoingDatagram]) (re //go:wasmimport wasi:sockets/udp@0.2.0 [method]outgoing-datagram-stream.send //go:noescape -func wasmimport_OutgoingDatagramStreamSend(self0 uint32, datagrams0 *OutgoingDatagram, datagrams1 uint32, result *cm.OKResult[uint64, network.ErrorCode]) +func wasmimport_OutgoingDatagramStreamSend(self0 uint32, datagrams0 *OutgoingDatagram, datagrams1 uint32, result *cm.Result[uint64, uint64, network.ErrorCode]) // Subscribe represents the imported method "subscribe". // diff --git a/src/vendor/github.com/ydnar/wasm-tools-go b/src/vendor/github.com/ydnar/wasm-tools-go index ca8d06b46f..49f7d9208e 160000 --- a/src/vendor/github.com/ydnar/wasm-tools-go +++ b/src/vendor/github.com/ydnar/wasm-tools-go @@ -1 +1 @@ -Subproject commit ca8d06b46fa45bdc90b76060fc9bbf7ab9af5eed +Subproject commit 49f7d9208ece21e46a1232189d664d649524b8cc From d150badf33d6087bdad8da5eb6a680e13dafe6ff Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 8 Jul 2024 16:34:40 -0700 Subject: [PATCH 087/444] gc_leaking: Don't zero out new allocations in some targets (#4302) Wasm linear memory is always initialized to zero by definition, so there's no need to waste time zeroing out this allocation. This is the case for freshly-obtained memory from mmap(). Signed-off-by: L. Pereira --- src/runtime/gc_leaking.go | 2 +- src/runtime/zero_new_alloc.go | 12 ++++++++++++ src/runtime/zero_new_alloc_noop.go | 14 ++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 src/runtime/zero_new_alloc.go create mode 100644 src/runtime/zero_new_alloc_noop.go diff --git a/src/runtime/gc_leaking.go b/src/runtime/gc_leaking.go index 26b012bdbb..5c9594277c 100644 --- a/src/runtime/gc_leaking.go +++ b/src/runtime/gc_leaking.go @@ -44,7 +44,7 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { runtimePanic("out of memory") } pointer := unsafe.Pointer(addr) - memzero(pointer, size) + zero_new_alloc(pointer, size) return pointer } diff --git a/src/runtime/zero_new_alloc.go b/src/runtime/zero_new_alloc.go new file mode 100644 index 0000000000..b94190ae0a --- /dev/null +++ b/src/runtime/zero_new_alloc.go @@ -0,0 +1,12 @@ +//go:build gc.leaking && (baremetal || nintendoswitch) + +package runtime + +import ( + "unsafe" +) + +//go:inline +func zero_new_alloc(ptr unsafe.Pointer, size uintptr) { + memzero(ptr, size) +} diff --git a/src/runtime/zero_new_alloc_noop.go b/src/runtime/zero_new_alloc_noop.go new file mode 100644 index 0000000000..79fefc199b --- /dev/null +++ b/src/runtime/zero_new_alloc_noop.go @@ -0,0 +1,14 @@ +//go:build gc.leaking && !baremetal && !nintendoswitch + +package runtime + +import ( + "unsafe" +) + +//go:inline +func zero_new_alloc(ptr unsafe.Pointer, size uintptr) { + // Wasm linear memory is initialized to zero by default, so + // there's no need to do anything. This is also the case for + // fresh-allocated memory from an mmap() system call. +} From ac396708da17271ec48bffefb57d1185dd8cadb1 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Mon, 8 Jul 2024 16:35:06 -0700 Subject: [PATCH 088/444] main_test: Diff expected and actual results when tests fail (#4288) Uses a vendored "internal/diff" from Big Go to show the differences between the expected and actual outputs when tests fail. Signed-off-by: L. Pereira --- diff.go | 261 +++++++++++++++++++++++++++++++++++++++++++++++++++ main_test.go | 1 + 2 files changed, 262 insertions(+) create mode 100644 diff.go diff --git a/diff.go b/diff.go new file mode 100644 index 0000000000..87ce86d0e5 --- /dev/null +++ b/diff.go @@ -0,0 +1,261 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "fmt" + "sort" + "strings" +) + +// A pair is a pair of values tracked for both the x and y side of a diff. +// It is typically a pair of line indexes. +type pair struct{ x, y int } + +// Diff returns an anchored diff of the two texts old and new +// in the “unified diff” format. If old and new are identical, +// Diff returns a nil slice (no output). +// +// Unix diff implementations typically look for a diff with +// the smallest number of lines inserted and removed, +// which can in the worst case take time quadratic in the +// number of lines in the texts. As a result, many implementations +// either can be made to run for a long time or cut off the search +// after a predetermined amount of work. +// +// In contrast, this implementation looks for a diff with the +// smallest number of “unique” lines inserted and removed, +// where unique means a line that appears just once in both old and new. +// We call this an “anchored diff” because the unique lines anchor +// the chosen matching regions. An anchored diff is usually clearer +// than a standard diff, because the algorithm does not try to +// reuse unrelated blank lines or closing braces. +// The algorithm also guarantees to run in O(n log n) time +// instead of the standard O(n²) time. +// +// Some systems call this approach a “patience diff,” named for +// the “patience sorting” algorithm, itself named for a solitaire card game. +// We avoid that name for two reasons. First, the name has been used +// for a few different variants of the algorithm, so it is imprecise. +// Second, the name is frequently interpreted as meaning that you have +// to wait longer (to be patient) for the diff, meaning that it is a slower algorithm, +// when in fact the algorithm is faster than the standard one. +func Diff(oldName string, old []byte, newName string, new []byte) []byte { + if bytes.Equal(old, new) { + return nil + } + x := lines(old) + y := lines(new) + + // Print diff header. + var out bytes.Buffer + fmt.Fprintf(&out, "diff %s %s\n", oldName, newName) + fmt.Fprintf(&out, "--- %s\n", oldName) + fmt.Fprintf(&out, "+++ %s\n", newName) + + // Loop over matches to consider, + // expanding each match to include surrounding lines, + // and then printing diff chunks. + // To avoid setup/teardown cases outside the loop, + // tgs returns a leading {0,0} and trailing {len(x), len(y)} pair + // in the sequence of matches. + var ( + done pair // printed up to x[:done.x] and y[:done.y] + chunk pair // start lines of current chunk + count pair // number of lines from each side in current chunk + ctext []string // lines for current chunk + ) + for _, m := range tgs(x, y) { + if m.x < done.x { + // Already handled scanning forward from earlier match. + continue + } + + // Expand matching lines as far as possible, + // establishing that x[start.x:end.x] == y[start.y:end.y]. + // Note that on the first (or last) iteration we may (or definitely do) + // have an empty match: start.x==end.x and start.y==end.y. + start := m + for start.x > done.x && start.y > done.y && x[start.x-1] == y[start.y-1] { + start.x-- + start.y-- + } + end := m + for end.x < len(x) && end.y < len(y) && x[end.x] == y[end.y] { + end.x++ + end.y++ + } + + // Emit the mismatched lines before start into this chunk. + // (No effect on first sentinel iteration, when start = {0,0}.) + for _, s := range x[done.x:start.x] { + ctext = append(ctext, "-"+s) + count.x++ + } + for _, s := range y[done.y:start.y] { + ctext = append(ctext, "+"+s) + count.y++ + } + + // If we're not at EOF and have too few common lines, + // the chunk includes all the common lines and continues. + const C = 3 // number of context lines + if (end.x < len(x) || end.y < len(y)) && + (end.x-start.x < C || (len(ctext) > 0 && end.x-start.x < 2*C)) { + for _, s := range x[start.x:end.x] { + ctext = append(ctext, " "+s) + count.x++ + count.y++ + } + done = end + continue + } + + // End chunk with common lines for context. + if len(ctext) > 0 { + n := end.x - start.x + if n > C { + n = C + } + for _, s := range x[start.x : start.x+n] { + ctext = append(ctext, " "+s) + count.x++ + count.y++ + } + done = pair{start.x + n, start.y + n} + + // Format and emit chunk. + // Convert line numbers to 1-indexed. + // Special case: empty file shows up as 0,0 not 1,0. + if count.x > 0 { + chunk.x++ + } + if count.y > 0 { + chunk.y++ + } + fmt.Fprintf(&out, "@@ -%d,%d +%d,%d @@\n", chunk.x, count.x, chunk.y, count.y) + for _, s := range ctext { + out.WriteString(s) + } + count.x = 0 + count.y = 0 + ctext = ctext[:0] + } + + // If we reached EOF, we're done. + if end.x >= len(x) && end.y >= len(y) { + break + } + + // Otherwise start a new chunk. + chunk = pair{end.x - C, end.y - C} + for _, s := range x[chunk.x:end.x] { + ctext = append(ctext, " "+s) + count.x++ + count.y++ + } + done = end + } + + return out.Bytes() +} + +// lines returns the lines in the file x, including newlines. +// If the file does not end in a newline, one is supplied +// along with a warning about the missing newline. +func lines(x []byte) []string { + l := strings.SplitAfter(string(x), "\n") + if l[len(l)-1] == "" { + l = l[:len(l)-1] + } else { + // Treat last line as having a message about the missing newline attached, + // using the same text as BSD/GNU diff (including the leading backslash). + l[len(l)-1] += "\n\\ No newline at end of file\n" + } + return l +} + +// tgs returns the pairs of indexes of the longest common subsequence +// of unique lines in x and y, where a unique line is one that appears +// once in x and once in y. +// +// The longest common subsequence algorithm is as described in +// Thomas G. Szymanski, “A Special Case of the Maximal Common +// Subsequence Problem,” Princeton TR #170 (January 1975), +// available at https://research.swtch.com/tgs170.pdf. +func tgs(x, y []string) []pair { + // Count the number of times each string appears in a and b. + // We only care about 0, 1, many, counted as 0, -1, -2 + // for the x side and 0, -4, -8 for the y side. + // Using negative numbers now lets us distinguish positive line numbers later. + m := make(map[string]int) + for _, s := range x { + if c := m[s]; c > -2 { + m[s] = c - 1 + } + } + for _, s := range y { + if c := m[s]; c > -8 { + m[s] = c - 4 + } + } + + // Now unique strings can be identified by m[s] = -1+-4. + // + // Gather the indexes of those strings in x and y, building: + // xi[i] = increasing indexes of unique strings in x. + // yi[i] = increasing indexes of unique strings in y. + // inv[i] = index j such that x[xi[i]] = y[yi[j]]. + var xi, yi, inv []int + for i, s := range y { + if m[s] == -1+-4 { + m[s] = len(yi) + yi = append(yi, i) + } + } + for i, s := range x { + if j, ok := m[s]; ok && j >= 0 { + xi = append(xi, i) + inv = append(inv, j) + } + } + + // Apply Algorithm A from Szymanski's paper. + // In those terms, A = J = inv and B = [0, n). + // We add sentinel pairs {0,0}, and {len(x),len(y)} + // to the returned sequence, to help the processing loop. + J := inv + n := len(xi) + T := make([]int, n) + L := make([]int, n) + for i := range T { + T[i] = n + 1 + } + for i := 0; i < n; i++ { + k := sort.Search(n, func(k int) bool { + return T[k] >= J[i] + }) + T[k] = J[i] + L[i] = k + 1 + } + k := 0 + for _, v := range L { + if k < v { + k = v + } + } + seq := make([]pair, 2+k) + seq[1+k] = pair{len(x), len(y)} // sentinel at end + lastj := n + for i := n - 1; i >= 0; i-- { + if L[i] == k && J[i] < lastj { + seq[k] = pair{xi[i], yi[J[i]]} + k-- + } + } + seq[0] = pair{0, 0} // sentinel at start + return seq +} diff --git a/main_test.go b/main_test.go index a4ec3c810d..2386143304 100644 --- a/main_test.go +++ b/main_test.go @@ -409,6 +409,7 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c fail = true } else if !bytes.Equal(expected, actual) { t.Logf("output did not match (expected %d bytes, got %d bytes):", len(expected), len(actual)) + t.Logf(string(Diff("expected", expected, "actual", actual))) fail = true } From 6f462fba4c4125f164aef634ab5b1bb8c6f88342 Mon Sep 17 00:00:00 2001 From: Dmitry Shemin Date: Tue, 9 Jul 2024 08:53:50 +0700 Subject: [PATCH 089/444] fix: remove message after test binary built --- main.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index cd2e43ace0..4a23fa8cf1 100644 --- a/main.go +++ b/main.go @@ -347,6 +347,11 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options } return err }) + + if testConfig.CompileOnly { + return true, nil + } + importPath := strings.TrimSuffix(result.ImportPath, ".test") var w io.Writer = stdout @@ -357,7 +362,7 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options fmt.Fprintf(w, "? \t%s\t[no test files]\n", err.ImportPath) // Pretend the test passed - it at least didn't fail. return true, nil - } else if passed && !testConfig.CompileOnly { + } else if passed { fmt.Fprintf(w, "ok \t%s\t%.3fs\n", importPath, duration.Seconds()) } else { fmt.Fprintf(w, "FAIL\t%s\t%.3fs\n", importPath, duration.Seconds()) From 2f3d821b136be347dda00025d0dba7c7749b93ca Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 12 Jul 2024 19:15:32 +0200 Subject: [PATCH 090/444] cgo: support preprocessor macros passed on the command line Go code might sometimes want to use preprocessor macros that were passed on the command line. This wasn't working before and resulted in the following error: internal error: could not find file where macro is defined This is now supported, though location information isn't available (which makes sense: the command line is not a file). I had to use the `clang_tokenize` API for this and reconstruct the original source location. Apparently this is the only way to do it: https://stackoverflow.com/a/19074846/559350 In the future we could consider replacing our own tokenization with the tokenizer that's built into Clang directly. This should reduce the possibility of bugs a bit. --- cgo/cgo_test.go | 9 ++++- cgo/const.go | 8 +++-- cgo/libclang.go | 68 ++++++++++++++++++++------------------ cgo/testdata/errors.go | 13 ++++++++ cgo/testdata/errors.out.go | 11 ++++-- 5 files changed, 72 insertions(+), 37 deletions(-) diff --git a/cgo/cgo_test.go b/cgo/cgo_test.go index 60af3e6f2a..dc79b21d53 100644 --- a/cgo/cgo_test.go +++ b/cgo/cgo_test.go @@ -7,6 +7,7 @@ import ( "go/ast" "go/format" "go/parser" + "go/scanner" "go/token" "go/types" "os" @@ -219,7 +220,13 @@ func (i simpleImporter) Import(path string) (*types.Package, error) { // formatDiagnostic formats the error message to be an indented comment. It // also fixes Windows path name issues (backward slashes). func formatDiagnostic(err error) string { - msg := err.Error() + var msg string + switch err := err.(type) { + case scanner.Error: + msg = err.Pos.String() + ": " + err.Msg + default: + msg = err.Error() + } if runtime.GOOS == "windows" { // Fix Windows path slashes. msg = strings.ReplaceAll(msg, "testdata\\", "testdata/") diff --git a/cgo/const.go b/cgo/const.go index 2d0e29e10d..f4707c80a8 100644 --- a/cgo/const.go +++ b/cgo/const.go @@ -195,7 +195,9 @@ func (t *tokenizer) Next() { t.curValue = t.peekValue // Parse the next peek token. - t.peekPos += token.Pos(len(t.curValue)) + if t.peekPos != token.NoPos { + t.peekPos += token.Pos(len(t.curValue)) + } for { if len(t.buf) == 0 { t.peekToken = token.EOF @@ -207,7 +209,9 @@ func (t *tokenizer) Next() { // Skip whitespace. // Based on this source, not sure whether it represents C whitespace: // https://en.cppreference.com/w/cpp/string/byte/isspace - t.peekPos++ + if t.peekPos != token.NoPos { + t.peekPos++ + } t.buf = t.buf[1:] case len(t.buf) >= 2 && (string(t.buf[:2]) == "||" || string(t.buf[:2]) == "&&" || string(t.buf[:2]) == "<<" || string(t.buf[:2]) == ">>"): // Two-character tokens. diff --git a/cgo/libclang.go b/cgo/libclang.go index 0860c6af4d..ee77611e69 100644 --- a/cgo/libclang.go +++ b/cgo/libclang.go @@ -4,6 +4,7 @@ package cgo // modification. It does not touch the AST itself. import ( + "bytes" "crypto/sha256" "crypto/sha512" "encoding/hex" @@ -369,42 +370,45 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { gen.Specs = append(gen.Specs, valueSpec) return gen, nil case C.CXCursor_MacroDefinition: + // Extract tokens from the Clang tokenizer. + // See: https://stackoverflow.com/a/19074846/559350 sourceRange := C.tinygo_clang_getCursorExtent(c) - start := C.clang_getRangeStart(sourceRange) - end := C.clang_getRangeEnd(sourceRange) - var file, endFile C.CXFile - var startOffset, endOffset C.unsigned - C.clang_getExpansionLocation(start, &file, nil, nil, &startOffset) - if file == nil { - f.addError(pos, "internal error: could not find file where macro is defined") - return nil, nil - } - C.clang_getExpansionLocation(end, &endFile, nil, nil, &endOffset) - if file != endFile { - f.addError(pos, "internal error: expected start and end location of a macro to be in the same file") - return nil, nil - } - if startOffset > endOffset { - f.addError(pos, "internal error: start offset of macro is after end offset") - return nil, nil - } - - // read file contents and extract the relevant byte range tu := C.tinygo_clang_Cursor_getTranslationUnit(c) - var size C.size_t - sourcePtr := C.clang_getFileContents(tu, file, &size) - if endOffset >= C.uint(size) { - f.addError(pos, "internal error: end offset of macro lies after end of file") - return nil, nil - } - source := string(((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[startOffset:endOffset:endOffset]) - if !strings.HasPrefix(source, name) { - f.addError(pos, fmt.Sprintf("internal error: expected macro value to start with %#v, got %#v", name, source)) - return nil, nil + var rawTokens *C.CXToken + var numTokens C.unsigned + C.clang_tokenize(tu, sourceRange, &rawTokens, &numTokens) + tokens := unsafe.Slice(rawTokens, numTokens) + // Convert this range of tokens back to source text. + // Ugly, but it works well enough. + sourceBuf := &bytes.Buffer{} + var startOffset int + for i, token := range tokens { + spelling := getString(C.clang_getTokenSpelling(tu, token)) + location := C.clang_getTokenLocation(tu, token) + var tokenOffset C.unsigned + C.clang_getExpansionLocation(location, nil, nil, nil, &tokenOffset) + if i == 0 { + // The first token is the macro name itself. + // Skip it (after using its location). + startOffset = int(tokenOffset) + len(name) + } else { + // Later tokens are the macro contents. + for int(tokenOffset) > (startOffset + sourceBuf.Len()) { + // Pad the source text with whitespace (that must have been + // present in the original source as well). + sourceBuf.WriteByte(' ') + } + sourceBuf.WriteString(spelling) + } } - value := source[len(name):] + C.clang_disposeTokens(tu, rawTokens, numTokens) + value := sourceBuf.String() // Try to convert this #define into a Go constant expression. - expr, scannerError := parseConst(pos+token.Pos(len(name)), f.fset, value) + tokenPos := token.NoPos + if pos != token.NoPos { + tokenPos = pos + token.Pos(len(name)) + } + expr, scannerError := parseConst(tokenPos, f.fset, value) if scannerError != nil { f.errors = append(f.errors, *scannerError) return nil, nil diff --git a/cgo/testdata/errors.go b/cgo/testdata/errors.go index 7ca5b79600..e5e809881f 100644 --- a/cgo/testdata/errors.go +++ b/cgo/testdata/errors.go @@ -13,10 +13,14 @@ typedef someType noType; // undefined type #define SOME_CONST_1 5) // invalid const syntax #define SOME_CONST_2 6) // const not used (so no error) #define SOME_CONST_3 1234 // const too large for byte +#define SOME_CONST_b 3 ) // const with lots of weird whitespace (to test error locations) +# define SOME_CONST_startspace 3) */ // // // #define SOME_CONST_4 8) // after some empty lines +// #cgo CFLAGS: -DSOME_PARAM_CONST_invalid=3/+3 +// #cgo CFLAGS: -DSOME_PARAM_CONST_valid=3+4 import "C" // #warning another warning @@ -24,6 +28,7 @@ import "C" // Make sure that errors for the following lines won't change with future // additions to the CGo preamble. +// //line errors.go:100 var ( // constant too large @@ -38,4 +43,12 @@ var ( _ byte = C.SOME_CONST_3 _ = C.SOME_CONST_4 + + _ = C.SOME_CONST_b + + _ = C.SOME_CONST_startspace + + // constants passed by a command line parameter + _ = C.SOME_PARAM_CONST_invalid + _ = C.SOME_PARAM_CONST_valid ) diff --git a/cgo/testdata/errors.out.go b/cgo/testdata/errors.out.go index b1646a2e0d..d0e04320ae 100644 --- a/cgo/testdata/errors.out.go +++ b/cgo/testdata/errors.out.go @@ -1,9 +1,12 @@ // CGo errors: // testdata/errors.go:4:2: warning: some warning // testdata/errors.go:11:9: error: unknown type name 'someType' -// testdata/errors.go:22:5: warning: another warning +// testdata/errors.go:26:5: warning: another warning // testdata/errors.go:13:23: unexpected token ), expected end of expression -// testdata/errors.go:19:26: unexpected token ), expected end of expression +// testdata/errors.go:21:26: unexpected token ), expected end of expression +// testdata/errors.go:16:33: unexpected token ), expected end of expression +// testdata/errors.go:17:34: unexpected token ), expected end of expression +// -: unexpected token INT, expected end of expression // Type checking errors after CGo processing: // testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as C.char value in variable declaration (overflows) @@ -11,6 +14,9 @@ // testdata/errors.go:108: undefined: C.SOME_CONST_1 // testdata/errors.go:110: cannot use C.SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows) // testdata/errors.go:112: undefined: C.SOME_CONST_4 +// testdata/errors.go:114: undefined: C.SOME_CONST_b +// testdata/errors.go:116: undefined: C.SOME_CONST_startspace +// testdata/errors.go:119: undefined: C.SOME_PARAM_CONST_invalid package main @@ -58,3 +64,4 @@ type C.struct_point_t struct { type C.point_t = C.struct_point_t const C.SOME_CONST_3 = 1234 +const C.SOME_PARAM_CONST_valid = 3 + 4 From 7ac1ca0ae2a727329550eecf71814f08dccf0700 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 12 Jul 2024 20:25:57 +0200 Subject: [PATCH 091/444] builder: remove workaround for generics race condition This commit reverts commit 13a8eae0d. It appars that the race has been fixed by https://github.com/golang/tools/commit/db513b091504, which we now use because it fixes another race condition as well. See: https://github.com/tinygo-org/tinygo/issues/4206 --- builder/build.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/builder/build.go b/builder/build.go index d99d640af6..50ec04d9d9 100644 --- a/builder/build.go +++ b/builder/build.go @@ -353,10 +353,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } packageActionIDJobs[pkg.ImportPath] = packageActionIDJob - // Build the SSA for the given package. - ssaPkg := program.Package(pkg.Pkg) - ssaPkg.Build() - // Now create the job to actually build the package. It will exit early // if the package is already compiled. job := &compileJob{ From 8a357af3d81b86ada93a91fbd3b57d9c5fb34fc6 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 9 Jul 2024 17:16:32 +0200 Subject: [PATCH 092/444] all: add testing for compiler error messages This is needed for some improvements I'm going to make next. This commit also refactors error handling slightly to make it more easily testable, this should hopefully not result in any actual changes in behavior. --- errors_test.go | 84 +++++++++++++++++++++++++++++++++++++++ main.go | 40 ++++++++++--------- main_test.go | 2 +- testdata/errors/cgo.go | 15 +++++++ testdata/errors/syntax.go | 7 ++++ testdata/errors/types.go | 12 ++++++ 6 files changed, 141 insertions(+), 19 deletions(-) create mode 100644 errors_test.go create mode 100644 testdata/errors/cgo.go create mode 100644 testdata/errors/syntax.go create mode 100644 testdata/errors/types.go diff --git a/errors_test.go b/errors_test.go new file mode 100644 index 0000000000..69c29148d2 --- /dev/null +++ b/errors_test.go @@ -0,0 +1,84 @@ +package main + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/tinygo-org/tinygo/compileopts" +) + +// Test the error messages of the TinyGo compiler. +func TestErrors(t *testing.T) { + for _, name := range []string{ + "cgo", + "syntax", + "types", + } { + t.Run(name, func(t *testing.T) { + testErrorMessages(t, "./testdata/errors/"+name+".go") + }) + } +} + +func testErrorMessages(t *testing.T, filename string) { + // Parse expected error messages. + expected := readErrorMessages(t, filename) + + // Try to build a binary (this should fail with an error). + tmpdir := t.TempDir() + err := Build(filename, tmpdir+"/out", &compileopts.Options{ + Target: "wasip1", + Semaphore: sema, + InterpTimeout: 180 * time.Second, + Debug: true, + VerifyIR: true, + Opt: "z", + }) + if err == nil { + t.Fatal("expected to get a compiler error") + } + + // Get the full ./testdata/errors directory. + wd, absErr := filepath.Abs("testdata/errors") + if absErr != nil { + t.Fatal(absErr) + } + + // Write error message out as plain text. + var buf bytes.Buffer + printCompilerError(err, func(v ...interface{}) { + fmt.Fprintln(&buf, v...) + }, wd) + actual := strings.TrimRight(buf.String(), "\n") + + // Check whether the error is as expected. + if actual != expected { + t.Errorf("expected error:\n%s\ngot:\n%s", indentText(expected, "> "), indentText(actual, "> ")) + } +} + +// Indent the given text with a given indentation string. +func indentText(text, indent string) string { + return indent + strings.ReplaceAll(text, "\n", "\n"+indent) +} + +// Read "// ERROR:" prefixed messages from the given file. +func readErrorMessages(t *testing.T, file string) string { + data, err := os.ReadFile(file) + if err != nil { + t.Fatal("could not read input file:", err) + } + + var errors []string + for _, line := range strings.Split(string(data), "\n") { + if strings.HasPrefix(line, "// ERROR: ") { + errors = append(errors, strings.TrimRight(line[len("// ERROR: "):], "\r\n")) + } + } + return strings.Join(errors, "\n") +} diff --git a/main.go b/main.go index 4a23fa8cf1..e18f12f1fd 100644 --- a/main.go +++ b/main.go @@ -1293,10 +1293,9 @@ func usage(command string) { // try to make the path relative to the current working directory. If any error // occurs, this error is ignored and the absolute path is returned instead. -func tryToMakePathRelative(dir string) string { - wd, err := os.Getwd() - if err != nil { - return dir +func tryToMakePathRelative(dir, wd string) string { + if wd == "" { + return dir // working directory not found } relpath, err := filepath.Rel(wd, dir) if err != nil { @@ -1307,28 +1306,25 @@ func tryToMakePathRelative(dir string) string { // printCompilerError prints compiler errors using the provided logger function // (similar to fmt.Println). -// -// There is one exception: interp errors may print to stderr unconditionally due -// to limitations in the LLVM bindings. -func printCompilerError(logln func(...interface{}), err error) { +func printCompilerError(err error, logln func(...interface{}), wd string) { switch err := err.(type) { case types.Error: - printCompilerError(logln, scanner.Error{ + printCompilerError(scanner.Error{ Pos: err.Fset.Position(err.Pos), Msg: err.Msg, - }) + }, logln, wd) case scanner.Error: if !strings.HasPrefix(err.Pos.Filename, filepath.Join(goenv.Get("GOROOT"), "src")) && !strings.HasPrefix(err.Pos.Filename, filepath.Join(goenv.Get("TINYGOROOT"), "src")) { // This file is not from the standard library (either the GOROOT or // the TINYGOROOT). Make the path relative, for easier reading. // Ignore any errors in the process (falling back to the absolute // path). - err.Pos.Filename = tryToMakePathRelative(err.Pos.Filename) + err.Pos.Filename = tryToMakePathRelative(err.Pos.Filename, wd) } logln(err) case scanner.ErrorList: for _, scannerErr := range err { - printCompilerError(logln, *scannerErr) + printCompilerError(*scannerErr, logln, wd) } case *interp.Error: logln("#", err.ImportPath) @@ -1346,7 +1342,7 @@ func printCompilerError(logln func(...interface{}), err error) { case loader.Errors: logln("#", err.Pkg.ImportPath) for _, err := range err.Errs { - printCompilerError(logln, err) + printCompilerError(err, logln, wd) } case loader.Error: logln(err.Err.Error()) @@ -1356,7 +1352,7 @@ func printCompilerError(logln func(...interface{}), err error) { } case *builder.MultiError: for _, err := range err.Errs { - printCompilerError(logln, err) + printCompilerError(err, logln, wd) } default: logln("error:", err) @@ -1365,9 +1361,13 @@ func printCompilerError(logln func(...interface{}), err error) { func handleCompilerError(err error) { if err != nil { - printCompilerError(func(args ...interface{}) { + wd, getwdErr := os.Getwd() + if getwdErr != nil { + wd = "" + } + printCompilerError(err, func(args ...interface{}) { fmt.Fprintln(os.Stderr, args...) - }, err) + }, wd) os.Exit(1) } } @@ -1769,9 +1769,13 @@ func main() { stderr := (*testStderr)(buf) passed, err := Test(pkgName, stdout, stderr, options, outpath) if err != nil { - printCompilerError(func(args ...interface{}) { + wd, getwdErr := os.Getwd() + if getwdErr != nil { + wd = "" + } + printCompilerError(err, func(args ...interface{}) { fmt.Fprintln(stderr, args...) - }, err) + }, wd) } if !passed { select { diff --git a/main_test.go b/main_test.go index 2386143304..d54af86348 100644 --- a/main_test.go +++ b/main_test.go @@ -380,7 +380,7 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c return cmd.Run() }) if err != nil { - printCompilerError(t.Log, err) + printCompilerError(err, t.Log, "") t.Fail() return } diff --git a/testdata/errors/cgo.go b/testdata/errors/cgo.go new file mode 100644 index 0000000000..e0853cc853 --- /dev/null +++ b/testdata/errors/cgo.go @@ -0,0 +1,15 @@ +package main + +// #error hello +// ))) +import "C" + +func main() { +} + +// TODO: this error should be relative to the current directory (so cgo.go +// instead of testdata/errors/cgo.go). + +// ERROR: # command-line-arguments +// ERROR: testdata/errors/cgo.go:3:5: error: hello +// ERROR: testdata/errors/cgo.go:4:4: error: expected identifier or '(' diff --git a/testdata/errors/syntax.go b/testdata/errors/syntax.go new file mode 100644 index 0000000000..48d9e732f8 --- /dev/null +++ b/testdata/errors/syntax.go @@ -0,0 +1,7 @@ +package main + +func main(var) { // syntax error +} + +// ERROR: # command-line-arguments +// ERROR: syntax.go:3:11: expected ')', found 'var' diff --git a/testdata/errors/types.go b/testdata/errors/types.go new file mode 100644 index 0000000000..491e2fe67a --- /dev/null +++ b/testdata/errors/types.go @@ -0,0 +1,12 @@ +package main + +func main() { + var a int + a = "foobar" + nonexisting() +} + +// ERROR: # command-line-arguments +// ERROR: types.go:5:6: cannot use "foobar" (untyped string constant) as int value in assignment +// ERROR: types.go:6:2: undefined: nonexisting +// ERROR: types.go:4:6: a declared and not used From b04b690842f837aedc6d8187bb4a4200b0cc6acc Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 9 Jul 2024 17:26:08 +0200 Subject: [PATCH 093/444] libclang: do not make error locations relative This is done at a later time anyway, so doesn't need to be done in the cgo package. In fact, _not_ doing it there makes it easier to print correct relative packages. --- cgo/libclang.go | 7 ------- testdata/errors/cgo.go | 7 ++----- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/cgo/libclang.go b/cgo/libclang.go index ee77611e69..59236bad42 100644 --- a/cgo/libclang.go +++ b/cgo/libclang.go @@ -646,13 +646,6 @@ func (p *cgoPackage) addErrorAfter(pos token.Pos, after, msg string) { // addErrorAt is a utility function to add an error to the list of errors. func (p *cgoPackage) addErrorAt(position token.Position, msg string) { - if filepath.IsAbs(position.Filename) { - // Relative paths for readability, like other Go parser errors. - relpath, err := filepath.Rel(p.currentDir, position.Filename) - if err == nil { - position.Filename = relpath - } - } p.errors = append(p.errors, scanner.Error{ Pos: position, Msg: msg, diff --git a/testdata/errors/cgo.go b/testdata/errors/cgo.go index e0853cc853..ce18278a94 100644 --- a/testdata/errors/cgo.go +++ b/testdata/errors/cgo.go @@ -7,9 +7,6 @@ import "C" func main() { } -// TODO: this error should be relative to the current directory (so cgo.go -// instead of testdata/errors/cgo.go). - // ERROR: # command-line-arguments -// ERROR: testdata/errors/cgo.go:3:5: error: hello -// ERROR: testdata/errors/cgo.go:4:4: error: expected identifier or '(' +// ERROR: cgo.go:3:5: error: hello +// ERROR: cgo.go:4:4: error: expected identifier or '(' From d7773d3e86e82a64c79a04fa70f740daf333a3be Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 9 Jul 2024 18:08:51 +0200 Subject: [PATCH 094/444] loader: handle `go list` errors inside TinyGo Instead of exiting with an error, handle these errors internally. This will enable a few improvements in the future. --- errors_test.go | 18 +++++++++++++++- loader/loader.go | 21 ++++++++++++++++--- main.go | 26 +++++++++++++++++++----- testdata/errors/importcycle/cycle.go | 3 +++ testdata/errors/invaliddep/invaliddep.go | 1 + testdata/errors/loader-importcycle.go | 10 +++++++++ testdata/errors/loader-invaliddep.go | 8 ++++++++ testdata/errors/loader-invalidpackage.go | 3 +++ testdata/errors/loader-nopackage.go | 14 +++++++++++++ 9 files changed, 95 insertions(+), 9 deletions(-) create mode 100644 testdata/errors/importcycle/cycle.go create mode 100644 testdata/errors/invaliddep/invaliddep.go create mode 100644 testdata/errors/loader-importcycle.go create mode 100644 testdata/errors/loader-invaliddep.go create mode 100644 testdata/errors/loader-invalidpackage.go create mode 100644 testdata/errors/loader-nopackage.go diff --git a/errors_test.go b/errors_test.go index 69c29148d2..1ee9a0e18f 100644 --- a/errors_test.go +++ b/errors_test.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "runtime" "strings" "testing" "time" @@ -16,6 +17,10 @@ import ( func TestErrors(t *testing.T) { for _, name := range []string{ "cgo", + "loader-importcycle", + "loader-invaliddep", + "loader-invalidpackage", + "loader-nopackage", "syntax", "types", } { @@ -57,11 +62,22 @@ func testErrorMessages(t *testing.T, filename string) { actual := strings.TrimRight(buf.String(), "\n") // Check whether the error is as expected. - if actual != expected { + if canonicalizeErrors(actual) != canonicalizeErrors(expected) { t.Errorf("expected error:\n%s\ngot:\n%s", indentText(expected, "> "), indentText(actual, "> ")) } } +func canonicalizeErrors(text string) string { + // Fix for Windows: replace all backslashes with forward slashes so that + // paths will be the same as on POSIX systems. + // (It may also change some other backslashes, but since this is only for + // comparing text it should be fine). + if runtime.GOOS == "windows" { + text = strings.ReplaceAll(text, "\\", "/") + } + return text +} + // Indent the given text with a given indentation string. func indentText(text, indent string) string { return indent + strings.ReplaceAll(text, "\n", "\n"+indent) diff --git a/loader/loader.go b/loader/loader.go index a874291a6a..fe75e6c9b4 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -128,7 +128,7 @@ func Load(config *compileopts.Config, inputPkg string, typeChecker types.Config) } // List the dependencies of this package, in raw JSON format. - extraArgs := []string{"-json", "-deps"} + extraArgs := []string{"-json", "-deps", "-e"} if config.TestConfig.CompileTestBinary { extraArgs = append(extraArgs, "-test") } @@ -149,6 +149,7 @@ func Load(config *compileopts.Config, inputPkg string, typeChecker types.Config) // Parse the returned json from `go list`. decoder := json.NewDecoder(buf) + var pkgErrors []error for { pkg := &Package{ program: p, @@ -188,6 +189,12 @@ func Load(config *compileopts.Config, inputPkg string, typeChecker types.Config) pos.Filename = strings.Join(fields[:len(fields)-1], ":") pos.Line, _ = strconv.Atoi(fields[len(fields)-1]) } + if abs, err := filepath.Abs(pos.Filename); err == nil { + // Make the path absolute, so that error messages will be + // prettier (it will be turned back into a relative path + // when printing the error). + pos.Filename = abs + } pos.Filename = p.getOriginalPath(pos.Filename) } err := scanner.Error{ @@ -195,10 +202,11 @@ func Load(config *compileopts.Config, inputPkg string, typeChecker types.Config) Msg: pkg.Error.Err, } if len(pkg.Error.ImportStack) != 0 { - return nil, Error{ + pkgErrors = append(pkgErrors, Error{ ImportStack: pkg.Error.ImportStack, Err: err, - } + }) + continue } return nil, err } @@ -241,6 +249,13 @@ func Load(config *compileopts.Config, inputPkg string, typeChecker types.Config) p.Packages[pkg.ImportPath] = pkg } + if len(pkgErrors) != 0 { + // TODO: use errors.Join in Go 1.20. + return nil, Errors{ + Errs: pkgErrors, + } + } + if config.TestConfig.CompileTestBinary && !strings.HasSuffix(p.sorted[len(p.sorted)-1].ImportPath, ".test") { // Trying to compile a test binary but there are no test files in this // package. diff --git a/main.go b/main.go index e18f12f1fd..66a172133b 100644 --- a/main.go +++ b/main.go @@ -1340,15 +1340,31 @@ func printCompilerError(err error, logln func(...interface{}), wd string) { } } case loader.Errors: - logln("#", err.Pkg.ImportPath) + // Parser errors, typechecking errors, or `go list` errors. + // err.Pkg is nil for `go list` errors. + if err.Pkg != nil { + logln("#", err.Pkg.ImportPath) + } for _, err := range err.Errs { printCompilerError(err, logln, wd) } case loader.Error: - logln(err.Err.Error()) - logln("package", err.ImportStack[0]) - for _, pkgPath := range err.ImportStack[1:] { - logln("\timports", pkgPath) + if err.Err.Pos.Filename != "" { + // Probably a syntax error in a dependency. + printCompilerError(err.Err, logln, wd) + } else { + // Probably an "import cycle not allowed" error. + logln("package", err.ImportStack[0]) + for i := 1; i < len(err.ImportStack); i++ { + pkgPath := err.ImportStack[i] + if i == len(err.ImportStack)-1 { + // last package + logln("\timports", pkgPath+": "+err.Err.Error()) + } else { + // not the last pacakge + logln("\timports", pkgPath) + } + } } case *builder.MultiError: for _, err := range err.Errs { diff --git a/testdata/errors/importcycle/cycle.go b/testdata/errors/importcycle/cycle.go new file mode 100644 index 0000000000..40ecf5e235 --- /dev/null +++ b/testdata/errors/importcycle/cycle.go @@ -0,0 +1,3 @@ +package importcycle + +import _ "github.com/tinygo-org/tinygo/testdata/errors/importcycle" diff --git a/testdata/errors/invaliddep/invaliddep.go b/testdata/errors/invaliddep/invaliddep.go new file mode 100644 index 0000000000..9b0b1c577f --- /dev/null +++ b/testdata/errors/invaliddep/invaliddep.go @@ -0,0 +1 @@ +ppackage // syntax error diff --git a/testdata/errors/loader-importcycle.go b/testdata/errors/loader-importcycle.go new file mode 100644 index 0000000000..4571bdb4de --- /dev/null +++ b/testdata/errors/loader-importcycle.go @@ -0,0 +1,10 @@ +package main + +import _ "github.com/tinygo-org/tinygo/testdata/errors/importcycle" + +func main() { +} + +// ERROR: package command-line-arguments +// ERROR: imports github.com/tinygo-org/tinygo/testdata/errors/importcycle +// ERROR: imports github.com/tinygo-org/tinygo/testdata/errors/importcycle: import cycle not allowed diff --git a/testdata/errors/loader-invaliddep.go b/testdata/errors/loader-invaliddep.go new file mode 100644 index 0000000000..db935d38ae --- /dev/null +++ b/testdata/errors/loader-invaliddep.go @@ -0,0 +1,8 @@ +package main + +import _ "github.com/tinygo-org/tinygo/testdata/errors/invaliddep" + +func main() { +} + +// ERROR: invaliddep/invaliddep.go:1:1: expected 'package', found ppackage diff --git a/testdata/errors/loader-invalidpackage.go b/testdata/errors/loader-invalidpackage.go new file mode 100644 index 0000000000..6d78810474 --- /dev/null +++ b/testdata/errors/loader-invalidpackage.go @@ -0,0 +1,3 @@ +ppackage // syntax error + +// ERROR: loader-invalidpackage.go:1:1: expected 'package', found ppackage diff --git a/testdata/errors/loader-nopackage.go b/testdata/errors/loader-nopackage.go new file mode 100644 index 0000000000..c0087fc0b6 --- /dev/null +++ b/testdata/errors/loader-nopackage.go @@ -0,0 +1,14 @@ +package main + +import ( + _ "github.com/tinygo-org/tinygo/testdata/errors/non-existing-package" + _ "github.com/tinygo-org/tinygo/testdata/errors/non-existing-package-2" +) + +func main() { +} + +// ERROR: loader-nopackage.go:4:2: no required module provides package github.com/tinygo-org/tinygo/testdata/errors/non-existing-package; to add it: +// ERROR: go get github.com/tinygo-org/tinygo/testdata/errors/non-existing-package +// ERROR: loader-nopackage.go:5:2: no required module provides package github.com/tinygo-org/tinygo/testdata/errors/non-existing-package-2; to add it: +// ERROR: go get github.com/tinygo-org/tinygo/testdata/errors/non-existing-package-2 From fb91c7097f4fdfc49cd2819753fb300cb31ce6b5 Mon Sep 17 00:00:00 2001 From: Anatol Pomozov Date: Sat, 13 Jul 2024 09:14:54 -0700 Subject: [PATCH 095/444] Do not stop compilation on compiler warnings Compilers like GCC keep adding new checks that produce new warnings. Sometimes it can be false positives. Do not treat such warnings in binaryen library as errors. tinygo won't be able to provide zero warnings in its dependencies. Closes #4332 --- GNUmakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GNUmakefile b/GNUmakefile index ca8718ca54..3fc7275db0 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -257,7 +257,7 @@ ifneq ($(USE_SYSTEM_BINARYEN),1) binaryen: build/wasm-opt$(EXE) build/wasm-opt$(EXE): mkdir -p build - cd lib/binaryen && cmake -G Ninja . -DBUILD_STATIC_LIB=ON -DBUILD_TESTS=OFF $(BINARYEN_OPTION) && ninja bin/wasm-opt$(EXE) + cd lib/binaryen && cmake -G Ninja . -DBUILD_STATIC_LIB=ON -DBUILD_TESTS=OFF -DENABLE_WERROR=OFF $(BINARYEN_OPTION) && ninja bin/wasm-opt$(EXE) cp lib/binaryen/bin/wasm-opt$(EXE) build/wasm-opt$(EXE) endif From f026422b04573f6eab49fd6bf8156a217efac1f7 Mon Sep 17 00:00:00 2001 From: Tai Groot Date: Sun, 14 Jul 2024 10:54:14 -0700 Subject: [PATCH 096/444] add chromeos 9p support --- main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 66a172133b..9af0b0fc5c 100644 --- a/main.go +++ b/main.go @@ -1077,7 +1077,8 @@ func findFATMounts(options *compileopts.Options) ([]mountPoint, error) { continue } fstype := fields[2] - if fstype != "vfat" { + // chromeos bind mounts use 9p + if !(fstype == "vfat" || fstype == "9p") { continue } fspath := strings.ReplaceAll(fields[1], "\\040", " ") From 87a8aafc4fc84ca0a2b5e68f9e0b425e4fac4878 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Tue, 16 Jul 2024 11:18:46 -0700 Subject: [PATCH 097/444] all: simplify wasm-tools-go dependency - add internal/wasm-tools/go.mod file to depend on wasm-tools-go - copy package cm into src/internal/cm - remove wasm-tools-go "vendor" submodule internal/tools: fix typo go.{mod,sum}, internal/tools: add wit-bindgen-go to tools GNUmakefile: use go run for wit-bindgen-go GNUmakefile: add tools target to go:generate tools binaries in internal/tools GNUmakefile: add .PHONY for lint and spell GNUmakefile, internal/cm: vendor package cm into internal/cm go.{mod,sum}: update wasm-tools-go to v0.1.4 internal/wasi: use internal/cm package remove submodule src/vendor/github.com/ydnar/wasm-tools-go GNUmakefile: add comment documenting what wasi-cm target does go.{mod,sum}: remove toolchain; go mod tidy go.mod: revert to Go 1.19 go.mod: go 1.19 go.{mod,sum}, internal/{tools,wasm-tools}: revert root go.mod file to go1.19 Create a wasm-tools specific module that can require go1.22 for wasm-tools-go. --- .gitmodules | 3 - GNUmakefile | 19 +- internal/tools/tools.go | 7 +- internal/wasm-tools/README.md | 5 + internal/wasm-tools/go.mod | 12 + internal/wasm-tools/go.sum | 25 ++ internal/wasm-tools/tools.go | 11 + loader/goroot.go | 3 +- src/internal/cm/abi.go | 118 +++++++++ src/internal/cm/docs.go | 8 + src/internal/cm/list.go | 48 ++++ src/internal/cm/option.go | 44 ++++ src/internal/cm/resource.go | 21 ++ src/internal/cm/result.go | 107 ++++++++ src/internal/cm/tuple.go | 230 ++++++++++++++++++ src/internal/cm/variant.go | 74 ++++++ .../cli/v0.2.0/environment/environment.wit.go | 2 +- src/internal/wasi/cli/v0.2.0/exit/exit.wit.go | 2 +- .../wasi/cli/v0.2.0/run/run.exports.go | 2 +- src/internal/wasi/cli/v0.2.0/run/run.wit.go | 2 +- .../wasi/cli/v0.2.0/stderr/stderr.wit.go | 2 +- .../wasi/cli/v0.2.0/stdin/stdin.wit.go | 2 +- .../wasi/cli/v0.2.0/stdout/stdout.wit.go | 2 +- .../terminal-input/terminal-input.wit.go | 2 +- .../terminal-output/terminal-output.wit.go | 2 +- .../terminal-stderr/terminal-stderr.wit.go | 2 +- .../terminal-stdin/terminal-stdin.wit.go | 2 +- .../terminal-stdout/terminal-stdout.wit.go | 2 +- .../monotonic-clock/monotonic-clock.wit.go | 2 +- .../v0.2.0/preopens/preopens.wit.go | 2 +- .../wasi/filesystem/v0.2.0/types/abi.go | 2 +- .../wasi/filesystem/v0.2.0/types/types.wit.go | 2 +- .../wasi/io/v0.2.0/error/error.wit.go | 2 +- src/internal/wasi/io/v0.2.0/poll/poll.wit.go | 2 +- .../wasi/io/v0.2.0/streams/streams.wit.go | 2 +- .../random/v0.2.0/insecure/insecure.wit.go | 2 +- .../wasi/random/v0.2.0/random/random.wit.go | 2 +- .../instance-network/instance-network.wit.go | 2 +- .../wasi/sockets/v0.2.0/ip-name-lookup/abi.go | 2 +- .../ip-name-lookup/ip-name-lookup.wit.go | 2 +- .../sockets/v0.2.0/network/network.wit.go | 2 +- .../tcp-create-socket.wit.go | 2 +- src/internal/wasi/sockets/v0.2.0/tcp/abi.go | 2 +- .../wasi/sockets/v0.2.0/tcp/tcp.wit.go | 2 +- .../udp-create-socket.wit.go | 2 +- src/internal/wasi/sockets/v0.2.0/udp/abi.go | 2 +- .../wasi/sockets/v0.2.0/udp/udp.wit.go | 2 +- src/runtime/runtime_tinygowasmp2.go | 4 +- src/syscall/libc_wasip2.go | 4 +- src/vendor/github.com/ydnar/wasm-tools-go | 1 - 50 files changed, 760 insertions(+), 46 deletions(-) create mode 100644 internal/wasm-tools/README.md create mode 100644 internal/wasm-tools/go.mod create mode 100644 internal/wasm-tools/go.sum create mode 100644 internal/wasm-tools/tools.go create mode 100644 src/internal/cm/abi.go create mode 100644 src/internal/cm/docs.go create mode 100644 src/internal/cm/list.go create mode 100644 src/internal/cm/option.go create mode 100644 src/internal/cm/resource.go create mode 100644 src/internal/cm/result.go create mode 100644 src/internal/cm/tuple.go create mode 100644 src/internal/cm/variant.go delete mode 160000 src/vendor/github.com/ydnar/wasm-tools-go diff --git a/.gitmodules b/.gitmodules index edd7b215c1..4a8820e3a6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -42,6 +42,3 @@ [submodule "lib/wasi-cli"] path = lib/wasi-cli url = https://github.com/WebAssembly/wasi-cli -[submodule "src/vendor/github.com/ydnar/wasm-tools-go"] - path = src/vendor/github.com/ydnar/wasm-tools-go - url = https://github.com/ydnar/wasm-tools-go.git diff --git a/GNUmakefile b/GNUmakefile index 3fc7275db0..f2f5e2f53f 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -269,9 +269,16 @@ lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a: cd lib/wasi-libc && $(MAKE) -j4 EXTRA_CFLAGS="-O2 -g -DNDEBUG -mnontrapping-fptoint -msign-ext" MALLOC_IMPL=none CC="$(CLANG)" AR=$(LLVM_AR) NM=$(LLVM_NM) # Generate WASI syscall bindings +WASM_TOOLS_MODULE=github.com/ydnar/wasm-tools-go .PHONY: wasi-syscall -wasi-syscall: - wit-bindgen-go generate -o ./src/internal -p internal --versioned ./lib/wasi-cli/wit +wasi-syscall: wasi-cm + go run -modfile ./internal/wasm-tools/go.mod $(WASM_TOOLS_MODULE)/cmd/wit-bindgen-go generate --versioned -o ./src/internal -p internal --cm internal/cm ./lib/wasi-cli/wit + +# Copy package cm into src/internal/cm +.PHONY: wasi-cm +wasi-cm: + # rm -rf ./src/internal/cm + rsync -rv --delete --exclude '*_test.go' $(shell go list -modfile ./internal/wasm-tools/go.mod -m -f {{.Dir}} $(WASM_TOOLS_MODULE))/cm ./src/internal/ # Check for Node.js used during WASM tests. NODEJS_VERSION := $(word 1,$(subst ., ,$(shell node -v | cut -c 2-))) @@ -946,6 +953,11 @@ release: build/release deb: build/release endif +.PHONY: tools +tools: + go generate -C ./internal/tools -tags tools ./ + +.PHONY: lint lint: go run github.com/mgechev/revive -version # TODO: lint more directories! @@ -954,6 +966,7 @@ lint: # Use 'grep .' to get rid of stray blank line go run github.com/mgechev/revive -config revive.toml compiler/... src/{os,reflect}/*.go | grep -v "should have comment or be unexported" | grep '.' | awk '{print}; END {exit NR>0}' +.PHONY: spell spell: - # Check for typos in comments. Skip git submodules etc. + # Check for typos in comments. Skip git submodules etc. go run github.com/client9/misspell/cmd/misspell -i 'ackward,devided,extint,inbetween,programmmer,rela' $$( find . -depth 1 -type d | egrep -w -v 'lib|llvm|src/net' ) diff --git a/internal/tools/tools.go b/internal/tools/tools.go index cda6891015..d592921a54 100644 --- a/internal/tools/tools.go +++ b/internal/tools/tools.go @@ -1,10 +1,13 @@ //go:build tools -// Install linter versions specified in go.mod -// See https://marcofranssen.nl/manage-go-tools-via-go-modules for idom +// Install tools specified in go.mod. +// See https://marcofranssen.nl/manage-go-tools-via-go-modules for idiom. package tools import ( _ "github.com/client9/misspell" _ "github.com/mgechev/revive" ) + +//go:generate go install github.com/client9/misspell/cmd/misspell +//go:generate go install github.com/mgechev/revive diff --git a/internal/wasm-tools/README.md b/internal/wasm-tools/README.md new file mode 100644 index 0000000000..5e3a94ec6e --- /dev/null +++ b/internal/wasm-tools/README.md @@ -0,0 +1,5 @@ +# wasm-tools directory + +This directory has a separate `go.mod` file because the `wasm-tools-go` module requires Go 1.22, while TinyGo itself supports Go 1.19. + +When the minimum Go version for TinyGo is 1.22, this directory can be folded into `internal/tools` and the `go.mod` and `go.sum` files deleted. diff --git a/internal/wasm-tools/go.mod b/internal/wasm-tools/go.mod new file mode 100644 index 0000000000..8a0e49351b --- /dev/null +++ b/internal/wasm-tools/go.mod @@ -0,0 +1,12 @@ +module github.com/tinygo-org/tinygo/internal/tools + +go 1.22.4 + +require github.com/ydnar/wasm-tools-go v0.1.4 + +require ( + github.com/coreos/go-semver v0.3.1 // indirect + github.com/urfave/cli/v3 v3.0.0-alpha9 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + golang.org/x/mod v0.19.0 // indirect +) diff --git a/internal/wasm-tools/go.sum b/internal/wasm-tools/go.sum new file mode 100644 index 0000000000..b2d0b1e3ac --- /dev/null +++ b/internal/wasm-tools/go.sum @@ -0,0 +1,25 @@ +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +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/urfave/cli/v3 v3.0.0-alpha9 h1:P0RMy5fQm1AslQS+XCmy9UknDXctOmG/q/FZkUFnJSo= +github.com/urfave/cli/v3 v3.0.0-alpha9/go.mod h1:0kK/RUFHyh+yIKSfWxwheGndfnrvYSmYFVeKCh03ZUc= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/ydnar/wasm-tools-go v0.1.4 h1:+25WqBj0AhLx8OFvZvrs7bQO6L3WtQ7t6JzQEYsXQb8= +github.com/ydnar/wasm-tools-go v0.1.4/go.mod h1:lQfv2Tde3tRgZDSYriro0EmdSHzP1mrHPMmYNahSS/g= +golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= +golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/wasm-tools/tools.go b/internal/wasm-tools/tools.go new file mode 100644 index 0000000000..c8eb42fde1 --- /dev/null +++ b/internal/wasm-tools/tools.go @@ -0,0 +1,11 @@ +//go:build tools + +// Install tools specified in go.mod. +// See https://marcofranssen.nl/manage-go-tools-via-go-modules for idiom. +package tools + +import ( + _ "github.com/ydnar/wasm-tools-go/cmd/wit-bindgen-go" +) + +//go:generate go install github.com/ydnar/wasm-tools-go/cmd/wit-bindgen-go diff --git a/loader/goroot.go b/loader/goroot.go index 72b2f33355..8661bf67e0 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -238,6 +238,7 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { "internal/": true, "internal/binary/": false, "internal/bytealg/": false, + "internal/cm/": false, "internal/fuzz/": false, "internal/reflectlite/": false, "internal/task/": false, @@ -251,8 +252,6 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { "runtime/": false, "sync/": true, "testing/": true, - "vendor/": true, - "vendor/github.com/": false, } if goMinor >= 19 { diff --git a/src/internal/cm/abi.go b/src/internal/cm/abi.go new file mode 100644 index 0000000000..91ca1be328 --- /dev/null +++ b/src/internal/cm/abi.go @@ -0,0 +1,118 @@ +package cm + +import "unsafe" + +// Reinterpret reinterprets the bits of type From into type T. +// Will panic if the size of From is smaller than the size of To. +func Reinterpret[T, From any](from From) (to T) { + if unsafe.Sizeof(to) > unsafe.Sizeof(from) { + panic("reinterpret: size of to > from") + } + return *(*T)(unsafe.Pointer(&from)) +} + +// LowerString lowers a [string] into a pair of Core WebAssembly types. +// +// [string]: https://pkg.go.dev/builtin#string +func LowerString[S ~string](s S) (*byte, uint32) { + return unsafe.StringData(string(s)), uint32(len(s)) +} + +// LiftString lifts Core WebAssembly types into a [string]. +func LiftString[T ~string, Data unsafe.Pointer | uintptr | *uint8, Len uint | uintptr | uint32 | uint64](data Data, len Len) T { + return T(unsafe.String((*uint8)(unsafe.Pointer(data)), int(len))) +} + +// LowerList lowers a [List] into a pair of Core WebAssembly types. +func LowerList[L ~struct{ list[T] }, T any](list L) (*T, uint32) { + l := (*List[T])(unsafe.Pointer(&list)) + return l.data, uint32(l.len) +} + +// LiftList lifts Core WebAssembly types into a [List]. +func LiftList[L List[T], T any, Data unsafe.Pointer | uintptr | *T, Len uint | uintptr | uint32 | uint64](data Data, len Len) L { + return L(NewList((*T)(unsafe.Pointer(data)), uint(len))) +} + +// BoolToU32 converts a value whose underlying type is [bool] into a [uint32]. +// Used to lower a [bool] into a Core WebAssembly i32 as specified in the [Canonical ABI]. +// +// [bool]: https://pkg.go.dev/builtin#bool +// [uint32]: https://pkg.go.dev/builtin#uint32 +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +func BoolToU32[B ~bool](v B) uint32 { return uint32(*(*uint8)(unsafe.Pointer(&v))) } + +// U32ToBool converts a [uint32] into a [bool]. +// Used to lift a Core WebAssembly i32 into a [bool] as specified in the [Canonical ABI]. +// +// [uint32]: https://pkg.go.dev/builtin#uint32 +// [bool]: https://pkg.go.dev/builtin#bool +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +func U32ToBool(v uint32) bool { tmp := uint8(v); return *(*bool)(unsafe.Pointer(&tmp)) } + +// F32ToU32 maps the bits of a [float32] into a [uint32]. +// Used to lower a [float32] into a Core WebAssembly i32 as specified in the [Canonical ABI]. +// +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +// [float32]: https://pkg.go.dev/builtin#float32 +// [uint32]: https://pkg.go.dev/builtin#uint32 +func F32ToU32(v float32) uint32 { return *(*uint32)(unsafe.Pointer(&v)) } + +// U32ToF32 maps the bits of a [uint32] into a [float32]. +// Used to lift a Core WebAssembly i32 into a [float32] as specified in the [Canonical ABI]. +// +// [uint32]: https://pkg.go.dev/builtin#uint32 +// [float32]: https://pkg.go.dev/builtin#float32 +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +func U32ToF32(v uint32) float32 { return *(*float32)(unsafe.Pointer(&v)) } + +// F64ToU64 maps the bits of a [float64] into a [uint64]. +// Used to lower a [float64] into a Core WebAssembly i64 as specified in the [Canonical ABI]. +// +// [float64]: https://pkg.go.dev/builtin#float64 +// [uint64]: https://pkg.go.dev/builtin#uint64 +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +// +// [uint32]: https://pkg.go.dev/builtin#uint32 +func F64ToU64(v float64) uint64 { return *(*uint64)(unsafe.Pointer(&v)) } + +// U64ToF64 maps the bits of a [uint64] into a [float64]. +// Used to lift a Core WebAssembly i64 into a [float64] as specified in the [Canonical ABI]. +// +// [uint64]: https://pkg.go.dev/builtin#uint64 +// [float64]: https://pkg.go.dev/builtin#float64 +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +func U64ToF64(v uint64) float64 { return *(*float64)(unsafe.Pointer(&v)) } + +// PointerToU32 converts a pointer of type *T into a [uint32]. +// Used to lower a pointer into a Core WebAssembly i32 as specified in the [Canonical ABI]. +// +// [uint32]: https://pkg.go.dev/builtin#uint32 +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +func PointerToU32[T any](v *T) uint32 { return uint32(uintptr(unsafe.Pointer(v))) } + +// U32ToPointer converts a [uint32] into a pointer of type *T. +// Used to lift a Core WebAssembly i32 into a pointer as specified in the [Canonical ABI]. +// +// [uint32]: https://pkg.go.dev/builtin#uint32 +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +func U32ToPointer[T any](v uint32) *T { return (*T)(unsafePointer(uintptr(v))) } + +// PointerToU64 converts a pointer of type *T into a [uint64]. +// Used to lower a pointer into a Core WebAssembly i64 as specified in the [Canonical ABI]. +// +// [uint64]: https://pkg.go.dev/builtin#uint64 +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +func PointerToU64[T any](v *T) uint64 { return uint64(uintptr(unsafe.Pointer(v))) } + +// U64ToPointer converts a [uint64] into a pointer of type *T. +// Used to lift a Core WebAssembly i64 into a pointer as specified in the [Canonical ABI]. +// +// [uint64]: https://pkg.go.dev/builtin#uint64 +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +func U64ToPointer[T any](v uint64) *T { return (*T)(unsafePointer(uintptr(v))) } + +// Appease vet, see https://github.com/golang/go/issues/58625 +func unsafePointer(p uintptr) unsafe.Pointer { + return *(*unsafe.Pointer)(unsafe.Pointer(&p)) +} diff --git a/src/internal/cm/docs.go b/src/internal/cm/docs.go new file mode 100644 index 0000000000..5fc48fb759 --- /dev/null +++ b/src/internal/cm/docs.go @@ -0,0 +1,8 @@ +// Package cm contains types and functions for interfacing with the WebAssembly Component Model. +// +// The types in this package (such as [List], [Option], [Result], and [Variant]) are designed to match the memory layout +// of [Component Model] types as specified in the [Canonical ABI]. +// +// [Component Model]: https://component-model.bytecodealliance.org/introduction.html +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#alignment +package cm diff --git a/src/internal/cm/list.go b/src/internal/cm/list.go new file mode 100644 index 0000000000..e170efbedd --- /dev/null +++ b/src/internal/cm/list.go @@ -0,0 +1,48 @@ +package cm + +import "unsafe" + +// List represents a Component Model list. +// The binary representation of list is similar to a Go slice minus the cap field. +type List[T any] struct{ list[T] } + +// NewList returns a List[T] from data and len. +func NewList[T any](data *T, len uint) List[T] { + return List[T]{ + list[T]{ + data: data, + len: len, + }, + } +} + +// ToList returns a List[T] equivalent to the Go slice s. +// The underlying slice data is not copied, and the resulting List points at the +// same array storage as the slice. +func ToList[S ~[]T, T any](s S) List[T] { + return NewList[T](unsafe.SliceData([]T(s)), uint(len(s))) +} + +// list represents the internal representation of a Component Model list. +// It is intended to be embedded in a [List], so embedding types maintain +// the methods defined on this type. +type list[T any] struct { + data *T + len uint +} + +// Slice returns a Go slice representing the List. +func (l list[T]) Slice() []T { + return unsafe.Slice(l.data, l.len) +} + +// Data returns the data pointer for the list. +func (l list[T]) Data() *T { + return l.data +} + +// Len returns the length of the list. +// TODO: should this return an int instead of a uint? +func (l list[T]) Len() uint { + return l.len +} diff --git a/src/internal/cm/option.go b/src/internal/cm/option.go new file mode 100644 index 0000000000..edc288b4c0 --- /dev/null +++ b/src/internal/cm/option.go @@ -0,0 +1,44 @@ +package cm + +// Option represents a Component Model [option] type. +// +// [option]: https://component-model.bytecodealliance.org/design/wit.html#options +type Option[T any] struct{ option[T] } + +// None returns an [Option] representing the none case, +// equivalent to the zero value. +func None[T any]() Option[T] { + return Option[T]{} +} + +// Some returns an [Option] representing the some case. +func Some[T any](v T) Option[T] { + return Option[T]{ + option[T]{ + isSome: true, + some: v, + }, + } +} + +// option represents the internal representation of a Component Model option type. +// The first byte is a bool representing none or some, +// followed by storage for the associated type T. +type option[T any] struct { + isSome bool + some T +} + +// None returns true if o represents the none case. +func (o *option[T]) None() bool { + return !o.isSome +} + +// Some returns a non-nil *T if o represents the some case, +// or nil if o represents the none case. +func (o *option[T]) Some() *T { + if o.isSome { + return &o.some + } + return nil +} diff --git a/src/internal/cm/resource.go b/src/internal/cm/resource.go new file mode 100644 index 0000000000..830d76591a --- /dev/null +++ b/src/internal/cm/resource.go @@ -0,0 +1,21 @@ +package cm + +// Resource represents an opaque Component Model [resource handle]. +// It is represented in the [Canonical ABI] as an 32-bit integer. +// +// [resource handle]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#handle-types +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +type Resource uint32 + +// Rep represents a Component Model [resource rep], the core representation type of a resource. +// It is represented in the [Canonical ABI] as an 32-bit integer. +// +// [resource rep]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#canon-resourcerep +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +type Rep uint32 + +// ResourceNone is a sentinel value indicating a null or uninitialized resource. +// This is a reserved value specified in the [Canonical ABI runtime state]. +// +// [Canonical ABI runtime state]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#runtime-state +const ResourceNone = 0 diff --git a/src/internal/cm/result.go b/src/internal/cm/result.go new file mode 100644 index 0000000000..d21275612a --- /dev/null +++ b/src/internal/cm/result.go @@ -0,0 +1,107 @@ +package cm + +import "unsafe" + +const ( + // ResultOK represents the OK case of a result. + ResultOK = false + + // ResultErr represents the error case of a result. + ResultErr = true +) + +// BoolResult represents a result with no OK or error type. +// False represents the OK case and true represents the error case. +type BoolResult bool + +// Result represents a result sized to hold the Shape type. +// The size of the Shape type must be greater than or equal to the size of OK and Err types. +// For results with two zero-length types, use [BoolResult]. +type Result[Shape, OK, Err any] struct{ result[Shape, OK, Err] } + +// result represents the internal representation of a Component Model result type. +type result[Shape, OK, Err any] struct { + isErr bool + _ [0]OK + _ [0]Err + data Shape // [unsafe.Sizeof(*(*Shape)(unsafe.Pointer(nil)))]byte +} + +// IsOK returns true if r represents the OK case. +func (r *result[Shape, OK, Err]) IsOK() bool { + r.validate() + return !r.isErr +} + +// IsErr returns true if r represents the error case. +func (r *result[Shape, OK, Err]) IsErr() bool { + r.validate() + return r.isErr +} + +// OK returns a non-nil *OK pointer if r represents the OK case. +// If r represents an error, then it returns nil. +func (r *result[Shape, OK, Err]) OK() *OK { + r.validate() + if r.isErr { + return nil + } + return (*OK)(unsafe.Pointer(&r.data)) +} + +// Err returns a non-nil *Err pointer if r represents the error case. +// If r represents the OK case, then it returns nil. +func (r *result[Shape, OK, Err]) Err() *Err { + r.validate() + if !r.isErr { + return nil + } + return (*Err)(unsafe.Pointer(&r.data)) +} + +// This function is sized so it can be inlined and optimized away. +func (r *result[Shape, OK, Err]) validate() { + var shape Shape + var ok OK + var err Err + + // Check if size of Shape is greater than both OK and Err + if unsafe.Sizeof(shape) > unsafe.Sizeof(ok) && unsafe.Sizeof(shape) > unsafe.Sizeof(err) { + panic("result: size of data type > OK and Err types") + } + + // Check if size of OK is greater than Shape + if unsafe.Sizeof(ok) > unsafe.Sizeof(shape) { + panic("result: size of OK type > data type") + } + + // Check if size of Err is greater than Shape + if unsafe.Sizeof(err) > unsafe.Sizeof(shape) { + panic("result: size of Err type > data type") + } + + // Check if Shape is zero-sized, but size of result != 1 + if unsafe.Sizeof(shape) == 0 && unsafe.Sizeof(*r) != 1 { + panic("result: size of data type == 0, but result size != 1") + } +} + +// OK returns an OK result with shape Shape and type OK and Err. +// Pass Result[OK, OK, Err] or Result[Err, OK, Err] as the first type argument. +func OK[R ~struct{ result[Shape, OK, Err] }, Shape, OK, Err any](ok OK) R { + var r struct{ result[Shape, OK, Err] } + r.validate() + r.isErr = ResultOK + *((*OK)(unsafe.Pointer(&r.data))) = ok + return R(r) +} + +// Err returns an error result with shape Shape and type OK and Err. +// Pass Result[OK, OK, Err] or Result[Err, OK, Err] as the first type argument. +func Err[R ~struct{ result[Shape, OK, Err] }, Shape, OK, Err any](err Err) R { + var r struct{ result[Shape, OK, Err] } + r.validate() + r.isErr = ResultErr + *((*Err)(unsafe.Pointer(&r.data))) = err + return R(r) +} diff --git a/src/internal/cm/tuple.go b/src/internal/cm/tuple.go new file mode 100644 index 0000000000..7b0e535c02 --- /dev/null +++ b/src/internal/cm/tuple.go @@ -0,0 +1,230 @@ +package cm + +// Tuple represents a [Component Model tuple] with 2 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple[T0, T1 any] struct { + F0 T0 + F1 T1 +} + +// Tuple3 represents a [Component Model tuple] with 3 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple3[T0, T1, T2 any] struct { + F0 T0 + F1 T1 + F2 T2 +} + +// Tuple4 represents a [Component Model tuple] with 4 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple4[T0, T1, T2, T3 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 +} + +// Tuple5 represents a [Component Model tuple] with 5 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple5[T0, T1, T2, T3, T4 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 +} + +// Tuple6 represents a [Component Model tuple] with 6 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple6[T0, T1, T2, T3, T4, T5 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 +} + +// Tuple7 represents a [Component Model tuple] with 7 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple7[T0, T1, T2, T3, T4, T5, T6 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 +} + +// Tuple8 represents a [Component Model tuple] with 8 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple8[T0, T1, T2, T3, T4, T5, T6, T7 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 +} + +// Tuple9 represents a [Component Model tuple] with 9 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple9[T0, T1, T2, T3, T4, T5, T6, T7, T8 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 +} + +// Tuple10 represents a [Component Model tuple] with 10 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple10[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 +} + +// Tuple11 represents a [Component Model tuple] with 11 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple11[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 +} + +// Tuple12 represents a [Component Model tuple] with 12 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple12[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 + F11 T11 +} + +// Tuple13 represents a [Component Model tuple] with 13 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple13[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 + F11 T11 + F12 T12 +} + +// Tuple14 represents a [Component Model tuple] with 14 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple14[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 + F11 T11 + F12 T12 + F13 T13 +} + +// Tuple15 represents a [Component Model tuple] with 15 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple15[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 + F11 T11 + F12 T12 + F13 T13 + F14 T14 +} + +// Tuple16 represents a [Component Model tuple] with 16 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple16[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any] struct { + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 + F11 T11 + F12 T12 + F13 T13 + F14 T14 + F15 T15 +} + +// MaxTuple specifies the maximum number of fields in a Tuple* type, currently [Tuple16]. +// See https://github.com/WebAssembly/component-model/issues/373 for more information. +const MaxTuple = 16 diff --git a/src/internal/cm/variant.go b/src/internal/cm/variant.go new file mode 100644 index 0000000000..5ae5cf381c --- /dev/null +++ b/src/internal/cm/variant.go @@ -0,0 +1,74 @@ +package cm + +import "unsafe" + +// Discriminant is the set of types that can represent the tag or discriminator of a variant. +// Use bool for 2-case variant types, result, or option types, uint8 where there are 256 or +// fewer cases, uint16 for up to 65,536 cases, or uint32 for anything greater. +type Discriminant interface { + bool | uint8 | uint16 | uint32 +} + +// Variant represents a loosely-typed Component Model variant. +// Shape and Align must be non-zero sized types. To create a variant with no associated +// types, use an enum. +type Variant[Tag Discriminant, Shape, Align any] struct{ variant[Tag, Shape, Align] } + +// NewVariant returns a [Variant] with tag of type Disc, storage and GC shape of type Shape, +// aligned to type Align, with a value of type T. +func NewVariant[Tag Discriminant, Shape, Align any, T any](tag Tag, data T) Variant[Tag, Shape, Align] { + validateVariant[Tag, Shape, Align, T]() + var v Variant[Tag, Shape, Align] + v.tag = tag + *(*T)(unsafe.Pointer(&v.data)) = data + return v +} + +// New returns a [Variant] with tag of type Disc, storage and GC shape of type Shape, +// aligned to type Align, with a value of type T. +func New[V ~struct{ variant[Tag, Shape, Align] }, Tag Discriminant, Shape, Align any, T any](tag Tag, data T) V { + validateVariant[Tag, Shape, Align, T]() + var v variant[Tag, Shape, Align] + v.tag = tag + *(*T)(unsafe.Pointer(&v.data)) = data + return *(*V)(unsafe.Pointer(&v)) +} + +// Case returns a non-nil *T if the [Variant] case is equal to tag, otherwise it returns nil. +func Case[T any, V ~struct{ variant[Tag, Shape, Align] }, Tag Discriminant, Shape, Align any](v *V, tag Tag) *T { + validateVariant[Tag, Shape, Align, T]() + v2 := (*variant[Tag, Shape, Align])(unsafe.Pointer(v)) + if v2.tag == tag { + return (*T)(unsafe.Pointer(&v2.data)) + } + return nil +} + +// variant is the internal representation of a Component Model variant. +// Shape and Align must be non-zero sized types. +type variant[Tag Discriminant, Shape, Align any] struct { + tag Tag + _ [0]Align + data Shape // [unsafe.Sizeof(*(*Shape)(unsafe.Pointer(nil)))]byte +} + +// Tag returns the tag (discriminant) of variant v. +func (v *variant[Tag, Shape, Align]) Tag() Tag { + return v.tag +} + +// This function is sized so it can be inlined and optimized away. +func validateVariant[Disc Discriminant, Shape, Align any, T any]() { + var v variant[Disc, Shape, Align] + var t T + + // Check if size of T is greater than Shape + if unsafe.Sizeof(t) > unsafe.Sizeof(v.data) { + panic("variant: size of requested type > data type") + } + + // Check if Shape is zero-sized, but size of result != 1 + if unsafe.Sizeof(v.data) == 0 && unsafe.Sizeof(v) != 1 { + panic("variant: size of data type == 0, but variant size != 1") + } +} diff --git a/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go b/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go index b75e74b185..81fec996ad 100644 --- a/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go +++ b/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go @@ -6,7 +6,7 @@ package environment import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" ) // GetEnvironment represents the imported function "get-environment". diff --git a/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go b/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go index d10b734c6d..24aa1b5610 100644 --- a/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go +++ b/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go @@ -6,7 +6,7 @@ package exit import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" ) // Exit represents the imported function "exit". diff --git a/src/internal/wasi/cli/v0.2.0/run/run.exports.go b/src/internal/wasi/cli/v0.2.0/run/run.exports.go index 3d0edcc802..8dfaedec22 100644 --- a/src/internal/wasi/cli/v0.2.0/run/run.exports.go +++ b/src/internal/wasi/cli/v0.2.0/run/run.exports.go @@ -5,7 +5,7 @@ package run import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" ) // Exports represents the caller-defined exports from "wasi:cli/run@0.2.0". diff --git a/src/internal/wasi/cli/v0.2.0/run/run.wit.go b/src/internal/wasi/cli/v0.2.0/run/run.wit.go index 47e00180c4..22e7fc4321 100644 --- a/src/internal/wasi/cli/v0.2.0/run/run.wit.go +++ b/src/internal/wasi/cli/v0.2.0/run/run.wit.go @@ -6,7 +6,7 @@ package run import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" ) //go:wasmexport wasi:cli/run@0.2.0#run diff --git a/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go index fd2b7517c2..b57323715b 100644 --- a/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go +++ b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go @@ -6,7 +6,7 @@ package stderr import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" "internal/wasi/io/v0.2.0/streams" ) diff --git a/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go index abe35cbbb0..664ca14bef 100644 --- a/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go +++ b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go @@ -6,7 +6,7 @@ package stdin import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" "internal/wasi/io/v0.2.0/streams" ) diff --git a/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go index 2f56b19ec4..6b2b28aac5 100644 --- a/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go +++ b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go @@ -6,7 +6,7 @@ package stdout import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" "internal/wasi/io/v0.2.0/streams" ) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go index 4788644280..318a91ac79 100644 --- a/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go +++ b/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go @@ -12,7 +12,7 @@ package terminalinput import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" ) // TerminalInput represents the imported resource "wasi:cli/terminal-input@0.2.0#terminal-input". diff --git a/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go index 759348b85e..6e56faf408 100644 --- a/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go +++ b/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go @@ -12,7 +12,7 @@ package terminaloutput import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" ) // TerminalOutput represents the imported resource "wasi:cli/terminal-output@0.2.0#terminal-output". diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go index db720e1511..d9e32838c0 100644 --- a/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go +++ b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go @@ -9,7 +9,7 @@ package terminalstderr import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" terminaloutput "internal/wasi/cli/v0.2.0/terminal-output" ) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go index 091d8e8c5f..834864f8ea 100644 --- a/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go +++ b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go @@ -9,7 +9,7 @@ package terminalstdin import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" terminalinput "internal/wasi/cli/v0.2.0/terminal-input" ) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go index d0d9bfe485..7dbb2cab1b 100644 --- a/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go +++ b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go @@ -9,7 +9,7 @@ package terminalstdout import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" terminaloutput "internal/wasi/cli/v0.2.0/terminal-output" ) diff --git a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go index 4c68033654..1b1ae5358d 100644 --- a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go +++ b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go @@ -17,7 +17,7 @@ package monotonicclock import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" "internal/wasi/io/v0.2.0/poll" ) diff --git a/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go index d0b71bf110..4638a9a39d 100644 --- a/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go +++ b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go @@ -6,7 +6,7 @@ package preopens import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" "internal/wasi/filesystem/v0.2.0/types" ) diff --git a/src/internal/wasi/filesystem/v0.2.0/types/abi.go b/src/internal/wasi/filesystem/v0.2.0/types/abi.go index 41f6848ecb..136fb06c2f 100644 --- a/src/internal/wasi/filesystem/v0.2.0/types/abi.go +++ b/src/internal/wasi/filesystem/v0.2.0/types/abi.go @@ -5,7 +5,7 @@ package types import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" wallclock "internal/wasi/clocks/v0.2.0/wall-clock" "unsafe" ) diff --git a/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go b/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go index 226edabd78..f1f139ed13 100644 --- a/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go +++ b/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go @@ -31,7 +31,7 @@ package types import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" wallclock "internal/wasi/clocks/v0.2.0/wall-clock" ioerror "internal/wasi/io/v0.2.0/error" "internal/wasi/io/v0.2.0/streams" diff --git a/src/internal/wasi/io/v0.2.0/error/error.wit.go b/src/internal/wasi/io/v0.2.0/error/error.wit.go index f962f22ea3..aae635062a 100644 --- a/src/internal/wasi/io/v0.2.0/error/error.wit.go +++ b/src/internal/wasi/io/v0.2.0/error/error.wit.go @@ -6,7 +6,7 @@ package ioerror import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" ) // Error represents the imported resource "wasi:io/error@0.2.0#error". diff --git a/src/internal/wasi/io/v0.2.0/poll/poll.wit.go b/src/internal/wasi/io/v0.2.0/poll/poll.wit.go index 0c362c0791..274a8e8a4b 100644 --- a/src/internal/wasi/io/v0.2.0/poll/poll.wit.go +++ b/src/internal/wasi/io/v0.2.0/poll/poll.wit.go @@ -9,7 +9,7 @@ package poll import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" ) // Pollable represents the imported resource "wasi:io/poll@0.2.0#pollable". diff --git a/src/internal/wasi/io/v0.2.0/streams/streams.wit.go b/src/internal/wasi/io/v0.2.0/streams/streams.wit.go index f3c1e3c355..317fdc8820 100644 --- a/src/internal/wasi/io/v0.2.0/streams/streams.wit.go +++ b/src/internal/wasi/io/v0.2.0/streams/streams.wit.go @@ -12,7 +12,7 @@ package streams import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" ioerror "internal/wasi/io/v0.2.0/error" "internal/wasi/io/v0.2.0/poll" ) diff --git a/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go b/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go index fbea789b5d..7b38f0118d 100644 --- a/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go +++ b/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go @@ -11,7 +11,7 @@ package insecure import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" ) // GetInsecureRandomBytes represents the imported function "get-insecure-random-bytes". diff --git a/src/internal/wasi/random/v0.2.0/random/random.wit.go b/src/internal/wasi/random/v0.2.0/random/random.wit.go index f60d468e19..05b7556236 100644 --- a/src/internal/wasi/random/v0.2.0/random/random.wit.go +++ b/src/internal/wasi/random/v0.2.0/random/random.wit.go @@ -11,7 +11,7 @@ package random import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" ) // GetRandomBytes represents the imported function "get-random-bytes". diff --git a/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go b/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go index fbd9dc8ad4..e7d5f0ea7e 100644 --- a/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go @@ -8,7 +8,7 @@ package instancenetwork import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" "internal/wasi/sockets/v0.2.0/network" ) diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go index 065c935e16..41714d9ec8 100644 --- a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go +++ b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go @@ -5,7 +5,7 @@ package ipnamelookup import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" "internal/wasi/sockets/v0.2.0/network" "unsafe" ) diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go index 34ca3a6a0b..dcd1fd9d95 100644 --- a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go @@ -6,7 +6,7 @@ package ipnamelookup import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" "internal/wasi/io/v0.2.0/poll" "internal/wasi/sockets/v0.2.0/network" ) diff --git a/src/internal/wasi/sockets/v0.2.0/network/network.wit.go b/src/internal/wasi/sockets/v0.2.0/network/network.wit.go index 7108f6d97b..5f2403c433 100644 --- a/src/internal/wasi/sockets/v0.2.0/network/network.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/network/network.wit.go @@ -6,7 +6,7 @@ package network import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" ) // Network represents the imported resource "wasi:sockets/network@0.2.0#network". diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go index 06de81f964..20478aaaeb 100644 --- a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go @@ -6,7 +6,7 @@ package tcpcreatesocket import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" "internal/wasi/sockets/v0.2.0/network" "internal/wasi/sockets/v0.2.0/tcp" ) diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/abi.go b/src/internal/wasi/sockets/v0.2.0/tcp/abi.go index 39ccd2f62c..f986948e37 100644 --- a/src/internal/wasi/sockets/v0.2.0/tcp/abi.go +++ b/src/internal/wasi/sockets/v0.2.0/tcp/abi.go @@ -5,7 +5,7 @@ package tcp import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" "internal/wasi/io/v0.2.0/streams" "internal/wasi/sockets/v0.2.0/network" "unsafe" diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go index c306afef94..e278713101 100644 --- a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go @@ -6,7 +6,7 @@ package tcp import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" monotonicclock "internal/wasi/clocks/v0.2.0/monotonic-clock" "internal/wasi/io/v0.2.0/poll" "internal/wasi/io/v0.2.0/streams" diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go index b30bafaad4..c06bc96c57 100644 --- a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go @@ -6,7 +6,7 @@ package udpcreatesocket import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" "internal/wasi/sockets/v0.2.0/network" "internal/wasi/sockets/v0.2.0/udp" ) diff --git a/src/internal/wasi/sockets/v0.2.0/udp/abi.go b/src/internal/wasi/sockets/v0.2.0/udp/abi.go index 47954d8724..3783acdac3 100644 --- a/src/internal/wasi/sockets/v0.2.0/udp/abi.go +++ b/src/internal/wasi/sockets/v0.2.0/udp/abi.go @@ -5,7 +5,7 @@ package udp import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" "internal/wasi/sockets/v0.2.0/network" "unsafe" ) diff --git a/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go b/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go index 0757398769..41b8491c0f 100644 --- a/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go @@ -6,7 +6,7 @@ package udp import ( - "github.com/ydnar/wasm-tools-go/cm" + "internal/cm" "internal/wasi/io/v0.2.0/poll" "internal/wasi/sockets/v0.2.0/network" ) diff --git a/src/runtime/runtime_tinygowasmp2.go b/src/runtime/runtime_tinygowasmp2.go index 96d7db0ff2..eb3c507fd2 100644 --- a/src/runtime/runtime_tinygowasmp2.go +++ b/src/runtime/runtime_tinygowasmp2.go @@ -3,13 +3,13 @@ package runtime import ( + "internal/cm" + exit "internal/wasi/cli/v0.2.0/exit" stdout "internal/wasi/cli/v0.2.0/stdout" monotonicclock "internal/wasi/clocks/v0.2.0/monotonic-clock" wallclock "internal/wasi/clocks/v0.2.0/wall-clock" random "internal/wasi/random/v0.2.0/random" - - "github.com/ydnar/wasm-tools-go/cm" ) const putcharBufferSize = 120 diff --git a/src/syscall/libc_wasip2.go b/src/syscall/libc_wasip2.go index ea3d3327da..1e985c1da9 100644 --- a/src/syscall/libc_wasip2.go +++ b/src/syscall/libc_wasip2.go @@ -7,6 +7,8 @@ package syscall import ( "unsafe" + "internal/cm" + "internal/wasi/cli/v0.2.0/environment" "internal/wasi/cli/v0.2.0/stderr" "internal/wasi/cli/v0.2.0/stdin" @@ -17,8 +19,6 @@ import ( ioerror "internal/wasi/io/v0.2.0/error" "internal/wasi/io/v0.2.0/streams" "internal/wasi/random/v0.2.0/random" - - "github.com/ydnar/wasm-tools-go/cm" ) func goString(cstr *byte) string { diff --git a/src/vendor/github.com/ydnar/wasm-tools-go b/src/vendor/github.com/ydnar/wasm-tools-go deleted file mode 160000 index 49f7d9208e..0000000000 --- a/src/vendor/github.com/ydnar/wasm-tools-go +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 49f7d9208ece21e46a1232189d664d649524b8cc From 2920492e0a11fb64080ea65489ca800d346d3ae6 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 17 Jul 2024 18:46:07 +0200 Subject: [PATCH 098/444] syscall: remove some dead code Not sure why it's here but it's not referenced anywhere. --- src/syscall/syscall.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/syscall/syscall.go b/src/syscall/syscall.go index dd36800c9a..f22289c5ad 100644 --- a/src/syscall/syscall.go +++ b/src/syscall/syscall.go @@ -2,7 +2,6 @@ package syscall import ( "errors" - "sync/atomic" ) const ( @@ -18,11 +17,6 @@ type Rlimit struct { Max uint64 } -// origRlimitNofile, if not {0, 0}, is the original soft RLIMIT_NOFILE. -// When we can assume that we are bootstrapping with Go 1.19, -// this can be atomic.Pointer[Rlimit]. -var origRlimitNofile atomic.Value // of Rlimit - func Setrlimit(resource int, rlim *Rlimit) error { return errors.New("Setrlimit not implemented") } From 208eb1a817d6f4c2724f92bc9be68ec020a43784 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sat, 20 Jul 2024 11:29:45 +0200 Subject: [PATCH 099/444] fix: set ubuntu version to 24.04 to try to address #4347 Signed-off-by: deadprogram --- .github/workflows/sizediff.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sizediff.yml b/.github/workflows/sizediff.yml index 5dac15409e..78535016ee 100644 --- a/.github/workflows/sizediff.yml +++ b/.github/workflows/sizediff.yml @@ -9,7 +9,7 @@ concurrency: jobs: sizediff: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 permissions: pull-requests: write steps: From e409950492340d0e74db681de25f4a2a0dc84b37 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 12 Jul 2024 14:30:15 +0200 Subject: [PATCH 100/444] main: refactor error messages to use FileCheck-like patterns Match the error messages in testdata/errors/*.go like LLVM FileCheck, which includes regular expressions. I believe this is much more flexible, and LLVM uses it in nearly all of their tests so it must work quite well for compilers. --- errors_test.go | 40 +++++++++++++++++++++------- testdata/errors/loader-invaliddep.go | 2 +- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/errors_test.go b/errors_test.go index 1ee9a0e18f..49d7b25732 100644 --- a/errors_test.go +++ b/errors_test.go @@ -5,7 +5,7 @@ import ( "fmt" "os" "path/filepath" - "runtime" + "regexp" "strings" "testing" "time" @@ -62,20 +62,40 @@ func testErrorMessages(t *testing.T, filename string) { actual := strings.TrimRight(buf.String(), "\n") // Check whether the error is as expected. - if canonicalizeErrors(actual) != canonicalizeErrors(expected) { + if !matchErrors(t, expected, actual) { t.Errorf("expected error:\n%s\ngot:\n%s", indentText(expected, "> "), indentText(actual, "> ")) } } -func canonicalizeErrors(text string) string { - // Fix for Windows: replace all backslashes with forward slashes so that - // paths will be the same as on POSIX systems. - // (It may also change some other backslashes, but since this is only for - // comparing text it should be fine). - if runtime.GOOS == "windows" { - text = strings.ReplaceAll(text, "\\", "/") +func matchErrors(t *testing.T, pattern, actual string) bool { + patternLines := strings.Split(pattern, "\n") + actualLines := strings.Split(actual, "\n") + if len(patternLines) != len(actualLines) { + return false } - return text + for i, patternLine := range patternLines { + indices := regexp.MustCompile(`\{\{.*?\}\}`).FindAllStringIndex(patternLine, -1) + patternParts := []string{"^"} + lastStop := 0 + for _, startstop := range indices { + start := startstop[0] + stop := startstop[1] + patternParts = append(patternParts, + regexp.QuoteMeta(patternLine[lastStop:start]), + patternLine[start+2:stop-2]) + lastStop = stop + } + patternParts = append(patternParts, regexp.QuoteMeta(patternLine[lastStop:]), "$") + pattern := strings.Join(patternParts, "") + re, err := regexp.Compile(pattern) + if err != nil { + t.Fatalf("could not compile regexp for %#v: %v", patternLine, err) + } + if !re.MatchString(actualLines[i]) { + return false + } + } + return true } // Indent the given text with a given indentation string. diff --git a/testdata/errors/loader-invaliddep.go b/testdata/errors/loader-invaliddep.go index db935d38ae..05c2f2d5b2 100644 --- a/testdata/errors/loader-invaliddep.go +++ b/testdata/errors/loader-invaliddep.go @@ -5,4 +5,4 @@ import _ "github.com/tinygo-org/tinygo/testdata/errors/invaliddep" func main() { } -// ERROR: invaliddep/invaliddep.go:1:1: expected 'package', found ppackage +// ERROR: invaliddep{{[\\/]}}invaliddep.go:1:1: expected 'package', found ppackage From a5f78a390065204d161fa80d0cfa83f9d0268d85 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 12 Jul 2024 14:32:50 +0200 Subject: [PATCH 101/444] main: add interp tests This one needed more advanced checking of the error messages, because we don't want to hardcode things like `!dbg !10` in the output. --- errors_test.go | 1 + interp/interpreter.go | 4 ++++ testdata/errors/interp.go | 31 +++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 testdata/errors/interp.go diff --git a/errors_test.go b/errors_test.go index 49d7b25732..d671e7335e 100644 --- a/errors_test.go +++ b/errors_test.go @@ -17,6 +17,7 @@ import ( func TestErrors(t *testing.T) { for _, name := range []string{ "cgo", + "interp", "loader-importcycle", "loader-invaliddep", "loader-invalidpackage", diff --git a/interp/interpreter.go b/interp/interpreter.go index ea5eeaa12e..b35129b814 100644 --- a/interp/interpreter.go +++ b/interp/interpreter.go @@ -417,6 +417,10 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent } else { locals[inst.localIndex] = literalValue{uint8(0)} } + case callFn.name == "__tinygo_interp_raise_test_error": + // Special function that will trigger an error. + // This is used to test error reporting. + return nil, mem, r.errorAt(inst, errors.New("test error")) case strings.HasSuffix(callFn.name, ".$typeassert"): if r.debug { fmt.Fprintln(os.Stderr, indent+"interface assert:", operands[1:]) diff --git a/testdata/errors/interp.go b/testdata/errors/interp.go new file mode 100644 index 0000000000..a3f5cee78e --- /dev/null +++ b/testdata/errors/interp.go @@ -0,0 +1,31 @@ +package main + +import _ "unsafe" + +func init() { + foo() +} + +func foo() { + interp_test_error() +} + +// This is a function that always causes an error in interp, for testing. +// +//go:linkname interp_test_error __tinygo_interp_raise_test_error +func interp_test_error() + +func main() { +} + +// ERROR: # main +// ERROR: {{.*testdata[\\/]errors[\\/]interp\.go}}:10:19: test error +// ERROR: call void @__tinygo_interp_raise_test_error{{.*}} +// ERROR: {{}} +// ERROR: traceback: +// ERROR: {{.*testdata[\\/]errors[\\/]interp\.go}}:10:19: +// ERROR: call void @__tinygo_interp_raise_test_error{{.*}} +// ERROR: {{.*testdata[\\/]errors[\\/]interp\.go}}:6:5: +// ERROR: call void @main.foo{{.*}} +// ERROR: {{.*testdata[\\/]errors}}: +// ERROR: call void @"main.init#1"{{.*}} From f4ce11ef37ea155ccf04e43590739a893b98a865 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 12 Jul 2024 17:05:04 +0200 Subject: [PATCH 102/444] main: add error tests for the compiler The compiler can in fact return errors, but these weren't tested yet. --- errors_test.go | 1 + testdata/errors/compiler.go | 13 +++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 testdata/errors/compiler.go diff --git a/errors_test.go b/errors_test.go index d671e7335e..1d29236ca5 100644 --- a/errors_test.go +++ b/errors_test.go @@ -17,6 +17,7 @@ import ( func TestErrors(t *testing.T) { for _, name := range []string{ "cgo", + "compiler", "interp", "loader-importcycle", "loader-invaliddep", diff --git a/testdata/errors/compiler.go b/testdata/errors/compiler.go new file mode 100644 index 0000000000..fb0057c933 --- /dev/null +++ b/testdata/errors/compiler.go @@ -0,0 +1,13 @@ +package main + +//go:wasmimport foo bar +func foo() { +} + +//go:align 7 +var global int + +// TODO: include package name in the error message + +// ERROR: compiler.go:4:6: can only use //go:wasmimport on declarations +// ERROR: compiler.go:8:5: global variable alignment must be a positive power of two From 3788b31ba52a79a1a0f832c0096e7805df6b8ee1 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 12 Jul 2024 17:15:52 +0200 Subject: [PATCH 103/444] main: add test for error coming from the optimizer This is just one optimizer pass that's easy to test for. There are others, but I'm ignoring them for now. The one other that would be reasonable to test is when starting a goroutine with -gc=none, but I'm leaving that one for another time. --- errors_test.go | 1 + testdata/errors/optimizer.go | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 testdata/errors/optimizer.go diff --git a/errors_test.go b/errors_test.go index 1d29236ca5..7917419787 100644 --- a/errors_test.go +++ b/errors_test.go @@ -23,6 +23,7 @@ func TestErrors(t *testing.T) { "loader-invaliddep", "loader-invalidpackage", "loader-nopackage", + "optimizer", "syntax", "types", } { diff --git a/testdata/errors/optimizer.go b/testdata/errors/optimizer.go new file mode 100644 index 0000000000..4e0d71b934 --- /dev/null +++ b/testdata/errors/optimizer.go @@ -0,0 +1,18 @@ +package main + +import "runtime/interrupt" + +var num = 5 + +func main() { + // Error coming from LowerInterrupts. + interrupt.New(num, func(interrupt.Interrupt) { + }) + + // 2nd error + interrupt.New(num, func(interrupt.Interrupt) { + }) +} + +// ERROR: optimizer.go:9:15: interrupt ID is not a constant +// ERROR: optimizer.go:13:15: interrupt ID is not a constant From 80269b98ba1163485edf0972d1b9e93353f67e5b Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 12 Jul 2024 18:28:53 +0200 Subject: [PATCH 104/444] diagnostics: move diagnostic printing to a new package This is a refactor, which should (in theory) not change the behavior of the compiler. But since this is a pretty large change, there is a chance there will be some regressions. For that reason, the previous commits added a bunch of tests to make sure most error messages will not be changed due to this refactor. --- diagnostics/diagnostics.go | 196 +++++++++++++++++++++++++++++++++++++ errors_test.go | 6 +- main.go | 96 +----------------- main_test.go | 7 +- 4 files changed, 207 insertions(+), 98 deletions(-) create mode 100644 diagnostics/diagnostics.go diff --git a/diagnostics/diagnostics.go b/diagnostics/diagnostics.go new file mode 100644 index 0000000000..c793ba4aa6 --- /dev/null +++ b/diagnostics/diagnostics.go @@ -0,0 +1,196 @@ +// Package diagnostics formats compiler errors and prints them in a consistent +// way. +package diagnostics + +import ( + "bytes" + "fmt" + "go/scanner" + "go/token" + "go/types" + "io" + "path/filepath" + "strings" + + "github.com/tinygo-org/tinygo/builder" + "github.com/tinygo-org/tinygo/goenv" + "github.com/tinygo-org/tinygo/interp" + "github.com/tinygo-org/tinygo/loader" +) + +// A single diagnostic. +type Diagnostic struct { + Pos token.Position + Msg string +} + +// One or multiple errors of a particular package. +// It can also represent whole-program errors (like linker errors) that can't +// easily be connected to a single package. +type PackageDiagnostic struct { + ImportPath string // the same ImportPath as in `go list -json` + Diagnostics []Diagnostic +} + +// Diagnostics of a whole program. This can include errors belonging to multiple +// packages, or just a single package. +type ProgramDiagnostic []PackageDiagnostic + +// CreateDiagnostics reads the underlying errors in the error object and creates +// a set of diagnostics that's sorted and can be readily printed. +func CreateDiagnostics(err error) ProgramDiagnostic { + if err == nil { + return nil + } + switch err := err.(type) { + case *builder.MultiError: + var diags ProgramDiagnostic + for _, err := range err.Errs { + diags = append(diags, createPackageDiagnostic(err)) + } + return diags + default: + return ProgramDiagnostic{ + createPackageDiagnostic(err), + } + } +} + +// Create diagnostics for a single package (though, in practice, it may also be +// used for whole-program diagnostics in some cases). +func createPackageDiagnostic(err error) PackageDiagnostic { + var pkgDiag PackageDiagnostic + switch err := err.(type) { + case loader.Errors: + if err.Pkg != nil { + pkgDiag.ImportPath = err.Pkg.ImportPath + } + for _, err := range err.Errs { + diags := createDiagnostics(err) + pkgDiag.Diagnostics = append(pkgDiag.Diagnostics, diags...) + } + case *interp.Error: + pkgDiag.ImportPath = err.ImportPath + w := &bytes.Buffer{} + fmt.Fprintln(w, err.Error()) + if len(err.Inst) != 0 { + fmt.Fprintln(w, err.Inst) + } + if len(err.Traceback) > 0 { + fmt.Fprintln(w, "\ntraceback:") + for _, line := range err.Traceback { + fmt.Fprintln(w, line.Pos.String()+":") + fmt.Fprintln(w, line.Inst) + } + } + pkgDiag.Diagnostics = append(pkgDiag.Diagnostics, Diagnostic{ + Msg: w.String(), + }) + default: + pkgDiag.Diagnostics = createDiagnostics(err) + } + // TODO: sort + return pkgDiag +} + +// Extract diagnostics from the given error message and return them as a slice +// of errors (which in many cases will just be a single diagnostic). +func createDiagnostics(err error) []Diagnostic { + switch err := err.(type) { + case types.Error: + return []Diagnostic{ + { + Pos: err.Fset.Position(err.Pos), + Msg: err.Msg, + }, + } + case scanner.Error: + return []Diagnostic{ + { + Pos: err.Pos, + Msg: err.Msg, + }, + } + case scanner.ErrorList: + var diags []Diagnostic + for _, err := range err { + diags = append(diags, createDiagnostics(*err)...) + } + return diags + case loader.Error: + if err.Err.Pos.Filename != "" { + // Probably a syntax error in a dependency. + return createDiagnostics(err.Err) + } else { + // Probably an "import cycle not allowed" error. + buf := &bytes.Buffer{} + fmt.Fprintln(buf, "package", err.ImportStack[0]) + for i := 1; i < len(err.ImportStack); i++ { + pkgPath := err.ImportStack[i] + if i == len(err.ImportStack)-1 { + // last package + fmt.Fprintln(buf, "\timports", pkgPath+": "+err.Err.Error()) + } else { + // not the last pacakge + fmt.Fprintln(buf, "\timports", pkgPath) + } + } + return []Diagnostic{ + {Msg: buf.String()}, + } + } + default: + return []Diagnostic{ + {Msg: err.Error()}, + } + } +} + +// Write program diagnostics to the given writer with 'wd' as the relative +// working directory. +func (progDiag ProgramDiagnostic) WriteTo(w io.Writer, wd string) { + for _, pkgDiag := range progDiag { + pkgDiag.WriteTo(w, wd) + } +} + +// Write package diagnostics to the given writer with 'wd' as the relative +// working directory. +func (pkgDiag PackageDiagnostic) WriteTo(w io.Writer, wd string) { + if pkgDiag.ImportPath != "" { + fmt.Fprintln(w, "#", pkgDiag.ImportPath) + } + for _, diag := range pkgDiag.Diagnostics { + diag.WriteTo(w, wd) + } +} + +// Write this diagnostic to the given writer with 'wd' as the relative working +// directory. +func (diag Diagnostic) WriteTo(w io.Writer, wd string) { + if diag.Pos == (token.Position{}) { + fmt.Fprintln(w, diag.Msg) + return + } + pos := diag.Pos // make a copy + if !strings.HasPrefix(pos.Filename, filepath.Join(goenv.Get("GOROOT"), "src")) && !strings.HasPrefix(pos.Filename, filepath.Join(goenv.Get("TINYGOROOT"), "src")) { + // This file is not from the standard library (either the GOROOT or the + // TINYGOROOT). Make the path relative, for easier reading. Ignore any + // errors in the process (falling back to the absolute path). + pos.Filename = tryToMakePathRelative(pos.Filename, wd) + } + fmt.Fprintf(w, "%s: %s\n", pos, diag.Msg) +} + +// try to make the path relative to the current working directory. If any error +// occurs, this error is ignored and the absolute path is returned instead. +func tryToMakePathRelative(dir, wd string) string { + if wd == "" { + return dir // working directory not found + } + relpath, err := filepath.Rel(wd, dir) + if err != nil { + return dir + } + return relpath +} diff --git a/errors_test.go b/errors_test.go index 7917419787..71eaff5ef2 100644 --- a/errors_test.go +++ b/errors_test.go @@ -2,7 +2,6 @@ package main import ( "bytes" - "fmt" "os" "path/filepath" "regexp" @@ -11,6 +10,7 @@ import ( "time" "github.com/tinygo-org/tinygo/compileopts" + "github.com/tinygo-org/tinygo/diagnostics" ) // Test the error messages of the TinyGo compiler. @@ -59,9 +59,7 @@ func testErrorMessages(t *testing.T, filename string) { // Write error message out as plain text. var buf bytes.Buffer - printCompilerError(err, func(v ...interface{}) { - fmt.Fprintln(&buf, v...) - }, wd) + diagnostics.CreateDiagnostics(err).WriteTo(&buf, wd) actual := strings.TrimRight(buf.String(), "\n") // Check whether the error is as expected. diff --git a/main.go b/main.go index 9af0b0fc5c..d0e1145ce4 100644 --- a/main.go +++ b/main.go @@ -8,8 +8,6 @@ import ( "errors" "flag" "fmt" - "go/scanner" - "go/types" "io" "os" "os/exec" @@ -30,8 +28,8 @@ import ( "github.com/mattn/go-colorable" "github.com/tinygo-org/tinygo/builder" "github.com/tinygo-org/tinygo/compileopts" + "github.com/tinygo-org/tinygo/diagnostics" "github.com/tinygo-org/tinygo/goenv" - "github.com/tinygo-org/tinygo/interp" "github.com/tinygo-org/tinygo/loader" "golang.org/x/tools/go/buildutil" "tinygo.org/x/go-llvm" @@ -1292,99 +1290,13 @@ func usage(command string) { } } -// try to make the path relative to the current working directory. If any error -// occurs, this error is ignored and the absolute path is returned instead. -func tryToMakePathRelative(dir, wd string) string { - if wd == "" { - return dir // working directory not found - } - relpath, err := filepath.Rel(wd, dir) - if err != nil { - return dir - } - return relpath -} - -// printCompilerError prints compiler errors using the provided logger function -// (similar to fmt.Println). -func printCompilerError(err error, logln func(...interface{}), wd string) { - switch err := err.(type) { - case types.Error: - printCompilerError(scanner.Error{ - Pos: err.Fset.Position(err.Pos), - Msg: err.Msg, - }, logln, wd) - case scanner.Error: - if !strings.HasPrefix(err.Pos.Filename, filepath.Join(goenv.Get("GOROOT"), "src")) && !strings.HasPrefix(err.Pos.Filename, filepath.Join(goenv.Get("TINYGOROOT"), "src")) { - // This file is not from the standard library (either the GOROOT or - // the TINYGOROOT). Make the path relative, for easier reading. - // Ignore any errors in the process (falling back to the absolute - // path). - err.Pos.Filename = tryToMakePathRelative(err.Pos.Filename, wd) - } - logln(err) - case scanner.ErrorList: - for _, scannerErr := range err { - printCompilerError(*scannerErr, logln, wd) - } - case *interp.Error: - logln("#", err.ImportPath) - logln(err.Error()) - if len(err.Inst) != 0 { - logln(err.Inst) - } - if len(err.Traceback) > 0 { - logln("\ntraceback:") - for _, line := range err.Traceback { - logln(line.Pos.String() + ":") - logln(line.Inst) - } - } - case loader.Errors: - // Parser errors, typechecking errors, or `go list` errors. - // err.Pkg is nil for `go list` errors. - if err.Pkg != nil { - logln("#", err.Pkg.ImportPath) - } - for _, err := range err.Errs { - printCompilerError(err, logln, wd) - } - case loader.Error: - if err.Err.Pos.Filename != "" { - // Probably a syntax error in a dependency. - printCompilerError(err.Err, logln, wd) - } else { - // Probably an "import cycle not allowed" error. - logln("package", err.ImportStack[0]) - for i := 1; i < len(err.ImportStack); i++ { - pkgPath := err.ImportStack[i] - if i == len(err.ImportStack)-1 { - // last package - logln("\timports", pkgPath+": "+err.Err.Error()) - } else { - // not the last pacakge - logln("\timports", pkgPath) - } - } - } - case *builder.MultiError: - for _, err := range err.Errs { - printCompilerError(err, logln, wd) - } - default: - logln("error:", err) - } -} - func handleCompilerError(err error) { if err != nil { wd, getwdErr := os.Getwd() if getwdErr != nil { wd = "" } - printCompilerError(err, func(args ...interface{}) { - fmt.Fprintln(os.Stderr, args...) - }, wd) + diagnostics.CreateDiagnostics(err).WriteTo(os.Stderr, wd) os.Exit(1) } } @@ -1790,9 +1702,7 @@ func main() { if getwdErr != nil { wd = "" } - printCompilerError(err, func(args ...interface{}) { - fmt.Fprintln(stderr, args...) - }, wd) + diagnostics.CreateDiagnostics(err).WriteTo(os.Stderr, wd) } if !passed { select { diff --git a/main_test.go b/main_test.go index d54af86348..c294b3a8a5 100644 --- a/main_test.go +++ b/main_test.go @@ -24,6 +24,7 @@ import ( "github.com/aykevl/go-wasm" "github.com/tinygo-org/tinygo/builder" "github.com/tinygo-org/tinygo/compileopts" + "github.com/tinygo-org/tinygo/diagnostics" "github.com/tinygo-org/tinygo/goenv" ) @@ -380,7 +381,11 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c return cmd.Run() }) if err != nil { - printCompilerError(err, t.Log, "") + w := &bytes.Buffer{} + diagnostics.CreateDiagnostics(err).WriteTo(w, "") + for _, line := range strings.Split(strings.TrimRight(w.String(), "\n"), "\n") { + t.Log(line) + } t.Fail() return } From 824bf244453bdb0502fa30de70c799d666665480 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 12 Jul 2024 18:35:54 +0200 Subject: [PATCH 105/444] diagnostics: sort package diagnostics by position Previously, the error messages could be shown out of order. With this patch, the errors are sorted before being shown to the user. This makes it easier to read the error messages, and matches what the `go` toolchain does. --- diagnostics/diagnostics.go | 17 ++++++++++++++++- testdata/errors/types.go | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/diagnostics/diagnostics.go b/diagnostics/diagnostics.go index c793ba4aa6..f9261e2923 100644 --- a/diagnostics/diagnostics.go +++ b/diagnostics/diagnostics.go @@ -10,6 +10,7 @@ import ( "go/types" "io" "path/filepath" + "sort" "strings" "github.com/tinygo-org/tinygo/builder" @@ -59,6 +60,7 @@ func CreateDiagnostics(err error) ProgramDiagnostic { // Create diagnostics for a single package (though, in practice, it may also be // used for whole-program diagnostics in some cases). func createPackageDiagnostic(err error) PackageDiagnostic { + // Extract diagnostics for this package. var pkgDiag PackageDiagnostic switch err := err.(type) { case loader.Errors: @@ -89,7 +91,20 @@ func createPackageDiagnostic(err error) PackageDiagnostic { default: pkgDiag.Diagnostics = createDiagnostics(err) } - // TODO: sort + + // Sort these diagnostics by file/line/column. + sort.SliceStable(pkgDiag.Diagnostics, func(i, j int) bool { + posI := pkgDiag.Diagnostics[i].Pos + posJ := pkgDiag.Diagnostics[j].Pos + if posI.Filename != posJ.Filename { + return posI.Filename < posJ.Filename + } + if posI.Line != posJ.Line { + return posI.Line < posJ.Line + } + return posI.Column < posJ.Column + }) + return pkgDiag } diff --git a/testdata/errors/types.go b/testdata/errors/types.go index 491e2fe67a..6bd949f0c4 100644 --- a/testdata/errors/types.go +++ b/testdata/errors/types.go @@ -7,6 +7,6 @@ func main() { } // ERROR: # command-line-arguments +// ERROR: types.go:4:6: a declared and not used // ERROR: types.go:5:6: cannot use "foobar" (untyped string constant) as int value in assignment // ERROR: types.go:6:2: undefined: nonexisting -// ERROR: types.go:4:6: a declared and not used From 89340f82dc65ec6b1adec75efec2dafe78368a40 Mon Sep 17 00:00:00 2001 From: Laurent Demailly Date: Sat, 20 Jul 2024 06:26:08 -0700 Subject: [PATCH 106/444] Allows compilation of code using debug.BuildInfo and show correct tinygo version (#4343) debug: Allows compilation of code using debug.BuildInfo and show correct tinygo version --- .gitignore | 2 + src/runtime/debug/debug.go | 81 +++++++++++++++++++++++++++++++++++--- 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 9994ee3dca..7c2f12b312 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.DS_Store +.vscode go.work go.work.sum diff --git a/src/runtime/debug/debug.go b/src/runtime/debug/debug.go index 7631173133..38e6ab763b 100644 --- a/src/runtime/debug/debug.go +++ b/src/runtime/debug/debug.go @@ -1,6 +1,17 @@ -// Package debug is a dummy package that is not yet implemented. +// Portions copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package debug is a very partially implemented package to allow compilation. package debug +import ( + "fmt" + "runtime" + "strconv" + "strings" +) + // SetMaxStack sets the maximum amount of memory that can be used by a single // goroutine stack. // @@ -27,16 +38,17 @@ func Stack() []byte { // // Not implemented. func ReadBuildInfo() (info *BuildInfo, ok bool) { - return nil, false + return &BuildInfo{GoVersion: runtime.Compiler + runtime.Version()}, true } // BuildInfo represents the build information read from // the running binary. type BuildInfo struct { - Path string // The main package path - Main Module // The module containing the main package - Deps []*Module // Module dependencies - Settings []BuildSetting + GoVersion string // version of the Go toolchain that built the binary, e.g. "go1.19.2" + Path string // The main package path + Main Module // The module containing the main package + Deps []*Module // Module dependencies + Settings []BuildSetting } type BuildSetting struct { @@ -58,3 +70,60 @@ type Module struct { func SetGCPercent(n int) int { return n } + +// Start of stolen from big go. TODO: import/reuse without copy pasta. + +// quoteKey reports whether key is required to be quoted. +func quoteKey(key string) bool { + return len(key) == 0 || strings.ContainsAny(key, "= \t\r\n\"`") +} + +// quoteValue reports whether value is required to be quoted. +func quoteValue(value string) bool { + return strings.ContainsAny(value, " \t\r\n\"`") +} + +func (bi *BuildInfo) String() string { + buf := new(strings.Builder) + if bi.GoVersion != "" { + fmt.Fprintf(buf, "go\t%s\n", bi.GoVersion) + } + if bi.Path != "" { + fmt.Fprintf(buf, "path\t%s\n", bi.Path) + } + var formatMod func(string, Module) + formatMod = func(word string, m Module) { + buf.WriteString(word) + buf.WriteByte('\t') + buf.WriteString(m.Path) + buf.WriteByte('\t') + buf.WriteString(m.Version) + if m.Replace == nil { + buf.WriteByte('\t') + buf.WriteString(m.Sum) + } else { + buf.WriteByte('\n') + formatMod("=>", *m.Replace) + } + buf.WriteByte('\n') + } + if bi.Main != (Module{}) { + formatMod("mod", bi.Main) + } + for _, dep := range bi.Deps { + formatMod("dep", *dep) + } + for _, s := range bi.Settings { + key := s.Key + if quoteKey(key) { + key = strconv.Quote(key) + } + value := s.Value + if quoteValue(value) { + value = strconv.Quote(value) + } + fmt.Fprintf(buf, "build\t%s=%s\n", key, value) + } + + return buf.String() +} From 4af3f618f83e1b881fa72ee480a01ce3e76d7506 Mon Sep 17 00:00:00 2001 From: Matthew Hiles Date: Thu, 11 Jul 2024 09:44:43 -0400 Subject: [PATCH 107/444] rewrite Reply() to fix sending long replies in I2C Target Mode --- src/machine/machine_rp2040_i2c.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/machine/machine_rp2040_i2c.go b/src/machine/machine_rp2040_i2c.go index 1b66a86876..7ca2a87cf9 100644 --- a/src/machine/machine_rp2040_i2c.go +++ b/src/machine/machine_rp2040_i2c.go @@ -501,15 +501,23 @@ func (i2c *I2C) Reply(buf []byte) error { } for txPtr < len(buf) { - if stat&rp.I2C0_IC_INTR_MASK_M_TX_EMPTY != 0 { - i2c.Bus.IC_DATA_CMD.Set(uint32(buf[txPtr])) + if i2c.Bus.GetIC_RAW_INTR_STAT_TX_EMPTY() != 0 { + i2c.Bus.SetIC_DATA_CMD_DAT(uint32(buf[txPtr])) txPtr++ + // The DW_apb_i2c flushes/resets/empties the + // TX_FIFO and RX_FIFO whenever there is a transmit abort + // caused by any of the events tracked by the + // IC_TX_ABRT_SOURCE register. + // In other words, it's safe to block until TX FIFO is + // EMPTY--it will empty from being transmitted or on error. + for i2c.Bus.GetIC_RAW_INTR_STAT_TX_EMPTY() == 0 { + } } // This Tx abort is a normal case - we're sending more // data than controller wants to receive - if stat&rp.I2C0_IC_INTR_MASK_M_TX_ABRT != 0 { - i2c.Bus.IC_CLR_TX_ABRT.Get() + if i2c.Bus.GetIC_RAW_INTR_STAT_TX_ABRT() != 0 { + i2c.Bus.GetIC_CLR_TX_ABRT_CLR_TX_ABRT() return nil } From 319683b86307d9e3686f28c7c248c3b2bb918f93 Mon Sep 17 00:00:00 2001 From: Laurent Demailly Date: Sat, 20 Jul 2024 11:19:03 -0700 Subject: [PATCH 108/444] Add enough tls.ConnectionState and tls.CipherSuiteName() to compile fortio/log (#4345) src/crypt: add enough tls.ConnectionState and tls.CipherSuiteName() to compile fortio/log --- src/crypto/tls/common.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index 5b9c0c5f59..caf0198e15 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -10,6 +10,7 @@ import ( "context" "crypto" "crypto/x509" + "fmt" "io" "net" "sync" @@ -23,9 +24,21 @@ import ( // only supports Elliptic Curve based groups. See RFC 8446, Section 4.2.7. type CurveID uint16 +// CipherSuiteName returns the standard name for the passed cipher suite ID +// +// Not Implemented. +func CipherSuiteName(id uint16) string { + return fmt.Sprintf("0x%04X", id) +} + // ConnectionState records basic TLS details about the connection. type ConnectionState struct { // TINYGO: empty; TLS connection offloaded to device + // + // Minimum (empty) fields for fortio.org/log http logging and others + // to compile and run. + PeerCertificates []*x509.Certificate + CipherSuite uint16 } // ClientAuthType declares the policy the server will follow for From 4f908f4877f8ff571002df1e260e338c7c3c8fe0 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sat, 20 Jul 2024 15:49:36 +0200 Subject: [PATCH 109/444] net: update to latest net package Signed-off-by: deadprogram --- src/net | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/net b/src/net index a79417481d..a237059610 160000 --- a/src/net +++ b/src/net @@ -1 +1 @@ -Subproject commit a79417481d37e21f29d257c28fecc503df9703e0 +Subproject commit a2370596106a621a9b9dd6ad930f0ec24cfee8d0 From 3e2230eadb14d0e54db621976d60d654487f891a Mon Sep 17 00:00:00 2001 From: leongross Date: Sun, 21 Jul 2024 10:56:55 +0200 Subject: [PATCH 110/444] os/Chown (#4213) src/os, src/syscall: add os.Chown --- src/os/file_anyos.go | 14 +++++++++++++ src/os/os_chmod_test.go | 41 +++++++++++++++++++++++++++++++++++++ src/syscall/syscall_libc.go | 14 +++++++++++++ 3 files changed, 69 insertions(+) diff --git a/src/os/file_anyos.go b/src/os/file_anyos.go index 1483f11c2a..da70d72847 100644 --- a/src/os/file_anyos.go +++ b/src/os/file_anyos.go @@ -147,6 +147,20 @@ func Chmod(name string, mode FileMode) error { return nil } +// Chown changes the numeric uid and gid of the named file. +// If the file is a symbolic link, it changes the uid and gid of the link's target. +// A uid or gid of -1 means to not change that value. +// If there is an error, it will be of type *PathError. +func Chown(name string, uid, gid int) error { + e := ignoringEINTR(func() error { + return syscall.Chown(name, uid, gid) + }) + if e != nil { + return &PathError{Op: "chown", Path: name, Err: e} + } + return nil +} + // ignoringEINTR makes a function call and repeats it if it returns an // EINTR error. This appears to be required even though we install all // signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846. diff --git a/src/os/os_chmod_test.go b/src/os/os_chmod_test.go index 3db6467add..ad151abb03 100644 --- a/src/os/os_chmod_test.go +++ b/src/os/os_chmod_test.go @@ -9,12 +9,15 @@ package os_test import ( + "errors" + "io/fs" . "os" "runtime" "testing" ) func TestChmod(t *testing.T) { + // Chmod f := newFile("TestChmod", t) defer Remove(f.Name()) defer f.Close() @@ -28,4 +31,42 @@ func TestChmod(t *testing.T) { t.Fatalf("chmod %s %#o: %s", f.Name(), fm, err) } checkMode(t, f.Name(), fm) + +} + +// Since testing syscalls requires a static, predictable environment that has to be controlled +// by the CI, we don't test for success but for failures and verify that the error messages are as expected. +// EACCES is returned when the user does not have the required permissions to change the ownership of the file +// ENOENT is returned when the file does not exist +// ENOTDIR is returned when the file is not a directory +func TestChownErr(t *testing.T) { + if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { + t.Log("skipping on " + runtime.GOOS) + return + } + + var ( + TEST_UID_ROOT = 0 + TEST_GID_ROOT = 0 + ) + + f := newFile("TestChown", t) + defer Remove(f.Name()) + defer f.Close() + + // EACCES + if err := Chown(f.Name(), TEST_UID_ROOT, TEST_GID_ROOT); err != nil { + errCmp := fs.PathError{Op: "chown", Path: f.Name(), Err: errors.New("operation not permitted")} + if errors.Is(err, &errCmp) { + t.Fatalf("chown(%s, uid=%v, gid=%v): got '%v', want 'operation not permitted'", f.Name(), TEST_UID_ROOT, TEST_GID_ROOT, err) + } + } + + // ENOENT + if err := Chown("invalid", Geteuid(), Getgid()); err != nil { + errCmp := fs.PathError{Op: "chown", Path: "invalid", Err: errors.New("no such file or directory")} + if errors.Is(err, &errCmp) { + t.Fatalf("chown(%s, uid=%v, gid=%v): got '%v', want 'no such file or directory'", f.Name(), Geteuid(), Getegid(), err) + } + } } diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index eed18cbd9b..07a8acb0b2 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -163,6 +163,15 @@ func Unlink(path string) (err error) { return } +func Chown(path string, uid, gid int) (err error) { + data := cstring(path) + fail := int(libc_chown(&data[0], uid, gid)) + if fail < 0 { + err = getErrno() + } + return +} + func Faccessat(dirfd int, path string, mode uint32, flags int) (err error) func Kill(pid int, sig Signal) (err error) { @@ -357,6 +366,11 @@ func libc_chdir(pathname *byte) int32 //export chmod func libc_chmod(pathname *byte, mode uint32) int32 +// int chown(const char *pathname, uid_t owner, gid_t group); +// +//export chown +func libc_chown(pathname *byte, owner, group int) int32 + // int mkdir(const char *pathname, mode_t mode); // //export mkdir From 04d1261f8a48b18a4404c52f858c35c0668316cd Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 21 Jul 2024 14:12:55 +0200 Subject: [PATCH 111/444] build: add package ID to compiler and optimization error messages This improves error reporting slightly. --- builder/build.go | 4 ++-- builder/error.go | 8 +++++--- diagnostics/diagnostics.go | 23 ++++++++++++----------- testdata/errors/compiler.go | 3 +-- testdata/errors/optimizer.go | 1 + 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/builder/build.go b/builder/build.go index 50ec04d9d9..49aad5c790 100644 --- a/builder/build.go +++ b/builder/build.go @@ -375,7 +375,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe defer mod.Context().Dispose() defer mod.Dispose() if errs != nil { - return newMultiError(errs) + return newMultiError(errs, pkg.ImportPath) } if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { return errors.New("verification error after compiling package " + pkg.ImportPath) @@ -1130,7 +1130,7 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config, globalValues m // O0/O1/O2/Os/Oz optimization pipeline). errs := transform.Optimize(mod, config) if len(errs) > 0 { - return newMultiError(errs) + return newMultiError(errs, "") } if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { return errors.New("verification failure after LLVM optimization passes") diff --git a/builder/error.go b/builder/error.go index 0f920a0315..3531007fb6 100644 --- a/builder/error.go +++ b/builder/error.go @@ -3,7 +3,8 @@ package builder // MultiError is a list of multiple errors (actually: diagnostics) returned // during LLVM IR generation. type MultiError struct { - Errs []error + ImportPath string + Errs []error } func (e *MultiError) Error() string { @@ -15,14 +16,15 @@ func (e *MultiError) Error() string { // newMultiError returns a *MultiError if there is more than one error, or // returns that error directly when there is only one. Passing an empty slice // will lead to a panic. -func newMultiError(errs []error) error { +// The importPath may be passed if this error is for a single package. +func newMultiError(errs []error, importPath string) error { switch len(errs) { case 0: panic("attempted to create empty MultiError") case 1: return errs[0] default: - return &MultiError{errs} + return &MultiError{importPath, errs} } } diff --git a/diagnostics/diagnostics.go b/diagnostics/diagnostics.go index f9261e2923..e58becfb8a 100644 --- a/diagnostics/diagnostics.go +++ b/diagnostics/diagnostics.go @@ -43,17 +43,10 @@ func CreateDiagnostics(err error) ProgramDiagnostic { if err == nil { return nil } - switch err := err.(type) { - case *builder.MultiError: - var diags ProgramDiagnostic - for _, err := range err.Errs { - diags = append(diags, createPackageDiagnostic(err)) - } - return diags - default: - return ProgramDiagnostic{ - createPackageDiagnostic(err), - } + // Right now, the compiler will only show errors for the first pacakge that + // fails to build. This is likely to change in the future. + return ProgramDiagnostic{ + createPackageDiagnostic(err), } } @@ -63,6 +56,14 @@ func createPackageDiagnostic(err error) PackageDiagnostic { // Extract diagnostics for this package. var pkgDiag PackageDiagnostic switch err := err.(type) { + case *builder.MultiError: + if err.ImportPath != "" { + pkgDiag.ImportPath = err.ImportPath + } + for _, err := range err.Errs { + diags := createDiagnostics(err) + pkgDiag.Diagnostics = append(pkgDiag.Diagnostics, diags...) + } case loader.Errors: if err.Pkg != nil { pkgDiag.ImportPath = err.Pkg.ImportPath diff --git a/testdata/errors/compiler.go b/testdata/errors/compiler.go index fb0057c933..3332c7c2f2 100644 --- a/testdata/errors/compiler.go +++ b/testdata/errors/compiler.go @@ -7,7 +7,6 @@ func foo() { //go:align 7 var global int -// TODO: include package name in the error message - +// ERROR: # command-line-arguments // ERROR: compiler.go:4:6: can only use //go:wasmimport on declarations // ERROR: compiler.go:8:5: global variable alignment must be a positive power of two diff --git a/testdata/errors/optimizer.go b/testdata/errors/optimizer.go index 4e0d71b934..c2e0b9c4bd 100644 --- a/testdata/errors/optimizer.go +++ b/testdata/errors/optimizer.go @@ -14,5 +14,6 @@ func main() { }) } +// ERROR: # command-line-arguments // ERROR: optimizer.go:9:15: interrupt ID is not a constant // ERROR: optimizer.go:13:15: interrupt ID is not a constant From 725518f007663d6742f1e92ebc2d6d88418ccf61 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 24 Jun 2024 20:22:16 +0200 Subject: [PATCH 112/444] all: add linux/mipsle support This adds linux/mipsle (little endian Mips) support to TinyGo. It also adds experimental linux/mips (big-endian) support. It doesn't quite work yet, some parts of the standard library (like the reflect package) currently seem to assume a little-endian system. --- .github/workflows/build-macos.yml | 2 +- .github/workflows/linux.yml | 6 +- .github/workflows/windows.yml | 2 +- GNUmakefile | 4 +- builder/builder_test.go | 2 + builder/library.go | 3 + compileopts/config.go | 5 +- compileopts/target.go | 20 +++++- compiler/defer.go | 12 ++++ compiler/llvm.go | 3 + compiler/syscall.go | 94 ++++++++++++++++++++++++++- main_test.go | 7 ++ src/internal/task/task_stack_mipsx.S | 66 +++++++++++++++++++ src/internal/task/task_stack_mipsx.go | 61 +++++++++++++++++ src/runtime/arch_386.go | 2 + src/runtime/arch_amd64.go | 2 + src/runtime/arch_arm.go | 2 + src/runtime/arch_arm64.go | 2 + src/runtime/arch_mips.go | 21 ++++++ src/runtime/arch_mipsle.go | 21 ++++++ src/runtime/asm_mipsx.S | 40 ++++++++++++ src/runtime/os_linux.go | 2 +- src/runtime/runtime_unix.go | 3 + 23 files changed, 371 insertions(+), 11 deletions(-) create mode 100644 src/internal/task/task_stack_mipsx.S create mode 100644 src/internal/task/task_stack_mipsx.go create mode 100644 src/runtime/arch_mips.go create mode 100644 src/runtime/arch_mipsle.go create mode 100644 src/runtime/asm_mipsx.S diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 7acd2e5b7d..17fe3852a6 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -68,7 +68,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-18-${{ matrix.os }}-v2 + key: llvm-build-18-${{ matrix.os }}-v3 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 8bb92e0832..218019224b 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -68,7 +68,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-18-linux-alpine-v1 + key: llvm-build-18-linux-alpine-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -223,7 +223,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-18-linux-asserts-v1 + key: llvm-build-18-linux-asserts-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -336,7 +336,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-18-linux-${{ matrix.goarch }}-v1 + key: llvm-build-18-linux-${{ matrix.goarch }}-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 2b26a9b1c6..0d144304d5 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -66,7 +66,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-18-windows-v1 + key: llvm-build-18-windows-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' diff --git a/GNUmakefile b/GNUmakefile index f2f5e2f53f..f06f28a18c 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -245,7 +245,7 @@ llvm-source: $(LLVM_PROJECTDIR)/llvm # Configure LLVM. TINYGO_SOURCE_DIR=$(shell pwd) $(LLVM_BUILDDIR)/build.ninja: - mkdir -p $(LLVM_BUILDDIR) && cd $(LLVM_BUILDDIR) && cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_ZSTD=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF -DCLANG_ENABLE_STATIC_ANALYZER=OFF -DCLANG_ENABLE_ARCMT=OFF $(LLVM_OPTION) + mkdir -p $(LLVM_BUILDDIR) && cd $(LLVM_BUILDDIR) && cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;AVR;Mips;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_ZSTD=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF -DCLANG_ENABLE_STATIC_ANALYZER=OFF -DCLANG_ENABLE_ARCMT=OFF $(LLVM_OPTION) # Build LLVM. $(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja @@ -835,6 +835,7 @@ endif $(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go @$(MD5SUM) test.hex GOOS=linux GOARCH=arm $(TINYGO) build -size short -o test.elf ./testdata/cgo + GOOS=linux GOARCH=mips $(TINYGO) build -size short -o test.elf ./testdata/cgo GOOS=windows GOARCH=amd64 $(TINYGO) build -size short -o test.exe ./testdata/cgo GOOS=windows GOARCH=arm64 $(TINYGO) build -size short -o test.exe ./testdata/cgo GOOS=darwin GOARCH=amd64 $(TINYGO) build -size short -o test ./testdata/cgo @@ -882,6 +883,7 @@ endif @cp -rp lib/musl/arch/arm build/release/tinygo/lib/musl/arch @cp -rp lib/musl/arch/generic build/release/tinygo/lib/musl/arch @cp -rp lib/musl/arch/i386 build/release/tinygo/lib/musl/arch + @cp -rp lib/musl/arch/mips build/release/tinygo/lib/musl/arch @cp -rp lib/musl/arch/x86_64 build/release/tinygo/lib/musl/arch @cp -rp lib/musl/crt/crt1.c build/release/tinygo/lib/musl/crt @cp -rp lib/musl/COPYRIGHT build/release/tinygo/lib/musl diff --git a/builder/builder_test.go b/builder/builder_test.go index c7b8638730..3fc166c5c8 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -57,6 +57,8 @@ func TestClangAttributes(t *testing.T) { {GOOS: "linux", GOARCH: "arm", GOARM: "6"}, {GOOS: "linux", GOARCH: "arm", GOARM: "7"}, {GOOS: "linux", GOARCH: "arm64"}, + {GOOS: "linux", GOARCH: "mips"}, + {GOOS: "linux", GOARCH: "mipsle"}, {GOOS: "darwin", GOARCH: "amd64"}, {GOOS: "darwin", GOARCH: "arm64"}, {GOOS: "windows", GOARCH: "amd64"}, diff --git a/builder/library.go b/builder/library.go index e0ac31a747..83fa3db940 100644 --- a/builder/library.go +++ b/builder/library.go @@ -182,6 +182,9 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ if strings.HasPrefix(target, "riscv64-") { args = append(args, "-march=rv64gc") } + if strings.HasPrefix(target, "mips") { + args = append(args, "-fno-pic") + } var once sync.Once diff --git a/compileopts/config.go b/compileopts/config.go index ab6de4e44c..893fbf0016 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -212,7 +212,10 @@ func (c *Config) RP2040BootPatch() bool { func MuslArchitecture(triple string) string { arch := strings.Split(triple, "-")[0] if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") { - arch = "arm" + return "arm" + } + if arch == "mipsel" { + return "mips" } return arch } diff --git a/compileopts/target.go b/compileopts/target.go index 646029e447..fdb29e2109 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -193,6 +193,10 @@ func LoadTarget(options *Options) (*TargetSpec, error) { default: return nil, fmt.Errorf("invalid GOARM=%s, must be 5, 6, or 7", options.GOARM) } + case "mips": + llvmarch = "mips" + case "mipsle": + llvmarch = "mipsel" case "wasm": llvmarch = "wasm32" default: @@ -327,6 +331,10 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { } else { // linux spec.Features = "+fp-armv8,+neon,-fmv,-outline-atomics" } + case "mips", "mipsle": + spec.CPU = "mips32r2" + spec.Features = "+fpxx,+mips32r2,+nooddspreg,-noabicalls" + spec.CFlags = append(spec.CFlags, "-fno-pic") case "wasm": spec.CPU = "generic" spec.Features = "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" @@ -419,8 +427,12 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { // operating systems so we need separate assembly files. suffix = "_windows" } - spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/asm_"+goarch+suffix+".S") - spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+goarch+suffix+".S") + asmGoarch := goarch + if goarch == "mips" || goarch == "mipsle" { + asmGoarch = "mipsx" + } + spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/asm_"+asmGoarch+suffix+".S") + spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+asmGoarch+suffix+".S") } if goarch != runtime.GOARCH { // Some educated guesses as to how to invoke helper programs. @@ -438,6 +450,10 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { spec.Emulator = "qemu-arm {}" case "arm64": spec.Emulator = "qemu-aarch64 {}" + case "mips": + spec.Emulator = "qemu-mips {}" + case "mipsle": + spec.Emulator = "qemu-mipsel {}" } } } diff --git a/compiler/defer.go b/compiler/defer.go index e1ff2f58ed..df8686957a 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -187,6 +187,18 @@ std z+5, r29 ldi r24, 0 1:` constraints = "={r24},z,~{r0},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{r13},~{r14},~{r15},~{r16},~{r17},~{r18},~{r19},~{r20},~{r21},~{r22},~{r23},~{r25},~{r26},~{r27}" + case "mips": + // $4 flag (zero or non-zero) + // $5 defer frame + asmString = ` +.set noat +move $$4, $$zero +jal 1f +1: +addiu $$ra, 8 +sw $$ra, 4($$5) +.set at` + constraints = "={$4},{$5},~{$1},~{$2},~{$3},~{$5},~{$6},~{$7},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$16},~{$17},~{$18},~{$19},~{$20},~{$21},~{$22},~{$23},~{$24},~{$25},~{$26},~{$27},~{$28},~{$29},~{$30},~{$31},~{$f0},~{$f1},~{$f2},~{$f3},~{$f4},~{$f5},~{$f6},~{$f7},~{$f8},~{$f9},~{$f10},~{$f11},~{$f12},~{$f13},~{$f14},~{$f15},~{$f16},~{$f17},~{$f18},~{$f19},~{$f20},~{$f21},~{$f22},~{$f23},~{$f24},~{$f25},~{$f26},~{$f27},~{$f28},~{$f29},~{$f30},~{$f31},~{memory}" case "riscv32": asmString = ` la a2, 1f diff --git a/compiler/llvm.go b/compiler/llvm.go index 5d4eaaa64b..9e3a95d4f5 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -429,6 +429,9 @@ func (c *compilerContext) archFamily() string { if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") { return "arm" } + if arch == "mipsel" { + return "mips" + } return arch } diff --git a/compiler/syscall.go b/compiler/syscall.go index bf3955df2d..d9d21c3cf5 100644 --- a/compiler/syscall.go +++ b/compiler/syscall.go @@ -12,7 +12,8 @@ import ( // createRawSyscall creates a system call with the provided system call number // and returns the result as a single integer (the system call result). The -// result is not further interpreted. +// result is not further interpreted (with the exception of MIPS to use the same +// return value everywhere). func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) { num := b.getValue(call.Args[0], getPos(call)) switch { @@ -137,6 +138,97 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) { target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false) return b.CreateCall(fnType, target, args, ""), nil + case (b.GOARCH == "mips" || b.GOARCH == "mipsle") && b.GOOS == "linux": + // Implement the system call convention for Linux. + // Source: syscall(2) man page and musl: + // https://git.musl-libc.org/cgit/musl/tree/arch/mips/syscall_arch.h + // Also useful: + // https://web.archive.org/web/20220529105937/https://www.linux-mips.org/wiki/Syscall + // The syscall number goes in r2, the result also in r2. + // Register r7 is both an input paramter and an output parameter: if it + // is non-zero, the system call failed and r2 is the error code. + // The code below implements the O32 syscall ABI, not the N32 ABI. It + // could implement both at the same time if needed (like what appears to + // be done in musl) by forcing arg5-arg7 into the right registers but + // letting the compiler decide the registers should result in _slightly_ + // faster and smaller code. + args := []llvm.Value{num} + argTypes := []llvm.Type{b.uintptrType} + constraints := "={$2},={$7},0" + syscallParams := call.Args[1:] + if len(syscallParams) > 7 { + // There is one syscall that uses 7 parameters: sync_file_range. + // But only 7, not more. Go however only has Syscall6 and Syscall9. + // Therefore, we can ignore the remaining parameters. + syscallParams = syscallParams[:7] + } + for i, arg := range syscallParams { + constraints += "," + [...]string{ + "{$4}", // arg1 + "{$5}", // arg2 + "{$6}", // arg3 + "1", // arg4, error return + "r", // arg5 on the stack + "r", // arg6 on the stack + "r", // arg7 on the stack + }[i] + llvmValue := b.getValue(arg, getPos(call)) + args = append(args, llvmValue) + argTypes = append(argTypes, llvmValue.Type()) + } + // Create assembly code. + // Parameters beyond the first 4 are passed on the stack instead of in + // registers in the O32 syscall ABI. + // We need ".set noat" because LLVM might pick register $1 ($at) as the + // register for a parameter and apparently this is not allowed on MIPS + // unless you use this specific pragma. + asm := "syscall" + switch len(syscallParams) { + case 5: + asm = "" + + ".set noat\n" + + "subu $$sp, $$sp, 32\n" + + "sw $7, 16($$sp)\n" + // arg5 + "syscall\n" + + "addu $$sp, $$sp, 32\n" + + ".set at\n" + case 6: + asm = "" + + ".set noat\n" + + "subu $$sp, $$sp, 32\n" + + "sw $7, 16($$sp)\n" + // arg5 + "sw $8, 20($$sp)\n" + // arg6 + "syscall\n" + + "addu $$sp, $$sp, 32\n" + + ".set at\n" + case 7: + asm = "" + + ".set noat\n" + + "subu $$sp, $$sp, 32\n" + + "sw $7, 16($$sp)\n" + // arg5 + "sw $8, 20($$sp)\n" + // arg6 + "sw $9, 24($$sp)\n" + // arg7 + "syscall\n" + + "addu $$sp, $$sp, 32\n" + + ".set at\n" + } + constraints += ",~{$3},~{$4},~{$5},~{$6},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$24},~{$25},~{hi},~{lo},~{memory}" + returnType := b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false) + fnType := llvm.FunctionType(returnType, argTypes, false) + target := llvm.InlineAsm(fnType, asm, constraints, true, true, 0, false) + call := b.CreateCall(fnType, target, args, "") + resultCode := b.CreateExtractValue(call, 0, "") // r2 + errorFlag := b.CreateExtractValue(call, 1, "") // r7 + // Pseudocode to return the result with the same convention as other + // archs: + // return (errorFlag != 0) ? -resultCode : resultCode; + // At least on QEMU with the O32 ABI, the error code is always positive. + zero := llvm.ConstInt(b.uintptrType, 0, false) + isError := b.CreateICmp(llvm.IntNE, errorFlag, zero, "") + negativeResult := b.CreateSub(zero, resultCode, "") + result := b.CreateSelect(isError, negativeResult, resultCode, "") + return result, nil + default: return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH) } diff --git a/main_test.go b/main_test.go index c294b3a8a5..057e008e87 100644 --- a/main_test.go +++ b/main_test.go @@ -37,6 +37,7 @@ var supportedLinuxArches = map[string]string{ "X86Linux": "linux/386", "ARMLinux": "linux/arm/6", "ARM64Linux": "linux/arm64", + "MIPSLinux": "linux/mipsle", "WASIp1": "wasip1/wasm", } @@ -211,6 +212,12 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { continue } } + if options.GOOS == "linux" && (options.GOARCH == "mips" || options.GOARCH == "mipsle") { + if name == "atomic.go" { + // 64-bit atomic operations aren't currently supported on MIPS. + continue + } + } if options.Target == "simavr" { // Not all tests are currently supported on AVR. // Skip the ones that aren't. diff --git a/src/internal/task/task_stack_mipsx.S b/src/internal/task/task_stack_mipsx.S new file mode 100644 index 0000000000..903a847c74 --- /dev/null +++ b/src/internal/task/task_stack_mipsx.S @@ -0,0 +1,66 @@ +.section .text.tinygo_startTask +.global tinygo_startTask +.type tinygo_startTask, %function +tinygo_startTask: + // Small assembly stub for starting a goroutine. This is already run on the + // new stack, with the callee-saved registers already loaded. + // Most importantly, s0 contains the pc of the to-be-started function and s1 + // contains the only argument it is given. Multiple arguments are packed + // into one by storing them in a new allocation. + + // Set the first argument of the goroutine start wrapper, which contains all + // the arguments. + move $a0, $s1 + + // Branch to the "goroutine start" function. Use jalr to write the return + // address to ra so we'll return here after the goroutine exits. + jalr $s0 + nop + + // After return, exit this goroutine. This is a tail call. + j tinygo_pause + nop + +.section .text.tinygo_swapTask +.global tinygo_swapTask +.type tinygo_swapTask, %function +tinygo_swapTask: + // This function gets the following parameters: + // a0 = newStack uintptr + // a1 = oldStack *uintptr + + // Push all callee-saved registers. + addiu $sp, $sp, -40 + sw $ra, 36($sp) + sw $s8, 32($sp) + sw $s7, 28($sp) + sw $s6, 24($sp) + sw $s5, 20($sp) + sw $s4, 16($sp) + sw $s3, 12($sp) + sw $s2, 8($sp) + sw $s1, 4($sp) + sw $s0, ($sp) + + // Save the current stack pointer in oldStack. + sw $sp, 0($a1) + + // Switch to the new stack pointer. + move $sp, $a0 + + // Pop all saved registers from this new stack. + lw $ra, 36($sp) + lw $s8, 32($sp) + lw $s7, 28($sp) + lw $s6, 24($sp) + lw $s5, 20($sp) + lw $s4, 16($sp) + lw $s3, 12($sp) + lw $s2, 8($sp) + lw $s1, 4($sp) + lw $s0, ($sp) + addiu $sp, $sp, 40 + + // Return into the task. + jalr $ra + nop diff --git a/src/internal/task/task_stack_mipsx.go b/src/internal/task/task_stack_mipsx.go new file mode 100644 index 0000000000..2a7fb5cbf0 --- /dev/null +++ b/src/internal/task/task_stack_mipsx.go @@ -0,0 +1,61 @@ +//go:build scheduler.tasks && (mips || mipsle) + +package task + +import "unsafe" + +var systemStack uintptr + +// calleeSavedRegs is the list of registers that must be saved and restored when +// switching between tasks. Also see task_stack_mips.S that relies on the exact +// layout of this struct. +type calleeSavedRegs struct { + s0 uintptr + s1 uintptr + s2 uintptr + s3 uintptr + s4 uintptr + s5 uintptr + s6 uintptr + s7 uintptr + s8 uintptr + ra uintptr +} + +// archInit runs architecture-specific setup for the goroutine startup. +func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) { + // Store the initial sp for the startTask function (implemented in assembly). + s.sp = uintptr(unsafe.Pointer(r)) + + // Initialize the registers. + // These will be popped off of the stack on the first resume of the goroutine. + + // Start the function at tinygo_startTask (defined in src/internal/task/task_stack_mipsle.S). + // This assembly code calls a function (passed in s0) with a single argument + // (passed in s1). After the function returns, it calls Pause(). + r.ra = uintptr(unsafe.Pointer(&startTask)) + + // Pass the function to call in s0. + // This function is a compiler-generated wrapper which loads arguments out of a struct pointer. + // See createGoroutineStartWrapper (defined in compiler/goroutine.go) for more information. + r.s0 = fn + + // Pass the pointer to the arguments struct in s1. + r.s1 = uintptr(args) +} + +func (s *state) resume() { + swapTask(s.sp, &systemStack) +} + +func (s *state) pause() { + newStack := systemStack + systemStack = 0 + swapTask(newStack, &s.sp) +} + +// SystemStack returns the system stack pointer when called from a task stack. +// When called from the system stack, it returns 0. +func SystemStack() uintptr { + return systemStack +} diff --git a/src/runtime/arch_386.go b/src/runtime/arch_386.go index c4dbdf1661..4e9cce72ba 100644 --- a/src/runtime/arch_386.go +++ b/src/runtime/arch_386.go @@ -9,6 +9,8 @@ const deferExtraRegs = 0 const callInstSize = 5 // "call someFunction" is 5 bytes +const linux_MAP_ANONYMOUS = 0x20 + // Align on word boundary. func align(ptr uintptr) uintptr { return (ptr + 15) &^ 15 diff --git a/src/runtime/arch_amd64.go b/src/runtime/arch_amd64.go index 996476013b..3bb03e3c71 100644 --- a/src/runtime/arch_amd64.go +++ b/src/runtime/arch_amd64.go @@ -9,6 +9,8 @@ const deferExtraRegs = 0 const callInstSize = 5 // "call someFunction" is 5 bytes +const linux_MAP_ANONYMOUS = 0x20 + // Align a pointer. // Note that some amd64 instructions (like movaps) expect 16-byte aligned // memory, thus the result must be 16-byte aligned. diff --git a/src/runtime/arch_arm.go b/src/runtime/arch_arm.go index 33a7513b0a..e28e854102 100644 --- a/src/runtime/arch_arm.go +++ b/src/runtime/arch_arm.go @@ -11,6 +11,8 @@ const deferExtraRegs = 0 const callInstSize = 4 // "bl someFunction" is 4 bytes +const linux_MAP_ANONYMOUS = 0x20 + // Align on the maximum alignment for this platform (double). func align(ptr uintptr) uintptr { return (ptr + 7) &^ 7 diff --git a/src/runtime/arch_arm64.go b/src/runtime/arch_arm64.go index ac28ab7c3d..4e798e36b1 100644 --- a/src/runtime/arch_arm64.go +++ b/src/runtime/arch_arm64.go @@ -9,6 +9,8 @@ const deferExtraRegs = 0 const callInstSize = 4 // "bl someFunction" is 4 bytes +const linux_MAP_ANONYMOUS = 0x20 + // Align on word boundary. func align(ptr uintptr) uintptr { return (ptr + 15) &^ 15 diff --git a/src/runtime/arch_mips.go b/src/runtime/arch_mips.go new file mode 100644 index 0000000000..bfaf890ae5 --- /dev/null +++ b/src/runtime/arch_mips.go @@ -0,0 +1,21 @@ +package runtime + +const GOARCH = "mips" + +// The bitness of the CPU (e.g. 8, 32, 64). +const TargetBits = 32 + +const deferExtraRegs = 0 + +const callInstSize = 8 // "jal someFunc" is 4 bytes, plus a MIPS delay slot + +const linux_MAP_ANONYMOUS = 0x800 + +// It appears that MIPS has a maximum alignment of 8 bytes. +func align(ptr uintptr) uintptr { + return (ptr + 7) &^ 7 +} + +func getCurrentStackPointer() uintptr { + return uintptr(stacksave()) +} diff --git a/src/runtime/arch_mipsle.go b/src/runtime/arch_mipsle.go new file mode 100644 index 0000000000..b6bf7d5169 --- /dev/null +++ b/src/runtime/arch_mipsle.go @@ -0,0 +1,21 @@ +package runtime + +const GOARCH = "mipsle" + +// The bitness of the CPU (e.g. 8, 32, 64). +const TargetBits = 32 + +const deferExtraRegs = 0 + +const callInstSize = 8 // "jal someFunc" is 4 bytes, plus a MIPS delay slot + +const linux_MAP_ANONYMOUS = 0x800 + +// It appears that MIPS has a maximum alignment of 8 bytes. +func align(ptr uintptr) uintptr { + return (ptr + 7) &^ 7 +} + +func getCurrentStackPointer() uintptr { + return uintptr(stacksave()) +} diff --git a/src/runtime/asm_mipsx.S b/src/runtime/asm_mipsx.S new file mode 100644 index 0000000000..e380643645 --- /dev/null +++ b/src/runtime/asm_mipsx.S @@ -0,0 +1,40 @@ +.section .text.tinygo_scanCurrentStack +.global tinygo_scanCurrentStack +.type tinygo_scanCurrentStack, %function +tinygo_scanCurrentStack: + // Push callee-saved registers onto the stack. + addiu $sp, $sp, -40 + sw $ra, 36($sp) + sw $s8, 32($sp) + sw $s7, 28($sp) + sw $s6, 24($sp) + sw $s5, 20($sp) + sw $s4, 16($sp) + sw $s3, 12($sp) + sw $s2, 8($sp) + sw $s1, 4($sp) + sw $s0, ($sp) + + // Scan the stack. + jal tinygo_scanstack + move $a0, $sp // in the branch delay slot + + // Restore return address. + lw $ra, 36($sp) + + // Restore stack state. + addiu $sp, $sp, 40 + + // Return to the caller. + jalr $ra + nop + +.section .text.tinygo_longjmp +.global tinygo_longjmp +tinygo_longjmp: + // Note: the code we jump to assumes a0 is non-zero, which is already the + // case because that's the defer frame pointer. + lw $sp, 0($a0) // jumpSP + lw $a1, 4($a0) // jumpPC + jr $a1 + nop diff --git a/src/runtime/os_linux.go b/src/runtime/os_linux.go index 7218ea653a..403f00246a 100644 --- a/src/runtime/os_linux.go +++ b/src/runtime/os_linux.go @@ -14,7 +14,7 @@ const ( flag_PROT_READ = 0x1 flag_PROT_WRITE = 0x2 flag_MAP_PRIVATE = 0x2 - flag_MAP_ANONYMOUS = 0x20 + flag_MAP_ANONYMOUS = linux_MAP_ANONYMOUS // different on alpha, hppa, mips, xtensa ) // Source: https://github.com/torvalds/linux/blob/master/include/uapi/linux/time.h diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index 50eb11dead..8c5a42ff7c 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -229,6 +229,9 @@ func preinit() { // heap size. // This can happen on 32-bit systems. heapMaxSize /= 2 + if heapMaxSize < 4096 { + runtimePanic("cannot allocate heap memory") + } continue } heapStart = uintptr(addr) From b318a941a4d5ffd22ff227606b9e74822901c8ef Mon Sep 17 00:00:00 2001 From: leongross Date: Tue, 23 Jul 2024 11:50:35 +0200 Subject: [PATCH 113/444] add Fork and Exec libc hooks Signed-off-by: leongross --- src/syscall/syscall_libc.go | 41 +++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index 07a8acb0b2..0dec4c74d5 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -172,6 +172,37 @@ func Chown(path string, uid, gid int) (err error) { return } +func Fork() (err error) { + fail := int(libc_fork()) + if fail < 0 { + err = getErrno() + } + return +} + +func Execve(pathname string, argv []string, envv []string) (err error) { + argv0 := cstring(pathname) + + // transform argv and envv into the format expected by execve + argv1 := make([]*byte, len(argv)+1) + for i, arg := range argv { + argv1[i] = &cstring(arg)[0] + } + argv1[len(argv)] = nil + + env1 := make([]*byte, len(envv)+1) + for i, env := range envv { + env1[i] = &cstring(env)[0] + } + env1[len(envv)] = nil + + fail := int(libc_execve(&argv0[0], &argv1[0], &env1[0])) + if fail < 0 { + err = getErrno() + } + return +} + func Faccessat(dirfd int, path string, mode uint32, flags int) (err error) func Kill(pid int, sig Signal) (err error) { @@ -410,3 +441,13 @@ func libc_readlink(path *byte, buf *byte, count uint) int // //export unlink func libc_unlink(pathname *byte) int32 + +// pid_t fork(void); +// +//export fork +func libc_fork() int32 + +// int execve(const char *filename, char *const argv[], char *const envp[]); +// +//export execve +func libc_execve(filename *byte, argv **byte, envp **byte) int From 61d36fb3f8b29cd412ea98443be7691933af07b7 Mon Sep 17 00:00:00 2001 From: Laurent Demailly Date: Sun, 21 Jul 2024 10:55:54 -0700 Subject: [PATCH 114/444] Add missing T.Deadline --- src/testing/testing.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/testing/testing.go b/src/testing/testing.go index 8429e92212..9058892d28 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -463,10 +463,23 @@ func (t *T) Run(name string, f func(t *T)) bool { return !sub.failed } +// Deadline reports the time at which the test binary will have +// exceeded the timeout specified by the -timeout flag. +// +// The ok result is false if the -timeout flag indicates “no timeout” (0). +// For now tinygo always return 0, false. +// +// Not Implemented. +func (t *T) Deadline() (deadline time.Time, ok bool) { + deadline = t.context.deadline + return deadline, !deadline.IsZero() +} + // testContext holds all fields that are common to all tests. This includes // synchronization primitives to run at most *parallel tests. type testContext struct { - match *matcher + match *matcher + deadline time.Time } func newTestContext(m *matcher) *testContext { From 1fd75ffbda1ff021289f2feb6a062434df68d8b7 Mon Sep 17 00:00:00 2001 From: Laurent Demailly Date: Tue, 23 Jul 2024 15:30:02 -0700 Subject: [PATCH 115/444] Getting DeepEqual to work with maps whose key is an interface (#4360) reflect: Getting DeepEqual to work with maps whose key is an interface --- src/reflect/all_test.go | 16 ++++++++++++++++ src/reflect/value.go | 4 +++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index 4c2fcc48be..8a3e09792e 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -1102,6 +1102,20 @@ func init() { cycleMap3["different"] = cycleMap3 } +type deepEqualInterface interface { + Foo() +} +type deepEqualConcrete struct { + int +} + +func (deepEqualConcrete) Foo() {} + +var ( + deepEqualConcrete1 = deepEqualConcrete{1} + deepEqualConcrete2 = deepEqualConcrete{2} +) + var deepEqualTests = []DeepEqualTest{ // Equalities {nil, nil, true}, @@ -1119,6 +1133,7 @@ var deepEqualTests = []DeepEqualTest{ {[]byte{1, 2, 3}, []byte{1, 2, 3}, true}, {[]MyByte{1, 2, 3}, []MyByte{1, 2, 3}, true}, {MyBytes{1, 2, 3}, MyBytes{1, 2, 3}, true}, + {map[deepEqualInterface]string{deepEqualConcrete1: "a"}, map[deepEqualInterface]string{deepEqualConcrete1: "a"}, true}, // Inequalities {1, 2, false}, @@ -1140,6 +1155,7 @@ var deepEqualTests = []DeepEqualTest{ {fn3, fn3, false}, {[][]int{{1}}, [][]int{{2}}, false}, {&structWithSelfPtr{p: &structWithSelfPtr{s: "a"}}, &structWithSelfPtr{p: &structWithSelfPtr{s: "b"}}, false}, + {map[deepEqualInterface]string{deepEqualConcrete1: "a"}, map[deepEqualInterface]string{deepEqualConcrete2: "a"}, false}, // Fun with floating point. {math.NaN(), math.NaN(), false}, diff --git a/src/reflect/value.go b/src/reflect/value.go index c8439ccef2..07553fbc5a 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -982,7 +982,9 @@ func (v Value) MapIndex(key Value) Value { vkey := v.typecode.key() // compare key type with actual key type of map - if !key.typecode.AssignableTo(vkey) { + // AssignableTo is not implemented for interfaces + // avoid: "reflect: unimplemented: AssignableTo with interface" + if vkey.Kind() != Interface && !key.typecode.AssignableTo(vkey) { // type error? panic("reflect.Value.MapIndex: incompatible types for key") } From 6184a6cd35d04dac86e1cbb33bef686c9d7087de Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 26 Jul 2024 16:56:42 +0200 Subject: [PATCH 116/444] Revert "Getting DeepEqual to work with maps whose key is an interface (#4360)" This reverts commit 1fd75ffbda1ff021289f2feb6a062434df68d8b7. The change is not correct and allows code to continue while it should panic. For details, see: https://github.com/tinygo-org/tinygo/pull/4360#issuecomment-2252943012 --- src/reflect/all_test.go | 16 ---------------- src/reflect/value.go | 4 +--- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index 8a3e09792e..4c2fcc48be 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -1102,20 +1102,6 @@ func init() { cycleMap3["different"] = cycleMap3 } -type deepEqualInterface interface { - Foo() -} -type deepEqualConcrete struct { - int -} - -func (deepEqualConcrete) Foo() {} - -var ( - deepEqualConcrete1 = deepEqualConcrete{1} - deepEqualConcrete2 = deepEqualConcrete{2} -) - var deepEqualTests = []DeepEqualTest{ // Equalities {nil, nil, true}, @@ -1133,7 +1119,6 @@ var deepEqualTests = []DeepEqualTest{ {[]byte{1, 2, 3}, []byte{1, 2, 3}, true}, {[]MyByte{1, 2, 3}, []MyByte{1, 2, 3}, true}, {MyBytes{1, 2, 3}, MyBytes{1, 2, 3}, true}, - {map[deepEqualInterface]string{deepEqualConcrete1: "a"}, map[deepEqualInterface]string{deepEqualConcrete1: "a"}, true}, // Inequalities {1, 2, false}, @@ -1155,7 +1140,6 @@ var deepEqualTests = []DeepEqualTest{ {fn3, fn3, false}, {[][]int{{1}}, [][]int{{2}}, false}, {&structWithSelfPtr{p: &structWithSelfPtr{s: "a"}}, &structWithSelfPtr{p: &structWithSelfPtr{s: "b"}}, false}, - {map[deepEqualInterface]string{deepEqualConcrete1: "a"}, map[deepEqualInterface]string{deepEqualConcrete2: "a"}, false}, // Fun with floating point. {math.NaN(), math.NaN(), false}, diff --git a/src/reflect/value.go b/src/reflect/value.go index 07553fbc5a..c8439ccef2 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -982,9 +982,7 @@ func (v Value) MapIndex(key Value) Value { vkey := v.typecode.key() // compare key type with actual key type of map - // AssignableTo is not implemented for interfaces - // avoid: "reflect: unimplemented: AssignableTo with interface" - if vkey.Kind() != Interface && !key.typecode.AssignableTo(vkey) { + if !key.typecode.AssignableTo(vkey) { // type error? panic("reflect.Value.MapIndex: incompatible types for key") } From 84c376160f7a127d1beecf3f09f0f8e5caff641c Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 28 Jul 2024 10:48:31 +0200 Subject: [PATCH 117/444] transform: use thinlto-pre-link passes This improves compilation performance by about 5% in my quick test, while increasing binary size on average by 0.13% when comparing the smoke tests in the drivers repo (and about two thirds of that 0.13% is actually caused by a single smoke test). I think this is a good idea because it aligns the TinyGo optimization sequence with what ThinLTO expects. --- transform/optimizer.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/transform/optimizer.go b/transform/optimizer.go index 05533b6a4a..54f9762bc4 100644 --- a/transform/optimizer.go +++ b/transform/optimizer.go @@ -142,11 +142,13 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error { fn.SetLinkage(llvm.InternalLinkage) } - // Run the default pass pipeline. - // TODO: set the PrepareForThinLTO flag somehow. + // Run the ThinLTO pre-link passes, meant to be run on each individual + // module. This saves compilation time compared to "default<#>" and is meant + // to better match the optimization passes that are happening during + // ThinLTO. po := llvm.NewPassBuilderOptions() defer po.Dispose() - passes := fmt.Sprintf("default<%s>", optLevel) + passes := fmt.Sprintf("thinlto-pre-link<%s>", optLevel) err := mod.RunPasses(passes, llvm.TargetMachine{}, po) if err != nil { return []error{fmt.Errorf("could not build pass pipeline: %w", err)} From 88f9fc3ce2802c8d79bb8205ef5d6a1be5391186 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 28 Jul 2024 19:00:00 +0200 Subject: [PATCH 118/444] reflect: return correct name for unsafe.Pointer type For some reason, the type kind name is "unsafe.Pointer" while the .Name() method just returns "Pointer". Not sure why this difference exists, but to be able to test .Name() in testdata/reflect.go it needs to match. --- src/reflect/type.go | 4 +- testdata/reflect.go | 3 + testdata/reflect.txt | 182 +++++++++++++++++++++---------------------- 3 files changed, 97 insertions(+), 92 deletions(-) diff --git a/src/reflect/type.go b/src/reflect/type.go index 1356f67cdd..1ac509cbbf 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -1060,8 +1060,10 @@ func (t *rawType) Name() string { panic("corrupt name data") } - if t.Kind() <= UnsafePointer { + if kind := t.Kind(); kind < UnsafePointer { return t.Kind().String() + } else if kind == UnsafePointer { + return "Pointer" } return "" diff --git a/testdata/reflect.go b/testdata/reflect.go index 595aaa8cfa..6971866dbd 100644 --- a/testdata/reflect.go +++ b/testdata/reflect.go @@ -423,6 +423,9 @@ func showValue(rv reflect.Value, indent string) { if !rt.Comparable() { print(" comparable=false") } + if name := rt.Name(); name != "" { + print(" name=", name) + } println() switch rt.Kind() { case reflect.Bool: diff --git a/testdata/reflect.txt b/testdata/reflect.txt index 3f1b5a17bf..90ac42ac98 100644 --- a/testdata/reflect.txt +++ b/testdata/reflect.txt @@ -7,140 +7,140 @@ false false values of interfaces -reflect type: bool +reflect type: bool name=bool bool: true -reflect type: bool +reflect type: bool name=bool bool: false -reflect type: int +reflect type: int name=int int: 2000 -reflect type: int +reflect type: int name=int int: -2000 -reflect type: uint +reflect type: uint name=uint uint: 2000 -reflect type: int8 +reflect type: int8 name=int8 int: -3 -reflect type: int8 +reflect type: int8 name=int8 int: 3 -reflect type: uint8 +reflect type: uint8 name=uint8 uint: 200 -reflect type: int16 +reflect type: int16 name=int16 int: -300 -reflect type: int16 +reflect type: int16 name=int16 int: 300 -reflect type: uint16 +reflect type: uint16 name=uint16 uint: 50000 -reflect type: int32 +reflect type: int32 name=int32 int: 7340032 -reflect type: int32 +reflect type: int32 name=int32 int: -7340032 -reflect type: uint32 +reflect type: uint32 name=uint32 uint: 7340032 -reflect type: int64 +reflect type: int64 name=int64 int: 9895604649984 -reflect type: int64 +reflect type: int64 name=int64 int: -9895604649984 -reflect type: uint64 +reflect type: uint64 name=uint64 uint: 9895604649984 -reflect type: uintptr +reflect type: uintptr name=uintptr uint: 12345 -reflect type: float32 +reflect type: float32 name=float32 float: +3.140000e+000 -reflect type: float64 +reflect type: float64 name=float64 float: +3.140000e+000 -reflect type: complex64 +reflect type: complex64 name=complex64 complex: (+1.200000e+000+3.000000e-001i) -reflect type: complex128 +reflect type: complex128 name=complex128 complex: (+1.300000e+000+4.000000e-001i) -reflect type: int +reflect type: int name=myint int: 32 -reflect type: string +reflect type: string name=string string: foo 3 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 102 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 111 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 111 -reflect type: unsafe.Pointer +reflect type: unsafe.Pointer name=Pointer pointer: true reflect type: chan chan: int nil: true -reflect type: chan +reflect type: chan name=mychan chan: int nil: true reflect type: ptr pointer: true int nil: false - reflect type: int settable=true addrable=true + reflect type: int settable=true addrable=true name=int int: 0 reflect type: ptr pointer: true interface nil: false - reflect type: interface settable=true addrable=true + reflect type: interface settable=true addrable=true name=error interface nil: true NumMethod: 1 reflect type: ptr pointer: true int nil: false - reflect type: int settable=true addrable=true + reflect type: int settable=true addrable=true name=int int: 42 -reflect type: ptr +reflect type: ptr name=myptr pointer: true int nil: false - reflect type: int settable=true addrable=true + reflect type: int settable=true addrable=true name=int int: 0 reflect type: slice comparable=false slice: uint8 3 3 pointer: true nil: false indexing: 0 - reflect type: uint8 settable=true addrable=true + reflect type: uint8 settable=true addrable=true name=uint8 uint: 1 indexing: 1 - reflect type: uint8 settable=true addrable=true + reflect type: uint8 settable=true addrable=true name=uint8 uint: 2 indexing: 2 - reflect type: uint8 settable=true addrable=true + reflect type: uint8 settable=true addrable=true name=uint8 uint: 3 reflect type: slice comparable=false slice: uint8 2 5 pointer: true nil: false indexing: 0 - reflect type: uint8 settable=true addrable=true + reflect type: uint8 settable=true addrable=true name=uint8 uint: 0 indexing: 1 - reflect type: uint8 settable=true addrable=true + reflect type: uint8 settable=true addrable=true name=uint8 uint: 0 reflect type: slice comparable=false slice: int32 2 2 pointer: true nil: false indexing: 0 - reflect type: int32 settable=true addrable=true + reflect type: int32 settable=true addrable=true name=int32 int: 3 indexing: 1 - reflect type: int32 settable=true addrable=true + reflect type: int32 settable=true addrable=true name=int32 int: 5 reflect type: slice comparable=false slice: string 2 2 pointer: true nil: false indexing: 0 - reflect type: string settable=true addrable=true + reflect type: string settable=true addrable=true name=string string: xyz 3 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 120 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 121 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 122 indexing: 1 - reflect type: string settable=true addrable=true + reflect type: string settable=true addrable=true name=string string: Z 1 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 90 reflect type: slice comparable=false slice: uint8 0 0 @@ -155,67 +155,67 @@ reflect type: slice comparable=false pointer: true nil: false indexing: 0 - reflect type: float32 settable=true addrable=true + reflect type: float32 settable=true addrable=true name=float32 float: +1.000000e+000 indexing: 1 - reflect type: float32 settable=true addrable=true + reflect type: float32 settable=true addrable=true name=float32 float: +1.320000e+000 reflect type: slice comparable=false slice: float64 2 2 pointer: true nil: false indexing: 0 - reflect type: float64 settable=true addrable=true + reflect type: float64 settable=true addrable=true name=float64 float: +1.000000e+000 indexing: 1 - reflect type: float64 settable=true addrable=true + reflect type: float64 settable=true addrable=true name=float64 float: +1.640000e+000 reflect type: slice comparable=false slice: complex64 2 2 pointer: true nil: false indexing: 0 - reflect type: complex64 settable=true addrable=true + reflect type: complex64 settable=true addrable=true name=complex64 complex: (+1.000000e+000+0.000000e+000i) indexing: 1 - reflect type: complex64 settable=true addrable=true + reflect type: complex64 settable=true addrable=true name=complex64 complex: (+1.640000e+000+3.000000e-001i) reflect type: slice comparable=false slice: complex128 2 2 pointer: true nil: false indexing: 0 - reflect type: complex128 settable=true addrable=true + reflect type: complex128 settable=true addrable=true name=complex128 complex: (+1.000000e+000+0.000000e+000i) indexing: 1 - reflect type: complex128 settable=true addrable=true + reflect type: complex128 settable=true addrable=true name=complex128 complex: (+1.128000e+000+4.000000e-001i) -reflect type: slice comparable=false +reflect type: slice comparable=false name=myslice slice: uint8 3 3 pointer: true nil: false indexing: 0 - reflect type: uint8 settable=true addrable=true + reflect type: uint8 settable=true addrable=true name=uint8 uint: 5 indexing: 1 - reflect type: uint8 settable=true addrable=true + reflect type: uint8 settable=true addrable=true name=uint8 uint: 3 indexing: 2 - reflect type: uint8 settable=true addrable=true + reflect type: uint8 settable=true addrable=true name=uint8 uint: 11 reflect type: array array: 3 int64 24 - reflect type: int64 + reflect type: int64 name=int64 int: 5 - reflect type: int64 + reflect type: int64 name=int64 int: 8 - reflect type: int64 + reflect type: int64 name=int64 int: 2 reflect type: array array: 2 uint8 2 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 3 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 5 reflect type: func comparable=false func @@ -238,7 +238,7 @@ reflect type: struct tag: "" embedded: true exported: false - reflect type: interface caninterface=false + reflect type: interface caninterface=false name=error interface nil: true NumMethod: 1 @@ -249,51 +249,51 @@ reflect type: struct tag: "" embedded: false exported: false - reflect type: uint8 caninterface=false + reflect type: uint8 caninterface=false name=uint8 uint: 42 field: 1 b pkg: main tag: "" embedded: false exported: false - reflect type: int16 caninterface=false + reflect type: int16 caninterface=false name=int16 int: 321 field: 2 c pkg: main tag: "" embedded: false exported: false - reflect type: int8 caninterface=false + reflect type: int8 caninterface=false name=int8 int: 123 -reflect type: struct comparable=false +reflect type: struct comparable=false name=mystruct struct: 5 field: 0 n pkg: main tag: "foo:\"bar\"" embedded: false exported: false - reflect type: int caninterface=false + reflect type: int caninterface=false name=int int: 5 field: 1 some pkg: main tag: "some\x00tag" embedded: false exported: false - reflect type: struct caninterface=false + reflect type: struct caninterface=false name=point struct: 2 field: 0 X pkg: tag: "" embedded: false exported: true - reflect type: int16 caninterface=false + reflect type: int16 caninterface=false name=int16 int: -5 field: 1 Y pkg: tag: "" embedded: false exported: true - reflect type: int16 caninterface=false + reflect type: int16 caninterface=false name=int16 int: 3 field: 2 zero pkg: main @@ -312,10 +312,10 @@ reflect type: struct comparable=false pointer: true nil: false indexing: 0 - reflect type: uint8 addrable=true caninterface=false + reflect type: uint8 addrable=true caninterface=false name=uint8 uint: 71 indexing: 1 - reflect type: uint8 addrable=true caninterface=false + reflect type: uint8 addrable=true caninterface=false name=uint8 uint: 111 field: 4 Buf pkg: @@ -327,12 +327,12 @@ reflect type: struct comparable=false pointer: true nil: false indexing: 0 - reflect type: uint8 settable=true addrable=true + reflect type: uint8 settable=true addrable=true name=uint8 uint: 88 reflect type: ptr pointer: true struct nil: false - reflect type: struct settable=true addrable=true + reflect type: struct settable=true addrable=true name=linkedList struct: 2 field: 0 next pkg: main @@ -347,7 +347,7 @@ reflect type: ptr tag: "" embedded: false exported: false - reflect type: int addrable=true caninterface=false + reflect type: int addrable=true caninterface=false name=int int: 42 reflect type: struct struct: 2 @@ -356,14 +356,14 @@ reflect type: struct tag: "" embedded: false exported: true - reflect type: uintptr + reflect type: uintptr name=uintptr uint: 2 field: 1 B pkg: tag: "" embedded: false exported: true - reflect type: uintptr + reflect type: uintptr name=uintptr uint: 3 reflect type: slice comparable=false slice: interface 3 3 @@ -374,52 +374,52 @@ reflect type: slice comparable=false interface nil: false NumMethod: 0 - reflect type: int + reflect type: int name=int int: 3 indexing: 1 reflect type: interface settable=true addrable=true interface nil: false NumMethod: 0 - reflect type: string + reflect type: string name=string string: str 3 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 115 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 116 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 114 indexing: 2 reflect type: interface settable=true addrable=true interface nil: false NumMethod: 0 - reflect type: complex128 + reflect type: complex128 name=complex128 complex: (-4.000000e+000+2.500000e+000i) reflect type: ptr pointer: true int8 nil: false - reflect type: int8 settable=true addrable=true + reflect type: int8 settable=true addrable=true name=int8 int: 5 reflect type: ptr pointer: true int16 nil: false - reflect type: int16 settable=true addrable=true + reflect type: int16 settable=true addrable=true name=int16 int: -800 reflect type: ptr pointer: true int32 nil: false - reflect type: int32 settable=true addrable=true + reflect type: int32 settable=true addrable=true name=int32 int: 100000000 reflect type: ptr pointer: true int64 nil: false - reflect type: int64 settable=true addrable=true + reflect type: int64 settable=true addrable=true name=int64 int: -1000000000000 reflect type: ptr pointer: true complex128 nil: false - reflect type: complex128 settable=true addrable=true + reflect type: complex128 settable=true addrable=true name=complex128 complex: (-8.000000e+000-2.000000e+006i) sizes: From 417a26d20c23ea0aaa8f95325bb30172b3e3e9e9 Mon Sep 17 00:00:00 2001 From: "L. Pereira" Date: Wed, 31 Jul 2024 13:20:12 -0700 Subject: [PATCH 119/444] runtime: Simplify slice growing/appending code (#4287) * reflect: rawFieldByNameFunc: copy index slice to avoid later overwrites * runtime: Simplify slice growing/appending code Refactor the slice appending function to rely on the slice growing function, and remove branches/loops to use a branchfree variant. Signed-off-by: L. Pereira * runtime: Remove one branch in sliceAppend() Both branches were equivalent, so guard the overall logic in sliceAppend() with the more general condition. Signed-off-by: L. Pereira * runtime: Simplify slice growing calculation Use `bits.Len()` rather than `32 - bits.LeadingZeros32()`. They're equivalent, but the Len version is a bit easier to read. Signed-off-by: L. Pereira * reflect: Always call sliceGrow() in extendSlice() sliceGrow() will return the old slice if its capacity is large enough. Signed-off-by: L. Pereira --------- Signed-off-by: L. Pereira Co-authored-by: Damian Gryski --- src/reflect/type.go | 4 +-- src/reflect/value.go | 13 +--------- src/runtime/slice.go | 61 +++++++++++++------------------------------- testdata/slice.txt | 4 +-- 4 files changed, 23 insertions(+), 59 deletions(-) diff --git a/src/reflect/type.go b/src/reflect/type.go index 1ac509cbbf..1a0d4fc854 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -774,7 +774,7 @@ func (t *rawType) rawFieldByNameFunc(match func(string) bool) (rawStructField, [ if match(name) { found = append(found, result{ rawStructFieldFromPointer(descriptor, field.fieldType, data, flagsByte, name, offset), - append(ll.index, int(i)), + append(ll.index[:len(ll.index):len(ll.index)], int(i)), }) } @@ -787,7 +787,7 @@ func (t *rawType) rawFieldByNameFunc(match func(string) bool) (rawStructField, [ nextlevel = append(nextlevel, fieldWalker{ t: embedded, - index: append(ll.index, int(i)), + index: append(ll.index[:len(ll.index):len(ll.index)], int(i)), }) } diff --git a/src/reflect/value.go b/src/reflect/value.go index c8439ccef2..56c0dc10d2 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -1712,18 +1712,7 @@ func extendSlice(v Value, n int) sliceHeader { old = *(*sliceHeader)(v.value) } - var nbuf unsafe.Pointer - var nlen, ncap uintptr - - if old.len+uintptr(n) > old.cap { - // we need to grow the slice - nbuf, nlen, ncap = sliceGrow(old.data, old.len, old.cap, old.cap+uintptr(n), v.typecode.elem().Size()) - } else { - // we can reuse the slice we have - nbuf = old.data - nlen = old.len - ncap = old.cap - } + nbuf, nlen, ncap := sliceGrow(old.data, old.len, old.cap, old.len+uintptr(n), v.typecode.elem().Size()) return sliceHeader{ data: nbuf, diff --git a/src/runtime/slice.go b/src/runtime/slice.go index 2269047a8c..7d804b11be 100644 --- a/src/runtime/slice.go +++ b/src/runtime/slice.go @@ -3,42 +3,24 @@ package runtime // This file implements compiler builtins for slices: append() and copy(). import ( + "math/bits" "unsafe" ) // Builtin append(src, elements...) function: append elements to src and return // the modified (possibly expanded) slice. -func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen uintptr, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) { - if elemsLen == 0 { - // Nothing to append, return the input slice. - return srcBuf, srcLen, srcCap +func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) { + newLen := srcLen + elemsLen + if elemsLen > 0 { + // Allocate a new slice with capacity for elemsLen more elements, if necessary; + // otherwise, reuse the passed slice. + srcBuf, _, srcCap = sliceGrow(srcBuf, srcLen, srcCap, newLen, elemSize) + + // Append the new elements in-place. + memmove(unsafe.Add(srcBuf, srcLen*elemSize), elemsBuf, elemsLen*elemSize) } - if srcLen+elemsLen > srcCap { - // Slice does not fit, allocate a new buffer that's large enough. - srcCap = srcCap * 2 - if srcCap == 0 { // e.g. zero slice - srcCap = 1 - } - for srcLen+elemsLen > srcCap { - // This algorithm may be made more memory-efficient: don't multiply - // by two but by 1.5 or something. As far as I can see, that's - // allowed by the Go language specification (but may be observed by - // programs). - srcCap *= 2 - } - buf := alloc(srcCap*elemSize, nil) - - // Copy the old slice to the new slice. - if srcLen != 0 { - memmove(buf, srcBuf, srcLen*elemSize) - } - srcBuf = buf - } - - // The slice fits (after possibly allocating a new one), append it in-place. - memmove(unsafe.Add(srcBuf, srcLen*elemSize), elemsBuf, elemsLen*elemSize) - return srcBuf, srcLen + elemsLen, srcCap + return srcBuf, newLen, srcCap } // Builtin copy(dst, src) function: copy bytes from dst to src. @@ -54,29 +36,22 @@ func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr // sliceGrow returns a new slice with space for at least newCap elements func sliceGrow(oldBuf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) { - - // TODO(dgryski): sliceGrow() and sliceAppend() should be refactored to share the base growth code. - if oldCap >= newCap { // No need to grow, return the input slice. return oldBuf, oldLen, oldCap } - // allow nil slice - if oldCap == 0 { - oldCap++ - } - - // grow capacity - for oldCap < newCap { - oldCap *= 2 - } + // This can be made more memory-efficient by multiplying by some other constant, such as 1.5, + // which seems to be allowed by the Go language specification (but this can be observed by + // programs); however, due to memory fragmentation and the current state of the TinyGo + // memory allocators, this causes some difficult to debug issues. + newCap = 1 << bits.Len(uint(newCap)) - buf := alloc(oldCap*elemSize, nil) + buf := alloc(newCap*elemSize, nil) if oldLen > 0 { // copy any data to new slice memmove(buf, oldBuf, oldLen*elemSize) } - return buf, oldLen, oldCap + return buf, oldLen, newCap } diff --git a/testdata/slice.txt b/testdata/slice.txt index d16a0bda9f..488170b52b 100644 --- a/testdata/slice.txt +++ b/testdata/slice.txt @@ -7,12 +7,12 @@ copy foo -> bar: 3 bar: len=3 cap=5 data: 1 2 4 slice is nil? true true grow: len=0 cap=0 data: -grow: len=1 cap=1 data: 42 +grow: len=1 cap=2 data: 42 grow: len=3 cap=4 data: 42 -1 -2 grow: len=7 cap=8 data: 42 -1 -2 1 2 4 5 grow: len=7 cap=8 data: 42 -1 -2 1 2 4 5 grow: len=14 cap=16 data: 42 -1 -2 1 2 4 5 42 -1 -2 1 2 4 5 -bytes: len=6 cap=6 data: 1 2 3 102 111 111 +bytes: len=6 cap=8 data: 1 2 3 102 111 111 slice to array pointer: 1 -2 20 4 unsafe.Add array: 1 5 8 4 unsafe.Slice array: 3 3 9 15 4 From a8a532f9d6201d76f7177e30b9abd367c37c1000 Mon Sep 17 00:00:00 2001 From: leongross Date: Fri, 2 Aug 2024 08:22:46 -0700 Subject: [PATCH 120/444] os: add file.Truncate --- src/os/file.go | 10 ------ src/os/file_unix.go | 19 ++++++++++++ src/os/file_windows.go | 14 +++++++++ src/os/truncate_test.go | 61 +++++++++++++++++++++++++++++++++++++ src/syscall/libc_wasip2.go | 10 ++++-- src/syscall/syscall_libc.go | 14 +++++++++ 6 files changed, 116 insertions(+), 12 deletions(-) create mode 100644 src/os/truncate_test.go diff --git a/src/os/file.go b/src/os/file.go index acf33f850d..7c3c0db125 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -290,16 +290,6 @@ func (f *File) Sync() (err error) { return } -// Truncate is a stub, not yet implemented -func (f *File) Truncate(size int64) (err error) { - if f.handle == nil { - err = ErrClosed - } else { - err = ErrNotImplemented - } - return &PathError{Op: "truncate", Path: f.name, Err: err} -} - // LinkError records an error during a link or symlink or rename system call and // the paths that caused it. type LinkError struct { diff --git a/src/os/file_unix.go b/src/os/file_unix.go index badfc71ff9..ef7abcbac8 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -125,6 +125,25 @@ func Readlink(name string) (string, error) { } } +// Truncate changes the size of the file. +// It does not change the I/O offset. +// If there is an error, it will be of type *PathError. +// Alternatively just use 'raw' syscall by file name +func (f *File) Truncate(size int64) (err error) { + if f.handle == nil { + return ErrClosed + } + + e := ignoringEINTR(func() error { + return syscall.Truncate(f.name, size) + }) + + if e != nil { + return &PathError{Op: "truncate", Path: f.name, Err: e} + } + return +} + // ReadAt reads up to len(b) bytes from the File starting at the given absolute offset. // It returns the number of bytes read and any error encountered, possibly io.EOF. // At end of file, Pread returns 0, io.EOF. diff --git a/src/os/file_windows.go b/src/os/file_windows.go index 70f3a3dd0c..5860c76b04 100644 --- a/src/os/file_windows.go +++ b/src/os/file_windows.go @@ -59,6 +59,10 @@ func Pipe() (r *File, w *File, err error) { return } +func (f *unixFileHandle) Truncate(size int64) error { + return ErrNotImplemented +} + func tempDir() string { n := uint32(syscall.MAX_PATH) for { @@ -106,6 +110,16 @@ func (f unixFileHandle) Sync() error { return ErrNotImplemented } +func (f *File) Truncate(size int64) error { + var err error + if f.handle == nil { + err = ErrClosed + } else { + err = ErrNotImplemented + } + return &PathError{Op: "truncate", Path: f.name, Err: err} +} + // isWindowsNulName reports whether name is os.DevNull ('NUL') on Windows. // True is returned if name is 'NUL' whatever the case. func isWindowsNulName(name string) bool { diff --git a/src/os/truncate_test.go b/src/os/truncate_test.go new file mode 100644 index 0000000000..2b1d982ba2 --- /dev/null +++ b/src/os/truncate_test.go @@ -0,0 +1,61 @@ +//go:build darwin || (linux && !baremetal && !js && !wasi) + +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package os_test + +import ( + . "os" + "path/filepath" + "runtime" + "testing" +) + +func TestTruncate(t *testing.T) { + // Truncate is not supported on Windows or wasi at the moment + if runtime.GOOS == "windows" || runtime.GOOS == "wasip1" || runtime.GOOS == "wasip2" { + t.Logf("skipping test on %s", runtime.GOOS) + return + } + + tmpDir := t.TempDir() + file := filepath.Join(tmpDir, "truncate_test") + + fd, err := Create(file) + if err != nil { + t.Fatalf("create %q: got %v, want nil", file, err) + } + defer fd.Close() + + // truncate up to 0x100 + if err := fd.Truncate(0x100); err != nil { + t.Fatalf("truncate %q: got %v, want nil", file, err) + } + + // check if size is 0x100 + fi, err := Stat(file) + if err != nil { + t.Fatalf("stat %q: got %v, want nil", file, err) + } + + if fi.Size() != 0x100 { + t.Fatalf("size of %q is %d; want 0x100", file, fi.Size()) + } + + // truncate down to 0x80 + if err := fd.Truncate(0x80); err != nil { + t.Fatalf("truncate %q: got %v, want nil", file, err) + } + + // check if size is 0x80 + fi, err = Stat(file) + if err != nil { + t.Fatalf("stat %q: got %v, want nil", file, err) + } + + if fi.Size() != 0x80 { + t.Fatalf("size of %q is %d; want 0x80", file, fi.Size()) + } +} diff --git a/src/syscall/libc_wasip2.go b/src/syscall/libc_wasip2.go index 1e985c1da9..3aadf877f3 100644 --- a/src/syscall/libc_wasip2.go +++ b/src/syscall/libc_wasip2.go @@ -432,7 +432,6 @@ func chmod(pathname *byte, mode uint32) int32 { // //export mkdir func mkdir(pathname *byte, mode uint32) int32 { - path := goString(pathname) dir, relPath := findPreopenForPath(path) if dir.d == cm.ResourceNone { @@ -773,7 +772,6 @@ var libcCWD wasiDir var wasiPreopens map[string]types.Descriptor func populatePreopens() { - var cwd string // find CWD @@ -1354,3 +1352,11 @@ func getcwd(buf *byte, size uint) *byte { copy(s, cwd) return buf } + +// int truncate(const char *path, off_t length); +// +//export truncate +func truncate(path *byte, length int64) int32 { + libcErrno = ENOSYS + return -1 +} diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index 0dec4c74d5..2321292d98 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -203,6 +203,15 @@ func Execve(pathname string, argv []string, envv []string) (err error) { return } +func Truncate(path string, length int64) (err error) { + data := cstring(path) + fail := int(libc_truncate(&data[0], length)) + if fail < 0 { + err = getErrno() + } + return +} + func Faccessat(dirfd int, path string, mode uint32, flags int) (err error) func Kill(pid int, sig Signal) (err error) { @@ -451,3 +460,8 @@ func libc_fork() int32 // //export execve func libc_execve(filename *byte, argv **byte, envp **byte) int + +// int truncate(const char *path, off_t length); +// +//export truncate +func libc_truncate(path *byte, length int64) int32 From 2d6d9eb76df322a7970dff5183f15ff8dc545301 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 4 Aug 2024 18:37:20 +0200 Subject: [PATCH 121/444] ci: don't include prebuilt libraries in the release These libraries will be automatically built when needed and cached. The main reason these were needed is for play.tinygo.org, but I've now prebuilt them there directly (so they don't need to be built for every tarball). --- GNUmakefile | 9 -------- builder/build.go | 10 ++++----- builder/builtins.go | 6 +++--- builder/library.go | 13 ------------ builder/mingw-w64.go | 2 +- builder/musl.go | 2 +- builder/picolibc.go | 4 ++-- builder/wasmbuiltins.go | 2 +- main.go | 46 +---------------------------------------- 9 files changed, 14 insertions(+), 80 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index f06f28a18c..d560ae82e3 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -867,9 +867,6 @@ build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN @mkdir -p build/release/tinygo/lib/wasi-libc/libc-top-half/musl/arch @mkdir -p build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @mkdir -p build/release/tinygo/lib/wasi-cli/ - @mkdir -p build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0 - @mkdir -p build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus - @mkdir -p build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4 @echo copying source files @cp -p build/tinygo$(EXE) build/release/tinygo/bin ifneq ($(USE_SYSTEM_BINARYEN),1) @@ -932,12 +929,6 @@ endif @cp -rp llvm-project/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt-builtins @cp -rp src build/release/tinygo/src @cp -rp targets build/release/tinygo/targets - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m0 -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0/compiler-rt compiler-rt - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m0plus -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus/compiler-rt compiler-rt - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m4 -o build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4/compiler-rt compiler-rt - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m0 -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0/picolibc picolibc - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m0plus -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus/picolibc picolibc - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m4 -o build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4/picolibc picolibc release: tar -czf build/release.tar.gz -C build/release tinygo diff --git a/builder/build.go b/builder/build.go index 49aad5c790..54f5c101be 100644 --- a/builder/build.go +++ b/builder/build.go @@ -148,7 +148,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe job := makeDarwinLibSystemJob(config, tmpdir) libcDependencies = append(libcDependencies, job) case "musl": - job, unlock, err := Musl.load(config, tmpdir) + job, unlock, err := libMusl.load(config, tmpdir) if err != nil { return BuildResult{}, err } @@ -156,7 +156,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(job.result), "crt1.o"))) libcDependencies = append(libcDependencies, job) case "picolibc": - libcJob, unlock, err := Picolibc.load(config, tmpdir) + libcJob, unlock, err := libPicolibc.load(config, tmpdir) if err != nil { return BuildResult{}, err } @@ -169,14 +169,14 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } libcDependencies = append(libcDependencies, dummyCompileJob(path)) case "wasmbuiltins": - libcJob, unlock, err := WasmBuiltins.load(config, tmpdir) + libcJob, unlock, err := libWasmBuiltins.load(config, tmpdir) if err != nil { return BuildResult{}, err } defer unlock() libcDependencies = append(libcDependencies, libcJob) case "mingw-w64": - _, unlock, err := MinGW.load(config, tmpdir) + _, unlock, err := libMinGW.load(config, tmpdir) if err != nil { return BuildResult{}, err } @@ -651,7 +651,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe // Add compiler-rt dependency if needed. Usually this is a simple load from // a cache. if config.Target.RTLib == "compiler-rt" { - job, unlock, err := CompilerRT.load(config, tmpdir) + job, unlock, err := libCompilerRT.load(config, tmpdir) if err != nil { return result, err } diff --git a/builder/builtins.go b/builder/builtins.go index 0dbfc42a00..228693e590 100644 --- a/builder/builtins.go +++ b/builder/builtins.go @@ -169,12 +169,12 @@ var avrBuiltins = []string{ "avr/udivmodqi4.S", } -// CompilerRT is a library with symbols required by programs compiled with LLVM. -// These symbols are for operations that cannot be emitted with a single +// libCompilerRT is a library with symbols required by programs compiled with +// LLVM. These symbols are for operations that cannot be emitted with a single // instruction or a short sequence of instructions for that target. // // For more information, see: https://compiler-rt.llvm.org/ -var CompilerRT = Library{ +var libCompilerRT = Library{ name: "compiler-rt", cflags: func(target, headerPath string) []string { return []string{"-Werror", "-Wall", "-std=c11", "-nostdlibinc"} diff --git a/builder/library.go b/builder/library.go index 83fa3db940..90ff702939 100644 --- a/builder/library.go +++ b/builder/library.go @@ -35,19 +35,6 @@ type Library struct { crt1Source string } -// Load the library archive, possibly generating and caching it if needed. -// The resulting directory may be stored in the provided tmpdir, which is -// expected to be removed after the Load call. -func (l *Library) Load(config *compileopts.Config, tmpdir string) (dir string, err error) { - job, unlock, err := l.load(config, tmpdir) - if err != nil { - return "", err - } - defer unlock() - err = runJobs(job, config.Options.Semaphore) - return filepath.Dir(job.result), err -} - // load returns a compile job to build this library file for the given target // and CPU. It may return a dummy compileJob if the library build is already // cached. The path is stored as job.result but is only valid after the job has diff --git a/builder/mingw-w64.go b/builder/mingw-w64.go index 1e7701d476..6b0c966fac 100644 --- a/builder/mingw-w64.go +++ b/builder/mingw-w64.go @@ -10,7 +10,7 @@ import ( "github.com/tinygo-org/tinygo/goenv" ) -var MinGW = Library{ +var libMinGW = Library{ name: "mingw-w64", makeHeaders: func(target, includeDir string) error { // copy _mingw.h diff --git a/builder/musl.go b/builder/musl.go index 9b5c52704e..87253fce17 100644 --- a/builder/musl.go +++ b/builder/musl.go @@ -12,7 +12,7 @@ import ( "github.com/tinygo-org/tinygo/goenv" ) -var Musl = Library{ +var libMusl = Library{ name: "musl", makeHeaders: func(target, includeDir string) error { bits := filepath.Join(includeDir, "bits") diff --git a/builder/picolibc.go b/builder/picolibc.go index ab49ba5ad1..82b3c53195 100644 --- a/builder/picolibc.go +++ b/builder/picolibc.go @@ -8,9 +8,9 @@ import ( "github.com/tinygo-org/tinygo/goenv" ) -// Picolibc is a C library for bare metal embedded devices. It was originally +// libPicolibc is a C library for bare metal embedded devices. It was originally // based on newlib. -var Picolibc = Library{ +var libPicolibc = Library{ name: "picolibc", makeHeaders: func(target, includeDir string) error { f, err := os.Create(filepath.Join(includeDir, "picolibc.h")) diff --git a/builder/wasmbuiltins.go b/builder/wasmbuiltins.go index bcd92f3ece..4c158f2337 100644 --- a/builder/wasmbuiltins.go +++ b/builder/wasmbuiltins.go @@ -7,7 +7,7 @@ import ( "github.com/tinygo-org/tinygo/goenv" ) -var WasmBuiltins = Library{ +var libWasmBuiltins = Library{ name: "wasmbuiltins", makeHeaders: func(target, includeDir string) error { if err := os.Mkdir(includeDir+"/bits", 0o777); err != nil { diff --git a/main.go b/main.go index d0e1145ce4..3518d4b543 100644 --- a/main.go +++ b/main.go @@ -1437,7 +1437,7 @@ func main() { flag.BoolVar(&flagTest, "test", false, "supply -test flag to go list") } var outpath string - if command == "help" || command == "build" || command == "build-library" || command == "test" { + if command == "help" || command == "build" || command == "test" { flag.StringVar(&outpath, "o", "", "output filename") } @@ -1570,50 +1570,6 @@ func main() { err := Build(pkgName, outpath, options) handleCompilerError(err) - case "build-library": - // Note: this command is only meant to be used while making a release! - if outpath == "" { - fmt.Fprintln(os.Stderr, "No output filename supplied (-o).") - usage(command) - os.Exit(1) - } - if *target == "" { - fmt.Fprintln(os.Stderr, "No target (-target).") - } - if flag.NArg() != 1 { - fmt.Fprintf(os.Stderr, "Build-library only accepts exactly one library name as argument, %d given\n", flag.NArg()) - usage(command) - os.Exit(1) - } - var lib *builder.Library - switch name := flag.Arg(0); name { - case "compiler-rt": - lib = &builder.CompilerRT - case "picolibc": - lib = &builder.Picolibc - default: - fmt.Fprintf(os.Stderr, "Unknown library: %s\n", name) - os.Exit(1) - } - tmpdir, err := os.MkdirTemp("", "tinygo*") - if err != nil { - handleCompilerError(err) - } - defer os.RemoveAll(tmpdir) - spec, err := compileopts.LoadTarget(options) - if err != nil { - handleCompilerError(err) - } - config := &compileopts.Config{ - Options: options, - Target: spec, - } - path, err := lib.Load(config, tmpdir) - handleCompilerError(err) - err = copyFile(path, outpath) - if err != nil { - handleCompilerError(err) - } case "flash", "gdb", "lldb": pkgName := filepath.ToSlash(flag.Arg(0)) if command == "flash" { From 020664591ab3a995d6d0aab5097c6fab838a925c Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 5 Aug 2024 13:34:02 +0200 Subject: [PATCH 122/444] main: show runtime panic addresses for `tinygo run` This adds the same panic locations that are already present for `tinygo flash -monitor`, but for `tinygo run` and `tinygo test`. For example, this is the output that I get while working on some GC code. It now shows the source location instead of just an address: $ tinygo test -v archive/zip === RUN TestReader === RUN TestReader/test.zip panic: runtime error at 0x000000000024d9b4: goroutine stack overflow [tinygo: panic at /home/ayke/src/tinygo/tinygo/src/internal/task/task_stack.go:58:15] FAIL archive/zip 0.139s (This particular location isn't all that useful, but it shows that the feature works). --- main.go | 2 +- monitor.go | 60 +++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/main.go b/main.go index 3518d4b543..c90bf7e69a 100644 --- a/main.go +++ b/main.go @@ -936,7 +936,7 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c // Configure stdout/stderr. The stdout may go to a buffer, not a real // stdout. - cmd.Stdout = stdout + cmd.Stdout = newOutputWriter(stdout, result.Executable) cmd.Stderr = os.Stderr if config.EmulatorName() == "simavr" { cmd.Stdout = nil // don't print initial load commands diff --git a/monitor.go b/monitor.go index 7b9c896434..46f3de927c 100644 --- a/monitor.go +++ b/monitor.go @@ -197,31 +197,14 @@ func Monitor(executable, port string, config *compileopts.Config) error { go func() { buf := make([]byte, 100*1024) - var line []byte + writer := newOutputWriter(os.Stdout, executable) for { n, err := serialConn.Read(buf) if err != nil { errCh <- fmt.Errorf("read error: %w", err) return } - start := 0 - for i, c := range buf[:n] { - if c == '\n' { - os.Stdout.Write(buf[start : i+1]) - start = i + 1 - address := extractPanicAddress(line) - if address != 0 { - loc, err := addressToLine(executable, address) - if err == nil && loc.IsValid() { - fmt.Printf("[tinygo: panic at %s]\n", loc.String()) - } - } - line = line[:0] - } else { - line = append(line, c) - } - } - os.Stdout.Write(buf[start:n]) + writer.Write(buf[:n]) } }() @@ -400,3 +383,42 @@ func readDWARF(executable string) (*dwarf.Data, error) { return nil, errors.New("unknown binary format") } } + +type outputWriter struct { + out io.Writer + executable string + line []byte +} + +// newOutputWriter returns an io.Writer that will intercept panic addresses and +// will try to insert a source location in the output if the source location can +// be found in the executable. +func newOutputWriter(out io.Writer, executable string) *outputWriter { + return &outputWriter{ + out: out, + executable: executable, + } +} + +func (w *outputWriter) Write(p []byte) (n int, err error) { + start := 0 + for i, c := range p { + if c == '\n' { + w.out.Write(p[start : i+1]) + start = i + 1 + address := extractPanicAddress(w.line) + if address != 0 { + loc, err := addressToLine(w.executable, address) + if err == nil && loc.Filename != "" { + fmt.Printf("[tinygo: panic at %s]\n", loc.String()) + } + } + w.line = w.line[:0] + } else { + w.line = append(w.line, c) + } + } + w.out.Write(p[start:]) + n = len(p) + return +} From fb3d98ce6e6786f7133facbe131374102a6aa375 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 6 Aug 2024 12:44:01 +0200 Subject: [PATCH 123/444] compileopts: add CanonicalArchName to centralize arch detection It's possible to detect the architecture from the target triple, but there are a number of exceptions that make it unpleasant to use for this purpose. There are just too many weird exceptions (like mips vs mipsel, and armv6m vs thumv6m vs arm64 vs aarch64) so it's better to centralize these to canonical architecture names. I picked the architecture names that happen to match the musl architecture names, because those seem the most natural to me. --- builder/library.go | 15 ++++++--------- compileopts/config.go | 15 ++++++++++++--- compiler/llvm.go | 13 ++----------- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/builder/library.go b/builder/library.go index 90ff702939..0ab7fa0e42 100644 --- a/builder/library.go +++ b/builder/library.go @@ -149,27 +149,24 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ if config.ABI() != "" { args = append(args, "-mabi="+config.ABI()) } - if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") { + switch compileopts.CanonicalArchName(target) { + case "arm": if strings.Split(target, "-")[2] == "linux" { args = append(args, "-fno-unwind-tables", "-fno-asynchronous-unwind-tables") } else { args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft", "-fno-unwind-tables", "-fno-asynchronous-unwind-tables") } - } - if strings.HasPrefix(target, "avr") { + case "avr": // AVR defaults to C float and double both being 32-bit. This deviates // from what most code (and certainly compiler-rt) expects. So we need // to force the compiler to use 64-bit floating point numbers for // double. args = append(args, "-mdouble=64") - } - if strings.HasPrefix(target, "riscv32-") { + case "riscv32": args = append(args, "-march=rv32imac", "-fforce-enable-int128") - } - if strings.HasPrefix(target, "riscv64-") { + case "riscv64": args = append(args, "-march=rv64gc") - } - if strings.HasPrefix(target, "mips") { + case "mips": args = append(args, "-fno-pic") } diff --git a/compileopts/config.go b/compileopts/config.go index 893fbf0016..a5ab7cd8f9 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -207,10 +207,13 @@ func (c *Config) RP2040BootPatch() bool { return false } -// MuslArchitecture returns the architecture name as used in musl libc. It is -// usually the same as the first part of the LLVM triple, but not always. -func MuslArchitecture(triple string) string { +// Return a canonicalized architecture name, so we don't have to deal with arm* +// vs thumb* vs arm64. +func CanonicalArchName(triple string) string { arch := strings.Split(triple, "-")[0] + if arch == "arm64" { + return "aarch64" + } if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") { return "arm" } @@ -220,6 +223,12 @@ func MuslArchitecture(triple string) string { return arch } +// MuslArchitecture returns the architecture name as used in musl libc. It is +// usually the same as the first part of the LLVM triple, but not always. +func MuslArchitecture(triple string) string { + return CanonicalArchName(triple) +} + // LibcPath returns the path to the libc directory. The libc path will be either // a precompiled libc shipped with a TinyGo build, or a libc path in the cache // directory (which might not yet be built). diff --git a/compiler/llvm.go b/compiler/llvm.go index 9e3a95d4f5..bdbf0ece16 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -7,6 +7,7 @@ import ( "math/big" "strings" + "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compiler/llvmutil" "tinygo.org/x/go-llvm" ) @@ -422,17 +423,7 @@ func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.In // architecture names ("armv6", "thumbv7m", etc) merged into a single // architecture name ("arm"). func (c *compilerContext) archFamily() string { - arch := strings.Split(c.Triple, "-")[0] - if strings.HasPrefix(arch, "arm64") { - return "aarch64" - } - if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") { - return "arm" - } - if arch == "mipsel" { - return "mips" - } - return arch + return compileopts.CanonicalArchName(c.Triple) } // isThumb returns whether we're in ARM or in Thumb mode. It panics if the From 841abb0903b4873041ef8d3ef2ec497c1343f914 Mon Sep 17 00:00:00 2001 From: Kobayashi Shunta Date: Thu, 8 Aug 2024 23:07:47 +0900 Subject: [PATCH 124/444] feat: add node: specifier --- targets/wasm_exec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/targets/wasm_exec.js b/targets/wasm_exec.js index a7f8c31b12..6902454409 100644 --- a/targets/wasm_exec.js +++ b/targets/wasm_exec.js @@ -29,7 +29,7 @@ } if (!global.fs && global.require) { - global.fs = require("fs"); + global.fs = require("node:fs"); } const enosys = () => { @@ -101,7 +101,7 @@ } if (!global.crypto) { - const nodeCrypto = require("crypto"); + const nodeCrypto = require("node:crypto"); global.crypto = { getRandomValues(b) { nodeCrypto.randomFillSync(b); @@ -119,11 +119,11 @@ } if (!global.TextEncoder) { - global.TextEncoder = require("util").TextEncoder; + global.TextEncoder = require("node:util").TextEncoder; } if (!global.TextDecoder) { - global.TextDecoder = require("util").TextDecoder; + global.TextDecoder = require("node:util").TextDecoder; } // End of polyfills for common API. From 55f7d21ff5664491af956574fbe94b23792d87a3 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 24 Jun 2024 18:02:07 +0200 Subject: [PATCH 125/444] builtins: add GENERIC_TF_SOURCES This addes GENERIC_TF_SOURCES, which contains long double floating point builtins (80 bit, 128 bit, etc). This is needed for full printf support. --- builder/builtins.go | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/builder/builtins.go b/builder/builtins.go index 228693e590..b493b6680a 100644 --- a/builder/builtins.go +++ b/builder/builtins.go @@ -3,8 +3,8 @@ package builder import ( "os" "path/filepath" - "strings" + "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/goenv" ) @@ -132,6 +132,38 @@ var genericBuiltins = []string{ "umodti3.c", } +// These are the GENERIC_TF_SOURCES as of LLVM 18. +// They are not needed on all platforms (32-bit platforms usually don't need +// these) but they seem to compile fine so it's easier to include them. +var genericBuiltins128 = []string{ + "addtf3.c", + "comparetf2.c", + "divtc3.c", + "divtf3.c", + "extenddftf2.c", + "extendhftf2.c", + "extendsftf2.c", + "fixtfdi.c", + "fixtfsi.c", + "fixtfti.c", + "fixunstfdi.c", + "fixunstfsi.c", + "fixunstfti.c", + "floatditf.c", + "floatsitf.c", + "floattitf.c", + "floatunditf.c", + "floatunsitf.c", + "floatuntitf.c", + "multc3.c", + "multf3.c", + "powitf2.c", + "subtf3.c", + "trunctfdf2.c", + "trunctfhf2.c", + "trunctfsf2.c", +} + var aeabiBuiltins = []string{ "arm/aeabi_cdcmp.S", "arm/aeabi_cdcmpeq_check_nan.c", @@ -190,11 +222,13 @@ var libCompilerRT = Library{ }, librarySources: func(target string) ([]string, error) { builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins - if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") { + switch compileopts.CanonicalArchName(target) { + case "arm": builtins = append(builtins, aeabiBuiltins...) - } - if strings.HasPrefix(target, "avr") { + case "avr": builtins = append(builtins, avrBuiltins...) + case "x86_64", "aarch64", "riscv64": // any 64-bit arch + builtins = append(builtins, genericBuiltins128...) } return builtins, nil }, From 3021e16bbf138ef48d840604e330049def61329a Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 16 Feb 2024 16:33:07 +0100 Subject: [PATCH 126/444] wasm: call __stdio_exit on exit This flushes stdio, so that functions like puts and printf write out all buffered data even if the output isn't connected to a terminal. For discussion, see: https://github.com/bytecodealliance/wasmtime/issues/7833 --- src/runtime/runtime_tinygowasm.go | 8 ++++++++ src/runtime/runtime_wasip1.go | 1 + src/runtime/runtime_wasm_js.go | 1 + 3 files changed, 10 insertions(+) diff --git a/src/runtime/runtime_tinygowasm.go b/src/runtime/runtime_tinygowasm.go index b69a91539a..744e138afb 100644 --- a/src/runtime/runtime_tinygowasm.go +++ b/src/runtime/runtime_tinygowasm.go @@ -21,6 +21,11 @@ func fd_write(id uint32, iovs *__wasi_iovec_t, iovs_len uint, nwritten *uint) (e //go:wasmimport wasi_snapshot_preview1 proc_exit func proc_exit(exitcode uint32) +// Flush stdio on exit. +// +//export __stdio_exit +func __stdio_exit() + const ( putcharBufferSize = 120 stdout = 1 @@ -72,6 +77,9 @@ func abort() { //go:linkname syscall_Exit syscall.Exit func syscall_Exit(code int) { + // TODO: should we call __stdio_exit here? + // It's a low-level exit (syscall.Exit) so doing any libc stuff seems + // unexpected, but then where else should stdio buffers be flushed? proc_exit(uint32(code)) } diff --git a/src/runtime/runtime_wasip1.go b/src/runtime/runtime_wasip1.go index 595cab9bf0..4a1afb2abd 100644 --- a/src/runtime/runtime_wasip1.go +++ b/src/runtime/runtime_wasip1.go @@ -19,6 +19,7 @@ func _start() { heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) run() + __stdio_exit() } // Read the command line arguments from WASI. diff --git a/src/runtime/runtime_wasm_js.go b/src/runtime/runtime_wasm_js.go index 96e923c89b..0b1aa5bc41 100644 --- a/src/runtime/runtime_wasm_js.go +++ b/src/runtime/runtime_wasm_js.go @@ -18,6 +18,7 @@ func _start() { wasmNested = true run() + __stdio_exit() wasmNested = false } From 2eb39785fe9d0188e444fd1eb29f1ce2c7a89419 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 27 Jan 2024 12:43:45 +0100 Subject: [PATCH 127/444] cgo: add support for printf The C printf function is sometimes needed for C files included using CGo. This commit makes sure they're available on all systems where CGo is fully supported (that is, everywhere except on AVR). For baremetal systems using picolibc, I've picked the integer-only version of printf to save on flash size. We might want to consider providing a way to pick the floating point version instead, if needed. --- GNUmakefile | 4 ++++ builder/build.go | 5 +++-- builder/mingw-w64.go | 26 +++++++++++++++++++++----- builder/musl.go | 4 ++++ builder/picolibc.go | 1 + testdata/cgo/main.c | 5 +++++ testdata/cgo/main.go | 4 ++++ testdata/cgo/main.h | 2 ++ testdata/cgo/out.txt | 1 + 9 files changed, 45 insertions(+), 7 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index d560ae82e3..986d519400 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -856,6 +856,7 @@ build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN @mkdir -p build/release/tinygo/lib/CMSIS/CMSIS @mkdir -p build/release/tinygo/lib/macos-minimal-sdk @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common + @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/stdio @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults @mkdir -p build/release/tinygo/lib/musl/arch @mkdir -p build/release/tinygo/lib/musl/crt @@ -891,10 +892,12 @@ endif @cp -rp lib/musl/src/include build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/internal build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/legacy build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/locale build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/linux build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/malloc build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/mman build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/math build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/multibyte build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/signal build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/stdio build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/string build/release/tinygo/lib/musl/src @@ -904,6 +907,7 @@ endif @cp -rp lib/mingw-w64/mingw-w64-crt/def-include build/release/tinygo/lib/mingw-w64/mingw-w64-crt @cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/api-ms-win-crt-* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common @cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/kernel32.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common + @cp -rp lib/mingw-w64/mingw-w64-crt/stdio/ucrt_* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/stdio @cp -rp lib/mingw-w64/mingw-w64-headers/crt/ build/release/tinygo/lib/mingw-w64/mingw-w64-headers @cp -rp lib/mingw-w64/mingw-w64-headers/defaults/include build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults @cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx diff --git a/builder/build.go b/builder/build.go index 54f5c101be..d16bf170bd 100644 --- a/builder/build.go +++ b/builder/build.go @@ -176,11 +176,12 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe defer unlock() libcDependencies = append(libcDependencies, libcJob) case "mingw-w64": - _, unlock, err := libMinGW.load(config, tmpdir) + job, unlock, err := libMinGW.load(config, tmpdir) if err != nil { return BuildResult{}, err } - unlock() + defer unlock() + libcDependencies = append(libcDependencies, job) libcDependencies = append(libcDependencies, makeMinGWExtraLibs(tmpdir, config.GOARCH())...) case "": // no library specified, so nothing to do diff --git a/builder/mingw-w64.go b/builder/mingw-w64.go index 6b0c966fac..32cf58f531 100644 --- a/builder/mingw-w64.go +++ b/builder/mingw-w64.go @@ -27,14 +27,30 @@ var libMinGW = Library{ _, err = io.Copy(outf, inf) return err }, - sourceDir: func() string { return "" }, // unused + sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64") }, cflags: func(target, headerPath string) []string { - // No flags necessary because there are no files to compile. - return nil + mingwDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64") + return []string{ + "-nostdlibinc", + "-isystem", mingwDir + "/mingw-w64-headers/crt", + "-I", mingwDir + "/mingw-w64-headers/defaults/include", + "-I" + headerPath, + } }, librarySources: func(target string) ([]string, error) { - // We only use the UCRT DLL file. No source files necessary. - return nil, nil + // These files are needed so that printf and the like are supported. + sources := []string{ + "mingw-w64-crt/stdio/ucrt_fprintf.c", + "mingw-w64-crt/stdio/ucrt_fwprintf.c", + "mingw-w64-crt/stdio/ucrt_printf.c", + "mingw-w64-crt/stdio/ucrt_snprintf.c", + "mingw-w64-crt/stdio/ucrt_sprintf.c", + "mingw-w64-crt/stdio/ucrt_vfprintf.c", + "mingw-w64-crt/stdio/ucrt_vprintf.c", + "mingw-w64-crt/stdio/ucrt_vsnprintf.c", + "mingw-w64-crt/stdio/ucrt_vsprintf.c", + } + return sources, nil }, } diff --git a/builder/musl.go b/builder/musl.go index 87253fce17..8130981e6c 100644 --- a/builder/musl.go +++ b/builder/musl.go @@ -92,6 +92,8 @@ var libMusl = Library{ "-Wno-ignored-pragmas", "-Wno-tautological-constant-out-of-range-compare", "-Wno-deprecated-non-prototype", + "-Wno-format", + "-Wno-parentheses", "-Qunused-arguments", // Select include dirs. Don't include standard library includes // (that would introduce host dependencies and other complications), @@ -119,11 +121,13 @@ var libMusl = Library{ "internal/syscall_ret.c", "internal/vdso.c", "legacy/*.c", + "locale/*.c", "linux/*.c", "malloc/*.c", "malloc/mallocng/*.c", "mman/*.c", "math/*.c", + "multibyte/*.c", "signal/*.c", "stdio/*.c", "string/*.c", diff --git a/builder/picolibc.go b/builder/picolibc.go index 82b3c53195..d33f31c094 100644 --- a/builder/picolibc.go +++ b/builder/picolibc.go @@ -29,6 +29,7 @@ var libPicolibc = Library{ "-D_HAVE_ALIAS_ATTRIBUTE", "-DTINY_STDIO", "-DPOSIX_IO", + "-DFORMAT_DEFAULT_INTEGER", // use __i_vfprintf and __i_vfscanf by default "-D_IEEE_LIBM", "-D__OBSOLETE_MATH_FLOAT=1", // use old math code that doesn't expect a FPU "-D__OBSOLETE_MATH_DOUBLE=0", diff --git a/testdata/cgo/main.c b/testdata/cgo/main.c index 7fb702ed67..4a5bd6b9c6 100644 --- a/testdata/cgo/main.c +++ b/testdata/cgo/main.c @@ -1,5 +1,6 @@ #include #include "main.h" +#include int global = 3; bool globalBool = 1; @@ -72,3 +73,7 @@ void arraydecay(int buf1[5], int buf2[3][8], int buf3[4][7][2]) { double doSqrt(double x) { return sqrt(x); } + +void printf_single_int(char *format, int arg) { + printf(format, arg); +} diff --git a/testdata/cgo/main.go b/testdata/cgo/main.go index fa3380bcea..ddd1992e22 100644 --- a/testdata/cgo/main.go +++ b/testdata/cgo/main.go @@ -179,6 +179,10 @@ func main() { // libc: test basic stdio functionality putsBuf := []byte("line written using C puts\x00") C.puts((*C.char)(unsafe.Pointer(&putsBuf[0]))) + + // libc: test whether printf works in C. + printfBuf := []byte("line written using C printf with value=%d\n\x00") + C.printf_single_int((*C.char)(unsafe.Pointer(&printfBuf[0])), -21) } func printUnion(union C.joined_t) C.joined_t { diff --git a/testdata/cgo/main.h b/testdata/cgo/main.h index f5405ade62..e7c64ffc3f 100644 --- a/testdata/cgo/main.h +++ b/testdata/cgo/main.h @@ -152,3 +152,5 @@ typedef int arraydecay_buf3[4][7][2]; void arraydecay(int buf1[5], int buf2[3][8], arraydecay_buf3 buf3); double doSqrt(double); + +void printf_single_int(char *format, int arg); diff --git a/testdata/cgo/out.txt b/testdata/cgo/out.txt index ae92c87a7c..4ea45d864c 100644 --- a/testdata/cgo/out.txt +++ b/testdata/cgo/out.txt @@ -77,3 +77,4 @@ copied string: foobar CGo sqrt(3): +1.732051e+000 C sqrt(3): +1.732051e+000 line written using C puts +line written using C printf with value=-21 From 2e76cd3687df0f6a4ea8d042589ced1d22f4ea78 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 10 Jul 2024 13:42:21 +0200 Subject: [PATCH 128/444] builder: interpret linker error messages This shows nicely formatted error messages for missing symbol names and for out-of-flash, out-of-RAM conditions (on microcontrollers with limited flash/RAM). Unfortunately the missing symbol name errors aren't available on Windows and WebAssembly because the linker doesn't report source locations yet. This is something that I could perhaps improve in LLD. --- builder/build.go | 2 +- builder/error.go | 4 +- builder/tools.go | 150 +++++++++++++++++++++--- errors_test.go | 60 ++++++---- main.go | 3 +- main_test.go | 4 +- testdata/errors/linker-flashoverflow.go | 21 ++++ testdata/errors/linker-ramoverflow.go | 9 ++ testdata/errors/linker-undefined.go | 11 ++ 9 files changed, 220 insertions(+), 44 deletions(-) create mode 100644 testdata/errors/linker-flashoverflow.go create mode 100644 testdata/errors/linker-ramoverflow.go create mode 100644 testdata/errors/linker-undefined.go diff --git a/builder/build.go b/builder/build.go index d16bf170bd..33c3b28583 100644 --- a/builder/build.go +++ b/builder/build.go @@ -779,7 +779,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } err = link(config.Target.Linker, ldflags...) if err != nil { - return &commandError{"failed to link", result.Executable, err} + return err } var calculatedStacks []string diff --git a/builder/error.go b/builder/error.go index 3531007fb6..fe1a2d422e 100644 --- a/builder/error.go +++ b/builder/error.go @@ -15,12 +15,12 @@ func (e *MultiError) Error() string { // newMultiError returns a *MultiError if there is more than one error, or // returns that error directly when there is only one. Passing an empty slice -// will lead to a panic. +// will return nil (because there is no error). // The importPath may be passed if this error is for a single package. func newMultiError(errs []error, importPath string) error { switch len(errs) { case 0: - panic("attempted to create empty MultiError") + return nil case 1: return errs[0] default: diff --git a/builder/tools.go b/builder/tools.go index a23714d604..e1c6443c65 100644 --- a/builder/tools.go +++ b/builder/tools.go @@ -1,10 +1,15 @@ package builder import ( + "bytes" + "fmt" + "go/scanner" + "go/token" "os" "os/exec" - - "github.com/tinygo-org/tinygo/goenv" + "regexp" + "strconv" + "strings" ) // runCCompiler invokes a C compiler with the given arguments. @@ -23,22 +28,135 @@ func runCCompiler(flags ...string) error { // link invokes a linker with the given name and flags. func link(linker string, flags ...string) error { - if hasBuiltinTools && (linker == "ld.lld" || linker == "wasm-ld") { - // Run command with internal linker. - cmd := exec.Command(os.Args[0], append([]string{linker}, flags...)...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() + // We only support LLD. + if linker != "ld.lld" && linker != "wasm-ld" { + return fmt.Errorf("unexpected: linker %s should be ld.lld or wasm-ld", linker) } - // Fall back to external command. - if _, ok := commands[linker]; ok { - return execCommand(linker, flags...) + var cmd *exec.Cmd + if hasBuiltinTools { + cmd = exec.Command(os.Args[0], append([]string{linker}, flags...)...) + } else { + name, err := LookupCommand(linker) + if err != nil { + return err + } + cmd = exec.Command(name, flags...) } - - cmd := exec.Command(linker, flags...) + var buf bytes.Buffer cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Dir = goenv.Get("TINYGOROOT") - return cmd.Run() + cmd.Stderr = &buf + err := cmd.Run() + if err != nil { + if buf.Len() == 0 { + // The linker failed but ther was no output. + // Therefore, show some output anyway. + return fmt.Errorf("failed to run linker: %w", err) + } + return parseLLDErrors(buf.String()) + } + return nil +} + +// Split LLD errors into individual erros (including errors that continue on the +// next line, using a ">>>" prefix). If possible, replace the raw errors with a +// more user-friendly version (and one that's more in a Go style). +func parseLLDErrors(text string) error { + // Split linker output in separate error messages. + lines := strings.Split(text, "\n") + var errorLines []string // one or more line (belonging to a single error) per line + for _, line := range lines { + line = strings.TrimRight(line, "\r") // needed for Windows + if len(errorLines) != 0 && strings.HasPrefix(line, ">>> ") { + errorLines[len(errorLines)-1] += "\n" + line + continue + } + if line == "" { + continue + } + errorLines = append(errorLines, line) + } + + // Parse error messages. + var linkErrors []error + var flashOverflow, ramOverflow uint64 + for _, message := range errorLines { + parsedError := false + + // Check for undefined symbols. + // This can happen in some cases like with CGo and //go:linkname tricker. + if matches := regexp.MustCompile(`^ld.lld: error: undefined symbol: (.*)\n`).FindStringSubmatch(message); matches != nil { + symbolName := matches[1] + for _, line := range strings.Split(message, "\n") { + matches := regexp.MustCompile(`referenced by .* \(((.*):([0-9]+))\)`).FindStringSubmatch(line) + if matches != nil { + parsedError = true + line, _ := strconv.Atoi(matches[3]) + // TODO: detect common mistakes like -gc=none? + linkErrors = append(linkErrors, scanner.Error{ + Pos: token.Position{ + Filename: matches[2], + Line: line, + }, + Msg: "linker could not find symbol " + symbolName, + }) + } + } + } + + // Check for flash/RAM overflow. + if matches := regexp.MustCompile(`^ld.lld: error: section '(.*?)' will not fit in region '(.*?)': overflowed by ([0-9]+) bytes$`).FindStringSubmatch(message); matches != nil { + region := matches[2] + n, err := strconv.ParseUint(matches[3], 10, 64) + if err != nil { + // Should not happen at all (unless it overflows an uint64 for some reason). + continue + } + + // Check which area overflowed. + // Some chips use differently named memory areas, but these are by + // far the most common. + switch region { + case "FLASH_TEXT": + if n > flashOverflow { + flashOverflow = n + } + parsedError = true + case "RAM": + if n > ramOverflow { + ramOverflow = n + } + parsedError = true + } + } + + // If we couldn't parse the linker error: show the error as-is to + // the user. + if !parsedError { + linkErrors = append(linkErrors, LinkerError{message}) + } + } + + if flashOverflow > 0 { + linkErrors = append(linkErrors, LinkerError{ + Msg: fmt.Sprintf("program too large for this chip (flash overflowed by %d bytes)\n\toptimization guide: https://tinygo.org/docs/guides/optimizing-binaries/", flashOverflow), + }) + } + if ramOverflow > 0 { + linkErrors = append(linkErrors, LinkerError{ + Msg: fmt.Sprintf("program uses too much static RAM on this chip (RAM overflowed by %d bytes)", ramOverflow), + }) + } + + return newMultiError(linkErrors, "") +} + +// LLD linker error that could not be parsed or doesn't refer to a source +// location. +type LinkerError struct { + Msg string +} + +func (e LinkerError) Error() string { + return e.Msg } diff --git a/errors_test.go b/errors_test.go index 71eaff5ef2..62d5af2cbb 100644 --- a/errors_test.go +++ b/errors_test.go @@ -7,7 +7,6 @@ import ( "regexp" "strings" "testing" - "time" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/diagnostics" @@ -15,38 +14,55 @@ import ( // Test the error messages of the TinyGo compiler. func TestErrors(t *testing.T) { - for _, name := range []string{ - "cgo", - "compiler", - "interp", - "loader-importcycle", - "loader-invaliddep", - "loader-invalidpackage", - "loader-nopackage", - "optimizer", - "syntax", - "types", + // TODO: nicely formatted error messages for: + // - duplicate symbols in ld.lld (currently only prints bitcode file) + type errorTest struct { + name string + target string + } + for _, tc := range []errorTest{ + {name: "cgo"}, + {name: "compiler"}, + {name: "interp"}, + {name: "linker-flashoverflow", target: "cortex-m-qemu"}, + {name: "linker-ramoverflow", target: "cortex-m-qemu"}, + {name: "linker-undefined", target: "darwin/arm64"}, + {name: "linker-undefined", target: "linux/amd64"}, + //{name: "linker-undefined", target: "windows/amd64"}, // TODO: no source location + {name: "linker-undefined", target: "cortex-m-qemu"}, + //{name: "linker-undefined", target: "wasip1"}, // TODO: no source location + {name: "loader-importcycle"}, + {name: "loader-invaliddep"}, + {name: "loader-invalidpackage"}, + {name: "loader-nopackage"}, + {name: "optimizer"}, + {name: "syntax"}, + {name: "types"}, } { + name := tc.name + if tc.target != "" { + name += "#" + tc.target + } + target := tc.target + if target == "" { + target = "wasip1" + } t.Run(name, func(t *testing.T) { - testErrorMessages(t, "./testdata/errors/"+name+".go") + options := optionsFromTarget(target, sema) + testErrorMessages(t, "./testdata/errors/"+tc.name+".go", &options) }) } } -func testErrorMessages(t *testing.T, filename string) { +func testErrorMessages(t *testing.T, filename string, options *compileopts.Options) { + t.Parallel() + // Parse expected error messages. expected := readErrorMessages(t, filename) // Try to build a binary (this should fail with an error). tmpdir := t.TempDir() - err := Build(filename, tmpdir+"/out", &compileopts.Options{ - Target: "wasip1", - Semaphore: sema, - InterpTimeout: 180 * time.Second, - Debug: true, - VerifyIR: true, - Opt: "z", - }) + err := Build(filename, tmpdir+"/out", options) if err == nil { t.Fatal("expected to get a compiler error") } diff --git a/main.go b/main.go index c90bf7e69a..32fc22665f 100644 --- a/main.go +++ b/main.go @@ -1467,7 +1467,8 @@ func main() { case "clang", "ld.lld", "wasm-ld": err := builder.RunTool(command, os.Args[2:]...) if err != nil { - fmt.Fprintln(os.Stderr, err) + // The tool should have printed an error message already. + // Don't print another error message here. os.Exit(1) } os.Exit(0) diff --git a/main_test.go b/main_test.go index 057e008e87..ccd490d622 100644 --- a/main_test.go +++ b/main_test.go @@ -8,7 +8,6 @@ import ( "bytes" "errors" "flag" - "fmt" "io" "os" "os/exec" @@ -696,7 +695,8 @@ func TestMain(m *testing.M) { // Invoke a specific tool. err := builder.RunTool(os.Args[1], os.Args[2:]...) if err != nil { - fmt.Fprintln(os.Stderr, err) + // The tool should have printed an error message already. + // Don't print another error message here. os.Exit(1) } os.Exit(0) diff --git a/testdata/errors/linker-flashoverflow.go b/testdata/errors/linker-flashoverflow.go new file mode 100644 index 0000000000..46e7d9858e --- /dev/null +++ b/testdata/errors/linker-flashoverflow.go @@ -0,0 +1,21 @@ +package main + +import "unsafe" + +const ( + a = "0123456789abcdef" // 16 bytes + b = a + a + a + a + a + a + a + a // 128 bytes + c = b + b + b + b + b + b + b + b // 1024 bytes + d = c + c + c + c + c + c + c + c // 8192 bytes + e = d + d + d + d + d + d + d + d // 65536 bytes + f = e + e + e + e + e + e + e + e // 524288 bytes +) + +var s = f + +func main() { + println(unsafe.StringData(s)) +} + +// ERROR: program too large for this chip (flash overflowed by {{[0-9]+}} bytes) +// ERROR: optimization guide: https://tinygo.org/docs/guides/optimizing-binaries/ diff --git a/testdata/errors/linker-ramoverflow.go b/testdata/errors/linker-ramoverflow.go new file mode 100644 index 0000000000..866f984ad0 --- /dev/null +++ b/testdata/errors/linker-ramoverflow.go @@ -0,0 +1,9 @@ +package main + +var b [64 << 10]byte // 64kB + +func main() { + println("ptr:", &b[0]) +} + +// ERROR: program uses too much static RAM on this chip (RAM overflowed by {{[0-9]+}} bytes) diff --git a/testdata/errors/linker-undefined.go b/testdata/errors/linker-undefined.go new file mode 100644 index 0000000000..fda2b623d7 --- /dev/null +++ b/testdata/errors/linker-undefined.go @@ -0,0 +1,11 @@ +package main + +func foo() + +func main() { + foo() + foo() +} + +// ERROR: linker-undefined.go:6: linker could not find symbol {{_?}}main.foo +// ERROR: linker-undefined.go:7: linker could not find symbol {{_?}}main.foo From d6e73b4c6842e67a40fea0b15e3cdd6f7dd46f94 Mon Sep 17 00:00:00 2001 From: Roger Standridge <9526806+archie2x@users.noreply.github.com> Date: Fri, 9 Aug 2024 01:53:42 -0700 Subject: [PATCH 129/444] add os.Truncate(name, size) (see #4209) See https://pkg.go.dev/os#Truncate --- src/os/file_unix.go | 22 ++++++++++++++-------- src/os/file_windows.go | 15 ++++++++++----- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/os/file_unix.go b/src/os/file_unix.go index ef7abcbac8..d8d086e8dc 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -55,6 +55,19 @@ func NewFile(fd uintptr, name string) *File { return &File{&file{handle: unixFileHandle(fd), name: name}} } +// Truncate changes the size of the named file. +// If the file is a symbolic link, it changes the size of the link's target. +// If there is an error, it will be of type *PathError. +func Truncate(name string, size int64) error { + e := ignoringEINTR(func() error { + return syscall.Truncate(name, size) + }) + if e != nil { + return &PathError{Op: "truncate", Path: name, Err: e} + } + return nil +} + func Pipe() (r *File, w *File, err error) { var p [2]int err = handleSyscallError(syscall.Pipe2(p[:], syscall.O_CLOEXEC)) @@ -134,14 +147,7 @@ func (f *File) Truncate(size int64) (err error) { return ErrClosed } - e := ignoringEINTR(func() error { - return syscall.Truncate(f.name, size) - }) - - if e != nil { - return &PathError{Op: "truncate", Path: f.name, Err: e} - } - return + return Truncate(f.name, size) } // ReadAt reads up to len(b) bytes from the File starting at the given absolute offset. diff --git a/src/os/file_windows.go b/src/os/file_windows.go index 5860c76b04..9588cac763 100644 --- a/src/os/file_windows.go +++ b/src/os/file_windows.go @@ -63,6 +63,12 @@ func (f *unixFileHandle) Truncate(size int64) error { return ErrNotImplemented } +// Truncate changes the size of the named file. +// If the file is a symbolic link, it changes the size of the link's target. +func Truncate(name string, size int64) error { + return &PathError{Op: "truncate", Path: name, Err: ErrNotImplemented} +} + func tempDir() string { n := uint32(syscall.MAX_PATH) for { @@ -110,14 +116,13 @@ func (f unixFileHandle) Sync() error { return ErrNotImplemented } +// Truncate changes the size of the named file. +// If the file is a symbolic link, it changes the size of the link's target. func (f *File) Truncate(size int64) error { - var err error if f.handle == nil { - err = ErrClosed - } else { - err = ErrNotImplemented + return &PathError{Op: "truncate", Path: f.name, Err: ErrClosed} } - return &PathError{Op: "truncate", Path: f.name, Err: err} + return Truncate(f.name, size) } // isWindowsNulName reports whether name is os.DevNull ('NUL') on Windows. From 6efc6d2bb6489a9157c5bd964bdc072ed23f29d1 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 30 Jul 2024 18:28:01 +0200 Subject: [PATCH 130/444] compileopts: refactor defaultTarget function Move triple calculation into defaultTarget. It was separate for historic reasons that no longer apply. Merging the code makes future changes easier (in particular, softfloat support). The new code is actually shorter than the old code even though it has more comments. --- builder/builder_test.go | 1 - compileopts/target.go | 174 +++++++++++++++++++--------------------- 2 files changed, 83 insertions(+), 92 deletions(-) diff --git a/builder/builder_test.go b/builder/builder_test.go index 3fc166c5c8..1b3e76ded9 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -64,7 +64,6 @@ func TestClangAttributes(t *testing.T) { {GOOS: "windows", GOARCH: "amd64"}, {GOOS: "windows", GOARCH: "arm64"}, {GOOS: "wasip1", GOARCH: "wasm"}, - {GOOS: "wasip2", GOARCH: "wasm"}, } { name := "GOOS=" + options.GOOS + ",GOARCH=" + options.GOARCH if options.GOARCH == "arm" { diff --git a/compileopts/target.go b/compileopts/target.go index fdb29e2109..ecbebd8b34 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -172,63 +172,7 @@ func (spec *TargetSpec) resolveInherits() error { // Load a target specification. func LoadTarget(options *Options) (*TargetSpec, error) { if options.Target == "" { - // Configure based on GOOS/GOARCH environment variables (falling back to - // runtime.GOOS/runtime.GOARCH), and generate a LLVM target based on it. - var llvmarch string - switch options.GOARCH { - case "386": - llvmarch = "i386" - case "amd64": - llvmarch = "x86_64" - case "arm64": - llvmarch = "aarch64" - case "arm": - switch options.GOARM { - case "5": - llvmarch = "armv5" - case "6": - llvmarch = "armv6" - case "7": - llvmarch = "armv7" - default: - return nil, fmt.Errorf("invalid GOARM=%s, must be 5, 6, or 7", options.GOARM) - } - case "mips": - llvmarch = "mips" - case "mipsle": - llvmarch = "mipsel" - case "wasm": - llvmarch = "wasm32" - default: - llvmarch = options.GOARCH - } - llvmvendor := "unknown" - llvmos := options.GOOS - switch llvmos { - case "darwin": - // Use macosx* instead of darwin, otherwise darwin/arm64 will refer - // to iOS! - llvmos = "macosx10.12.0" - if llvmarch == "aarch64" { - // Looks like Apple prefers to call this architecture ARM64 - // instead of AArch64. - llvmarch = "arm64" - llvmos = "macosx11.0.0" - } - llvmvendor = "apple" - case "wasip1": - llvmos = "wasi" - } - // Target triples (which actually have four components, but are called - // triples for historical reasons) have the form: - // arch-vendor-os-environment - target := llvmarch + "-" + llvmvendor + "-" + llvmos - if options.GOOS == "windows" { - target += "-gnu" - } else if options.GOARCH == "arm" { - target += "-gnueabihf" - } - return defaultTarget(options.GOOS, options.GOARCH, target) + return defaultTarget(options) } // See whether there is a target specification for this target (e.g. @@ -289,14 +233,13 @@ func GetTargetSpecs() (map[string]*TargetSpec, error) { return maps, nil } -func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { - // No target spec available. Use the default one, useful on most systems - // with a regular OS. +// Load a target from environment variables (which default to +// runtime.GOOS/runtime.GOARCH). +func defaultTarget(options *Options) (*TargetSpec, error) { spec := TargetSpec{ - Triple: triple, - GOOS: goos, - GOARCH: goarch, - BuildTags: []string{goos, goarch}, + GOOS: options.GOOS, + GOARCH: options.GOARCH, + BuildTags: []string{options.GOOS, options.GOARCH}, GC: "precise", Scheduler: "tasks", Linker: "cc", @@ -304,29 +247,43 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { GDB: []string{"gdb"}, PortReset: "false", } - switch goarch { + + // Configure target based on GOARCH. + var llvmarch string + switch options.GOARCH { case "386": + llvmarch = "i386" spec.CPU = "pentium4" spec.Features = "+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" case "amd64": + llvmarch = "x86_64" spec.CPU = "x86-64" spec.Features = "+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" case "arm": spec.CPU = "generic" spec.CFlags = append(spec.CFlags, "-fno-unwind-tables", "-fno-asynchronous-unwind-tables") - switch strings.Split(triple, "-")[0] { - case "armv5": + switch options.GOARM { + case "5": + llvmarch = "armv5" spec.Features = "+armv5t,+strict-align,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" - case "armv6": + case "6": + llvmarch = "armv6" spec.Features = "+armv6,+dsp,+fp64,+strict-align,+vfp2,+vfp2sp,-aes,-d32,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-neon,-sha2,-thumb-mode,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" - case "armv7": + case "7": + llvmarch = "armv7" spec.Features = "+armv7-a,+d32,+dsp,+fp64,+neon,+vfp2,+vfp2sp,+vfp3,+vfp3d16,+vfp3d16sp,+vfp3sp,-aes,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-sha2,-thumb-mode,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + default: + return nil, fmt.Errorf("invalid GOARM=%s, must be 5, 6, or 7", options.GOARM) } case "arm64": spec.CPU = "generic" - if goos == "darwin" { + llvmarch = "aarch64" + if options.GOOS == "darwin" { spec.Features = "+fp-armv8,+neon" - } else if goos == "windows" { + // Looks like Apple prefers to call this architecture ARM64 + // instead of AArch64. + llvmarch = "arm64" + } else if options.GOOS == "windows" { spec.Features = "+fp-armv8,+neon,-fmv" } else { // linux spec.Features = "+fp-armv8,+neon,-fmv,-outline-atomics" @@ -335,7 +292,13 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { spec.CPU = "mips32r2" spec.Features = "+fpxx,+mips32r2,+nooddspreg,-noabicalls" spec.CFlags = append(spec.CFlags, "-fno-pic") + if options.GOOS == "mips" { + llvmarch = "mips" // big endian + } else { + llvmarch = "mipsel" // little endian + } case "wasm": + llvmarch = "wasm32" spec.CPU = "generic" spec.Features = "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" spec.BuildTags = append(spec.BuildTags, "tinygo.wasm") @@ -344,24 +307,37 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { "-mnontrapping-fptoint", "-msign-ext", ) + default: + return nil, fmt.Errorf("unknown GOARCH=%s", options.GOARCH) } - if goos == "darwin" { + + // Configure target based on GOOS. + llvmos := options.GOOS + llvmvendor := "unknown" + switch options.GOOS { + case "darwin": + platformVersion := "10.12.0" + if options.GOARCH == "arm64" { + platformVersion = "11.0.0" // first macosx platform with arm64 support + } + llvmvendor = "apple" spec.Linker = "ld.lld" spec.Libc = "darwin-libSystem" - arch := strings.Split(triple, "-")[0] - platformVersion := strings.TrimPrefix(strings.Split(triple, "-")[2], "macosx") + // Use macosx* instead of darwin, otherwise darwin/arm64 will refer to + // iOS! + llvmos = "macosx" + platformVersion spec.LDFlags = append(spec.LDFlags, "-flavor", "darwin", "-dead_strip", - "-arch", arch, + "-arch", llvmarch, "-platform_version", "macos", platformVersion, platformVersion, ) - } else if goos == "linux" { + case "linux": spec.Linker = "ld.lld" spec.RTLib = "compiler-rt" spec.Libc = "musl" spec.LDFlags = append(spec.LDFlags, "--gc-sections") - if goarch == "arm64" { + if options.GOARCH == "arm64" { // Disable outline atomics. For details, see: // https://cpufun.substack.com/p/atomics-in-aarch64 // A better way would be to fully support outline atomics, which @@ -375,7 +351,7 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { // proper threading. spec.CFlags = append(spec.CFlags, "-mno-outline-atomics") } - } else if goos == "windows" { + case "windows": spec.Linker = "ld.lld" spec.Libc = "mingw-w64" // Note: using a medium code model, low image base and no ASLR @@ -384,7 +360,7 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { // normally present in Go (without explicitly opting in). // For more discussion: // https://groups.google.com/g/Golang-nuts/c/Jd9tlNc6jUE/m/Zo-7zIP_m3MJ?pli=1 - switch goarch { + switch options.GOARCH { case "amd64": spec.LDFlags = append(spec.LDFlags, "-m", "i386pep", @@ -401,7 +377,7 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { "--no-insert-timestamp", "--no-dynamicbase", ) - } else if goos == "wasip1" { + case "wasip1": spec.GC = "" // use default GC spec.Scheduler = "asyncify" spec.Linker = "wasm-ld" @@ -417,28 +393,43 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { "src/runtime/asm_tinygowasm.S", "src/internal/task/task_asyncify_wasm.S", ) - } else { - spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie + llvmos = "wasi" + default: + return nil, fmt.Errorf("unknown GOOS=%s", options.GOOS) } - if goarch != "wasm" { + + // Target triples (which actually have four components, but are called + // triples for historical reasons) have the form: + // arch-vendor-os-environment + spec.Triple = llvmarch + "-" + llvmvendor + "-" + llvmos + if options.GOOS == "windows" { + spec.Triple += "-gnu" + } else if options.GOARCH == "arm" { + spec.Triple += "-gnueabihf" + } + + // Add extra assembly files (needed for the scheduler etc). + if options.GOARCH != "wasm" { suffix := "" - if goos == "windows" && goarch == "amd64" { + if options.GOOS == "windows" && options.GOARCH == "amd64" { // Windows uses a different calling convention on amd64 from other // operating systems so we need separate assembly files. suffix = "_windows" } - asmGoarch := goarch - if goarch == "mips" || goarch == "mipsle" { + asmGoarch := options.GOARCH + if options.GOARCH == "mips" || options.GOARCH == "mipsle" { asmGoarch = "mipsx" } spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/asm_"+asmGoarch+suffix+".S") spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+asmGoarch+suffix+".S") } - if goarch != runtime.GOARCH { + + // Configure the emulator. + if options.GOARCH != runtime.GOARCH { // Some educated guesses as to how to invoke helper programs. spec.GDB = []string{"gdb-multiarch"} - if goos == "linux" { - switch goarch { + if options.GOOS == "linux" { + switch options.GOARCH { case "386": // amd64 can _usually_ run 32-bit programs, so skip the emulator in that case. if runtime.GOARCH != "amd64" { @@ -457,11 +448,12 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { } } } - if goos != runtime.GOOS { - if goos == "windows" { + if options.GOOS != runtime.GOOS { + if options.GOOS == "windows" { spec.Emulator = "wine {}" } } + return &spec, nil } From f188eaf5f92618adc6422c2b55db8f822c6cc458 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 30 Jul 2024 19:08:55 +0200 Subject: [PATCH 131/444] mips: add GOMIPS=softfloat support Previously, the compiler would default to hardfloat. This is not supported by some MIPS CPUs. This took me much longer than it should have because of a quirk in the LLVM Mips backend: if the target-features string is not set (like during LTO), the Mips backend picks the first function in the module and uses that. Unfortunately, in the case of TinyGo this first function is `llvm.dbg.value`, which is an LLVM intrinsic and doesn't have the target-features string. I fixed it by adding a `-mllvm -mattr=` flag to the linker. --- builder/build.go | 1 + builder/builder_test.go | 9 +++++++-- builder/library.go | 5 +++++ compileopts/config.go | 9 +++++++++ compileopts/options.go | 1 + compileopts/target.go | 16 +++++++++++++++- goenv/goenv.go | 12 +++++++++++- main.go | 3 +++ main_test.go | 8 ++++++-- 9 files changed, 58 insertions(+), 6 deletions(-) diff --git a/builder/build.go b/builder/build.go index 33c3b28583..f728cde79d 100644 --- a/builder/build.go +++ b/builder/build.go @@ -746,6 +746,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe ldflags = append(ldflags, dependency.result) } ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU()) + ldflags = append(ldflags, "-mllvm", "-mattr="+config.Features()) // needed for MIPS softfloat if config.GOOS() == "windows" { // Options for the MinGW wrapper for the lld COFF linker. ldflags = append(ldflags, diff --git a/builder/builder_test.go b/builder/builder_test.go index 1b3e76ded9..92ed9bdcc7 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -57,8 +57,10 @@ func TestClangAttributes(t *testing.T) { {GOOS: "linux", GOARCH: "arm", GOARM: "6"}, {GOOS: "linux", GOARCH: "arm", GOARM: "7"}, {GOOS: "linux", GOARCH: "arm64"}, - {GOOS: "linux", GOARCH: "mips"}, - {GOOS: "linux", GOARCH: "mipsle"}, + {GOOS: "linux", GOARCH: "mips", GOMIPS: "hardfloat"}, + {GOOS: "linux", GOARCH: "mipsle", GOMIPS: "hardfloat"}, + {GOOS: "linux", GOARCH: "mips", GOMIPS: "softfloat"}, + {GOOS: "linux", GOARCH: "mipsle", GOMIPS: "softfloat"}, {GOOS: "darwin", GOARCH: "amd64"}, {GOOS: "darwin", GOARCH: "arm64"}, {GOOS: "windows", GOARCH: "amd64"}, @@ -69,6 +71,9 @@ func TestClangAttributes(t *testing.T) { if options.GOARCH == "arm" { name += ",GOARM=" + options.GOARM } + if options.GOARCH == "mips" || options.GOARCH == "mipsle" { + name += ",GOMIPS=" + options.GOMIPS + } t.Run(name, func(t *testing.T) { testClangAttributes(t, options) }) diff --git a/builder/library.go b/builder/library.go index 0ab7fa0e42..b8d1834147 100644 --- a/builder/library.go +++ b/builder/library.go @@ -169,6 +169,11 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ case "mips": args = append(args, "-fno-pic") } + if config.Target.SoftFloat { + // Use softfloat instead of floating point instructions. This is + // supported on many architectures. + args = append(args, "-msoft-float") + } var once sync.Once diff --git a/compileopts/config.go b/compileopts/config.go index a5ab7cd8f9..67c773de16 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -72,6 +72,12 @@ func (c *Config) GOARM() string { return c.Options.GOARM } +// GOMIPS will return the GOMIPS environment variable given to the compiler when +// building a program. +func (c *Config) GOMIPS() string { + return c.Options.GOMIPS +} + // BuildTags returns the complete list of build tags used during this build. func (c *Config) BuildTags() []string { tags := append([]string(nil), c.Target.BuildTags...) // copy slice (avoid a race) @@ -240,6 +246,9 @@ func (c *Config) LibcPath(name string) (path string, precompiled bool) { if c.ABI() != "" { archname += "-" + c.ABI() } + if c.Target.SoftFloat { + archname += "-softfloat" + } // Try to load a precompiled library. precompiledDir := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", archname, name) diff --git a/compileopts/options.go b/compileopts/options.go index 8b0e2266de..9601ae3221 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -23,6 +23,7 @@ type Options struct { GOOS string // environment variable GOARCH string // environment variable GOARM string // environment variable (only used with GOARCH=arm) + GOMIPS string // environment variable (only used with GOARCH=mips and GOARCH=mipsle) Directory string // working dir, leave it unset to use the current working dir Target string Opt string diff --git a/compileopts/target.go b/compileopts/target.go index ecbebd8b34..9388b84960 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -30,6 +30,7 @@ type TargetSpec struct { Features string `json:"features,omitempty"` GOOS string `json:"goos,omitempty"` GOARCH string `json:"goarch,omitempty"` + SoftFloat bool // used for non-baremetal systems (GOMIPS=softfloat etc) BuildTags []string `json:"build-tags,omitempty"` GC string `json:"gc,omitempty"` Scheduler string `json:"scheduler,omitempty"` @@ -86,6 +87,10 @@ func (spec *TargetSpec) overrideProperties(child *TargetSpec) error { if src.Uint() != 0 { dst.Set(src) } + case reflect.Bool: + if src.Bool() { + dst.Set(src) + } case reflect.Ptr: // for pointers, copy if not nil if !src.IsNil() { dst.Set(src) @@ -290,13 +295,22 @@ func defaultTarget(options *Options) (*TargetSpec, error) { } case "mips", "mipsle": spec.CPU = "mips32r2" - spec.Features = "+fpxx,+mips32r2,+nooddspreg,-noabicalls" spec.CFlags = append(spec.CFlags, "-fno-pic") if options.GOOS == "mips" { llvmarch = "mips" // big endian } else { llvmarch = "mipsel" // little endian } + switch options.GOMIPS { + case "hardfloat": + spec.Features = "+fpxx,+mips32r2,+nooddspreg,-noabicalls" + case "softfloat": + spec.SoftFloat = true + spec.Features = "+mips32r2,+soft-float,-noabicalls" + spec.CFlags = append(spec.CFlags, "-msoft-float") + default: + return nil, fmt.Errorf("invalid GOMIPS=%s: must be hardfloat or softfloat", options.GOMIPS) + } case "wasm": llvmarch = "wasm32" spec.CPU = "generic" diff --git a/goenv/goenv.go b/goenv/goenv.go index 187bcc6df1..fe4c8bf63e 100644 --- a/goenv/goenv.go +++ b/goenv/goenv.go @@ -30,8 +30,11 @@ var Keys = []string{ } func init() { - if Get("GOARCH") == "arm" { + switch Get("GOARCH") { + case "arm": Keys = append(Keys, "GOARM") + case "mips", "mipsle": + Keys = append(Keys, "GOMIPS") } } @@ -128,6 +131,13 @@ func Get(name string) string { // difference between ARMv6 and ARMv7. ARMv6 binaries are much smaller, // especially when floating point instructions are involved. return "6" + case "GOMIPS": + gomips := os.Getenv("GOMIPS") + if gomips == "" { + // Default to hardfloat (this matches the Go toolchain). + gomips = "hardfloat" + } + return gomips case "GOROOT": readGoEnvVars() return goEnvVars.GOROOT diff --git a/main.go b/main.go index 32fc22665f..cbe4b9dd48 100644 --- a/main.go +++ b/main.go @@ -1499,6 +1499,7 @@ func main() { GOOS: goenv.Get("GOOS"), GOARCH: goenv.Get("GOARCH"), GOARM: goenv.Get("GOARM"), + GOMIPS: goenv.Get("GOMIPS"), Target: *target, StackSize: stackSize, Opt: *opt, @@ -1737,6 +1738,7 @@ func main() { GOOS string `json:"goos"` GOARCH string `json:"goarch"` GOARM string `json:"goarm"` + GOMIPS string `json:"gomips"` BuildTags []string `json:"build_tags"` GC string `json:"garbage_collector"` Scheduler string `json:"scheduler"` @@ -1747,6 +1749,7 @@ func main() { GOOS: config.GOOS(), GOARCH: config.GOARCH(), GOARM: config.GOARM(), + GOMIPS: config.GOMIPS(), BuildTags: config.BuildTags(), GC: config.GC(), Scheduler: config.Scheduler(), diff --git a/main_test.go b/main_test.go index ccd490d622..4c11f7b18d 100644 --- a/main_test.go +++ b/main_test.go @@ -36,7 +36,7 @@ var supportedLinuxArches = map[string]string{ "X86Linux": "linux/386", "ARMLinux": "linux/arm/6", "ARM64Linux": "linux/arm64", - "MIPSLinux": "linux/mipsle", + "MIPSLinux": "linux/mipsle/hardfloat", "WASIp1": "wasip1/wasm", } @@ -325,6 +325,7 @@ func optionsFromTarget(target string, sema chan struct{}) compileopts.Options { GOOS: goenv.Get("GOOS"), GOARCH: goenv.Get("GOARCH"), GOARM: goenv.Get("GOARM"), + GOMIPS: goenv.Get("GOMIPS"), Target: target, Semaphore: sema, InterpTimeout: 180 * time.Second, @@ -348,8 +349,11 @@ func optionsFromOSARCH(osarch string, sema chan struct{}) compileopts.Options { VerifyIR: true, Opt: "z", } - if options.GOARCH == "arm" { + switch options.GOARCH { + case "arm": options.GOARM = parts[2] + case "mips", "mipsle": + options.GOMIPS = parts[2] } return options } From eab1a5d7ecfdfa6c4a896b2462d256eb11b01c0a Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 12 Aug 2024 13:10:07 +0200 Subject: [PATCH 132/444] reflect, runtime: remove *UnsafePointer wrappers for functions This was needed in the past because LLVM used typed pointers and there was a mismatch between pointer types. But we've dropped support for typed pointers a while ago so now we can remove these wrappers. This is just a cleanup, it shouldn't have any practical effect. --- src/reflect/value.go | 28 ++++++++++----------- src/runtime/chan.go | 12 --------- src/runtime/hashmap.go | 56 ------------------------------------------ 3 files changed, 14 insertions(+), 82 deletions(-) diff --git a/src/reflect/value.go b/src/reflect/value.go index 56c0dc10d2..9e602f69de 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -646,10 +646,10 @@ func (v Value) Slice3(i, j, k int) Value { panic("unimplemented: (reflect.Value).Slice3()") } -//go:linkname maplen runtime.hashmapLenUnsafePointer +//go:linkname maplen runtime.hashmapLen func maplen(p unsafe.Pointer) int -//go:linkname chanlen runtime.chanLenUnsafePointer +//go:linkname chanlen runtime.chanLen func chanlen(p unsafe.Pointer) int // Len returns the length of this value for slices, strings, arrays, channels, @@ -671,7 +671,7 @@ func (v Value) Len() int { } } -//go:linkname chancap runtime.chanCapUnsafePointer +//go:linkname chancap runtime.chanCap func chancap(p unsafe.Pointer) int // Cap returns the capacity of this value for arrays, channels and slices. @@ -965,13 +965,13 @@ func (v Value) MapKeys() []Value { return keys } -//go:linkname hashmapStringGet runtime.hashmapStringGetUnsafePointer +//go:linkname hashmapStringGet runtime.hashmapStringGet func hashmapStringGet(m unsafe.Pointer, key string, value unsafe.Pointer, valueSize uintptr) bool -//go:linkname hashmapBinaryGet runtime.hashmapBinaryGetUnsafePointer +//go:linkname hashmapBinaryGet runtime.hashmapBinaryGet func hashmapBinaryGet(m unsafe.Pointer, key, value unsafe.Pointer, valueSize uintptr) bool -//go:linkname hashmapInterfaceGet runtime.hashmapInterfaceGetUnsafePointer +//go:linkname hashmapInterfaceGet runtime.hashmapInterfaceGet func hashmapInterfaceGet(m unsafe.Pointer, key interface{}, value unsafe.Pointer, valueSize uintptr) bool func (v Value) MapIndex(key Value) Value { @@ -1018,7 +1018,7 @@ func (v Value) MapIndex(key Value) Value { //go:linkname hashmapNewIterator runtime.hashmapNewIterator func hashmapNewIterator() unsafe.Pointer -//go:linkname hashmapNext runtime.hashmapNextUnsafePointer +//go:linkname hashmapNext runtime.hashmapNext func hashmapNext(m unsafe.Pointer, it unsafe.Pointer, key, value unsafe.Pointer) bool func (v Value) MapRange() *MapIter { @@ -1786,22 +1786,22 @@ func (v Value) Grow(n int) { slice.cap = newslice.cap } -//go:linkname hashmapStringSet runtime.hashmapStringSetUnsafePointer +//go:linkname hashmapStringSet runtime.hashmapStringSet func hashmapStringSet(m unsafe.Pointer, key string, value unsafe.Pointer) -//go:linkname hashmapBinarySet runtime.hashmapBinarySetUnsafePointer +//go:linkname hashmapBinarySet runtime.hashmapBinarySet func hashmapBinarySet(m unsafe.Pointer, key, value unsafe.Pointer) -//go:linkname hashmapInterfaceSet runtime.hashmapInterfaceSetUnsafePointer +//go:linkname hashmapInterfaceSet runtime.hashmapInterfaceSet func hashmapInterfaceSet(m unsafe.Pointer, key interface{}, value unsafe.Pointer) -//go:linkname hashmapStringDelete runtime.hashmapStringDeleteUnsafePointer +//go:linkname hashmapStringDelete runtime.hashmapStringDelete func hashmapStringDelete(m unsafe.Pointer, key string) -//go:linkname hashmapBinaryDelete runtime.hashmapBinaryDeleteUnsafePointer +//go:linkname hashmapBinaryDelete runtime.hashmapBinaryDelete func hashmapBinaryDelete(m unsafe.Pointer, key unsafe.Pointer) -//go:linkname hashmapInterfaceDelete runtime.hashmapInterfaceDeleteUnsafePointer +//go:linkname hashmapInterfaceDelete runtime.hashmapInterfaceDelete func hashmapInterfaceDelete(m unsafe.Pointer, key interface{}) func (v Value) SetMapIndex(key, elem Value) { @@ -1930,7 +1930,7 @@ func (v Value) FieldByNameFunc(match func(string) bool) Value { return Value{} } -//go:linkname hashmapMake runtime.hashmapMakeUnsafePointer +//go:linkname hashmapMake runtime.hashmapMake func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) unsafe.Pointer // MakeMapWithSize creates a new map with the specified type and initial space diff --git a/src/runtime/chan.go b/src/runtime/chan.go index 532b31cc59..269f5a01b6 100644 --- a/src/runtime/chan.go +++ b/src/runtime/chan.go @@ -148,12 +148,6 @@ func chanLen(c *channel) int { return int(c.bufUsed) } -// wrapper for use in reflect -func chanLenUnsafePointer(p unsafe.Pointer) int { - c := (*channel)(p) - return chanLen(c) -} - // Return the capacity of this chan, called from the cap builtin. // A nil chan is defined as having capacity 0. // @@ -165,12 +159,6 @@ func chanCap(c *channel) int { return int(c.bufSize) } -// wrapper for use in reflect -func chanCapUnsafePointer(p unsafe.Pointer) int { - c := (*channel)(p) - return chanCap(c) -} - // resumeRX resumes the next receiver and returns the destination pointer. // If the ok value is true, then the caller is expected to store a value into this pointer. func (ch *channel) resumeRX(ok bool) unsafe.Pointer { diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index dfbec300ed..a148415f7a 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -87,10 +87,6 @@ func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) *hashm } } -func hashmapMakeUnsafePointer(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) unsafe.Pointer { - 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). @@ -178,10 +174,6 @@ func hashmapLen(m *hashmap) int { return int(m.count) } -func hashmapLenUnsafePointer(m unsafe.Pointer) int { - return hashmapLen((*hashmap)(m)) -} - //go:inline func hashmapBucketSize(m *hashmap) uintptr { return unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8 @@ -268,10 +260,6 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3 *emptySlotTophash = tophash } -func hashmapSetUnsafePointer(m unsafe.Pointer, key unsafe.Pointer, value unsafe.Pointer, hash uint32) { - hashmapSet((*hashmap)(m), key, value, hash) -} - // hashmapInsertIntoNewBucket creates a new bucket, inserts the given key and // value into the bucket, and returns a pointer to this bucket. func hashmapInsertIntoNewBucket(m *hashmap, key, value unsafe.Pointer, tophash uint8) *hashmapBucket { @@ -352,10 +340,6 @@ func hashmapGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr, hash u return false } -func hashmapGetUnsafePointer(m unsafe.Pointer, key, value unsafe.Pointer, valueSize uintptr, hash uint32) bool { - return hashmapGet((*hashmap)(m), key, value, valueSize, hash) -} - // Delete a given key from the map. No-op when the key does not exist in the // map. // @@ -456,10 +440,6 @@ func hashmapNext(m *hashmap, it *hashmapIterator, key, value unsafe.Pointer) boo } } -func hashmapNextUnsafePointer(m unsafe.Pointer, it unsafe.Pointer, key, value unsafe.Pointer) bool { - return hashmapNext((*hashmap)(m), (*hashmapIterator)(it), key, value) -} - // Hashmap with plain binary data keys (not containing strings etc.). func hashmapBinarySet(m *hashmap, key, value unsafe.Pointer) { if m == nil { @@ -469,10 +449,6 @@ func hashmapBinarySet(m *hashmap, key, value unsafe.Pointer) { hashmapSet(m, key, value, hash) } -func hashmapBinarySetUnsafePointer(m unsafe.Pointer, key, value unsafe.Pointer) { - hashmapBinarySet((*hashmap)(m), key, value) -} - func hashmapBinaryGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr) bool { if m == nil { memzero(value, uintptr(valueSize)) @@ -482,10 +458,6 @@ func hashmapBinaryGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr) return hashmapGet(m, key, value, valueSize, hash) } -func hashmapBinaryGetUnsafePointer(m unsafe.Pointer, key, value unsafe.Pointer, valueSize uintptr) bool { - return hashmapBinaryGet((*hashmap)(m), key, value, valueSize) -} - func hashmapBinaryDelete(m *hashmap, key unsafe.Pointer) { if m == nil { return @@ -494,10 +466,6 @@ func hashmapBinaryDelete(m *hashmap, key unsafe.Pointer) { hashmapDelete(m, key, hash) } -func hashmapBinaryDeleteUnsafePointer(m unsafe.Pointer, key unsafe.Pointer) { - hashmapBinaryDelete((*hashmap)(m), key) -} - // Hashmap with string keys (a common case). func hashmapStringEqual(x, y unsafe.Pointer, n uintptr) bool { @@ -522,10 +490,6 @@ func hashmapStringSet(m *hashmap, key string, value unsafe.Pointer) { hashmapSet(m, unsafe.Pointer(&key), value, hash) } -func hashmapStringSetUnsafePointer(m unsafe.Pointer, key string, value unsafe.Pointer) { - hashmapStringSet((*hashmap)(m), key, value) -} - func hashmapStringGet(m *hashmap, key string, value unsafe.Pointer, valueSize uintptr) bool { if m == nil { memzero(value, uintptr(valueSize)) @@ -535,10 +499,6 @@ func hashmapStringGet(m *hashmap, key string, value unsafe.Pointer, valueSize ui return hashmapGet(m, unsafe.Pointer(&key), value, valueSize, hash) } -func hashmapStringGetUnsafePointer(m unsafe.Pointer, key string, value unsafe.Pointer, valueSize uintptr) bool { - return hashmapStringGet((*hashmap)(m), key, value, valueSize) -} - func hashmapStringDelete(m *hashmap, key string) { if m == nil { return @@ -547,10 +507,6 @@ func hashmapStringDelete(m *hashmap, key string) { hashmapDelete(m, unsafe.Pointer(&key), hash) } -func hashmapStringDeleteUnsafePointer(m unsafe.Pointer, key string) { - hashmapStringDelete((*hashmap)(m), key) -} - // Hashmap with interface keys (for everything else). // This is a method that is intentionally unexported in the reflect package. It @@ -654,10 +610,6 @@ func hashmapInterfaceSet(m *hashmap, key interface{}, value unsafe.Pointer) { hashmapSet(m, unsafe.Pointer(&key), value, hash) } -func hashmapInterfaceSetUnsafePointer(m unsafe.Pointer, key interface{}, value unsafe.Pointer) { - hashmapInterfaceSet((*hashmap)(m), key, value) -} - func hashmapInterfaceGet(m *hashmap, key interface{}, value unsafe.Pointer, valueSize uintptr) bool { if m == nil { memzero(value, uintptr(valueSize)) @@ -667,10 +619,6 @@ func hashmapInterfaceGet(m *hashmap, key interface{}, value unsafe.Pointer, valu return hashmapGet(m, unsafe.Pointer(&key), value, valueSize, hash) } -func hashmapInterfaceGetUnsafePointer(m unsafe.Pointer, key interface{}, value unsafe.Pointer, valueSize uintptr) bool { - return hashmapInterfaceGet((*hashmap)(m), key, value, valueSize) -} - func hashmapInterfaceDelete(m *hashmap, key interface{}) { if m == nil { return @@ -678,7 +626,3 @@ func hashmapInterfaceDelete(m *hashmap, key interface{}) { hash := hashmapInterfaceHash(key, m.seed) hashmapDelete(m, unsafe.Pointer(&key), hash) } - -func hashmapInterfaceDeleteUnsafePointer(m unsafe.Pointer, key interface{}) { - hashmapInterfaceDelete((*hashmap)(m), key) -} From 835e73237e7413b5b71a5fe04f642b1768778c66 Mon Sep 17 00:00:00 2001 From: dkegel-fastly <79674949+dkegel-fastly@users.noreply.github.com> Date: Mon, 12 Aug 2024 10:19:16 -0700 Subject: [PATCH 133/444] GNUmakefile: add "help" target (#4390) Example output: $ make help clean Remove build directory fmt Reformat source fmt-check Warn if any source needs reformatting gen-device Generate microcontroller-specific sources llvm-source Get LLVM sources llvm-build Build LLVM lint Lint source tree spell Spellcheck source tree Might even work on windows, since git for windows comes with awk. --- BUILDING.md | 15 +++++++++++++++ GNUmakefile | 31 ++++++++++++++++++------------- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/BUILDING.md b/BUILDING.md index dfc069c0fd..6f90d9badd 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -28,6 +28,21 @@ build tools to be built. Go is of course necessary to build TinyGo itself. The rest of this guide assumes you're running Linux, but it should be equivalent on a different system like Mac. +## Using GNU Make + +The static build of TinyGo is driven by GNUmakefile, which provides a help target for quick reference: + + % make help + clean Remove build directory + fmt Reformat source + fmt-check Warn if any source needs reformatting + gen-device Generate microcontroller-specific sources + llvm-source Get LLVM sources + llvm-build Build LLVM + tinygo Build the TinyGo compiler + lint Lint source tree + spell Spellcheck source tree + ## Download the source The first step is to download the TinyGo sources (use `--recursive` if you clone diff --git a/GNUmakefile b/GNUmakefile index 986d519400..a27489cc26 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -175,17 +175,17 @@ ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","") CGO_LDFLAGS+=-L$(abspath $(LLVM_BUILDDIR)/lib) -lclang $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_CONFIG_PREFIX) $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA) endif -clean: +clean: ## Remove build directory @rm -rf build FMT_PATHS = ./*.go builder cgo/*.go compiler interp loader src transform -fmt: +fmt: ## Reformat source @gofmt -l -w $(FMT_PATHS) -fmt-check: +fmt-check: ## Warn if any source needs reformatting @unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1 -gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp gen-device-rp +gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp gen-device-rp ## Generate microcontroller-specific sources ifneq ($(STM32), 0) gen-device: gen-device-stm32 endif @@ -237,18 +237,16 @@ gen-device-renesas: build/gen-device-svd ./build/gen-device-svd -source=https://github.com/tinygo-org/renesas-svd lib/renesas-svd/ src/device/renesas/ GO111MODULE=off $(GO) fmt ./src/device/renesas -# Get LLVM sources. $(LLVM_PROJECTDIR)/llvm: git clone -b tinygo_xtensa_release_18.1.2 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR) -llvm-source: $(LLVM_PROJECTDIR)/llvm +llvm-source: $(LLVM_PROJECTDIR)/llvm ## Get LLVM sources # Configure LLVM. TINYGO_SOURCE_DIR=$(shell pwd) $(LLVM_BUILDDIR)/build.ninja: mkdir -p $(LLVM_BUILDDIR) && cd $(LLVM_BUILDDIR) && cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;AVR;Mips;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_ZSTD=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF -DCLANG_ENABLE_STATIC_ANALYZER=OFF -DCLANG_ENABLE_ARCMT=OFF $(LLVM_OPTION) -# Build LLVM. -$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja +$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja ## Build LLVM cd $(LLVM_BUILDDIR) && ninja $(NINJA_BUILD_TARGETS) ifneq ($(USE_SYSTEM_BINARYEN),1) @@ -291,8 +289,7 @@ ifeq (, $(shell which node)) endif @if [ $(NODEJS_VERSION) -lt $(MIN_NODEJS_VERSION) ]; then echo "Install NodeJS version 18+ to run tests."; exit 1; fi -# Build the Go compiler. -tinygo: +tinygo: ## Build the TinyGo compiler @if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " $(MAKE) llvm-source"; echo " $(MAKE) $(LLVM_BUILDDIR)"; exit 1; fi CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" -ldflags="-X github.com/tinygo-org/tinygo/goenv.GitSha1=`git rev-parse --short HEAD`" . test: wasi-libc check-nodejs-version @@ -955,7 +952,7 @@ tools: go generate -C ./internal/tools -tags tools ./ .PHONY: lint -lint: +lint: ## Lint source tree go run github.com/mgechev/revive -version # TODO: lint more directories! # revive.toml isn't flexible enough to filter out just one kind of error from a checker, so do it with grep here. @@ -964,6 +961,14 @@ lint: go run github.com/mgechev/revive -config revive.toml compiler/... src/{os,reflect}/*.go | grep -v "should have comment or be unexported" | grep '.' | awk '{print}; END {exit NR>0}' .PHONY: spell -spell: - # Check for typos in comments. Skip git submodules etc. +spell: ## Spellcheck source tree go run github.com/client9/misspell/cmd/misspell -i 'ackward,devided,extint,inbetween,programmmer,rela' $$( find . -depth 1 -type d | egrep -w -v 'lib|llvm|src/net' ) + +# https://www.client9.com/self-documenting-makefiles/ +.PHONY: help +help: + @awk -F ':|##' '/^[^\t].+?:.*?##/ {\ + gsub(/\$$\(LLVM_BUILDDIR\)/, "$(LLVM_BUILDDIR)"); \ + printf "\033[36m%-30s\033[0m %s\n", $$1, $$NF \ + }' $(MAKEFILE_LIST) +#.DEFAULT_GOAL=help From 8135be4e908d0744c46399aac9d9fc3aa4a17e55 Mon Sep 17 00:00:00 2001 From: dkegel-fastly <79674949+dkegel-fastly@users.noreply.github.com> Date: Mon, 12 Aug 2024 14:24:38 -0700 Subject: [PATCH 134/444] GNUmakefile: add spellfix target, use it. (#4387) TODO: Remove the go.mod/go.sum in internal/tools once doing so doesn't break CI (e.g. once we drop support for go 1.19) * builder/cc1as.h: fix typo found by 'make spell' * GNUmakefile: remove exception for inbetween, fix instance now found by 'make spell' * GNUmakefile: remove exception for programmmer, fix instance now found by 'make spell' * go.mod: use updated misspell. GNUmakefile: add spellfix target, use it. * ignore directories properly when invoking spellchecker. * make spell: give internal/tools its own go.mod, as misspell requires newer go * make lint: depend on tools and run the installed revive (which was perhaps implied by the change that added revive to internal/tools, but not required in GNUmakefile until we gave internal/tools its own temporary go.mod) * .github: now that 'make spell' works well, run it from CI * GNUmakefile: make spell now aborts if it finds misspelt words, so what it finds doesn't get lost in CI logs * GNUmakefile: tools: avoid -C option on go generate to make test-llvm15-go119 circleci job happy, see https://cs.opensource.google/go/go/+/2af48cbb7d85e5fdc635e75b99f949010c607786 * internal/tools/go.mod: fix format of go version to leave out patchlevel, else go complains. --- .github/workflows/linux.yml | 2 + GNUmakefile | 17 ++++-- builder/ar.go | 2 +- builder/cc1as.h | 2 +- builder/tools.go | 2 +- compileopts/config.go | 4 +- compiler/asserts.go | 2 +- compiler/compiler.go | 2 +- compiler/gc.go | 2 +- compiler/interface.go | 2 +- compiler/llvm.go | 2 +- compiler/llvmutil/llvm.go | 2 +- compiler/map.go | 2 +- compiler/syscall.go | 2 +- diagnostics/diagnostics.go | 4 +- go.mod | 13 +---- go.sum | 33 +---------- internal/tools/go.mod | 30 ++++++++++ internal/tools/go.sum | 56 +++++++++++++++++++ internal/tools/tools.go | 4 +- interp/README.md | 2 +- misspell.csv | 22 ++++++++ src/crypto/rand/rand_arc4random.go | 2 +- src/crypto/tls/tls.go | 2 +- src/machine/machine_atmega1280.go | 4 +- src/machine/machine_atmega328.go | 4 +- src/machine/machine_esp32c3.go | 2 +- src/machine/machine_mimxrt1062.go | 2 +- src/machine/machine_rp2040_i2c.go | 2 +- src/machine/machine_stm32f103.go | 2 +- src/machine/machine_stm32l0.go | 4 +- src/machine/machine_stm32l4.go | 4 +- src/os/exec.go | 1 + src/os/os_anyos_test.go | 2 +- src/os/tempfile_test.go | 2 +- src/reflect/type.go | 2 +- src/reflect/value_test.go | 2 +- src/runtime/gc_stack_portable.go | 2 +- src/runtime/interrupt/interrupt_avr.go | 2 +- src/runtime/interrupt/interrupt_cortexm.go | 2 +- .../interrupt/interrupt_gameboyadvance.go | 2 +- src/runtime/interrupt/interrupt_none.go | 2 +- .../interrupt/interrupt_tinygoriscv.go | 2 +- src/runtime/interrupt/interrupt_xtensa.go | 2 +- src/sync/mutex_test.go | 2 +- 45 files changed, 169 insertions(+), 93 deletions(-) create mode 100644 internal/tools/go.mod create mode 100644 internal/tools/go.sum create mode 100644 misspell.csv diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 218019224b..589eb71fe6 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -115,6 +115,8 @@ jobs: gem install --no-document fpm - name: Run linter run: make lint + - name: Run spellcheck + run: make spell - name: Build TinyGo release run: | make release deb -j3 STATIC=1 diff --git a/GNUmakefile b/GNUmakefile index a27489cc26..504e1561af 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -949,20 +949,25 @@ endif .PHONY: tools tools: - go generate -C ./internal/tools -tags tools ./ + cd internal/tools && go generate -tags tools ./ .PHONY: lint -lint: ## Lint source tree - go run github.com/mgechev/revive -version +lint: tools ## Lint source tree + revive -version # TODO: lint more directories! # revive.toml isn't flexible enough to filter out just one kind of error from a checker, so do it with grep here. # Can't use grep with friendly formatter. Plain output isn't too bad, though. # Use 'grep .' to get rid of stray blank line - go run github.com/mgechev/revive -config revive.toml compiler/... src/{os,reflect}/*.go | grep -v "should have comment or be unexported" | grep '.' | awk '{print}; END {exit NR>0}' + revive -config revive.toml compiler/... src/{os,reflect}/*.go | grep -v "should have comment or be unexported" | grep '.' | awk '{print}; END {exit NR>0}' +SPELLDIRSCMD=find . -depth 1 -type d | egrep -wv '.git|lib|llvm|src'; find src -depth 1 | egrep -wv 'device|internal|net|vendor'; find src/internal -depth 1 -type d | egrep -wv src/internal/wasi .PHONY: spell -spell: ## Spellcheck source tree - go run github.com/client9/misspell/cmd/misspell -i 'ackward,devided,extint,inbetween,programmmer,rela' $$( find . -depth 1 -type d | egrep -w -v 'lib|llvm|src/net' ) +spell: tools ## Spellcheck source tree + misspell -error --dict misspell.csv -i 'ackward,devided,extint,rela' $$( $(SPELLDIRSCMD) ) + +.PHONY: spellfix +spellfix: tools ## Same as spell, but fixes what it finds + misspell -w --dict misspell.csv -i 'ackward,devided,extint,rela' $$( $(SPELLDIRSCMD) ) # https://www.client9.com/self-documenting-makefiles/ .PHONY: help diff --git a/builder/ar.go b/builder/ar.go index 578b88ba57..807595b792 100644 --- a/builder/ar.go +++ b/builder/ar.go @@ -16,7 +16,7 @@ import ( "github.com/blakesmith/ar" ) -// makeArchive creates an arcive for static linking from a list of object files +// makeArchive creates an archive for static linking from a list of object files // given as a parameter. It is equivalent to the following command: // // ar -rcs diff --git a/builder/cc1as.h b/builder/cc1as.h index 4b22fc3e81..67b97f6cf9 100644 --- a/builder/cc1as.h +++ b/builder/cc1as.h @@ -93,7 +93,7 @@ struct AssemblerInvocation { EmitDwarfUnwindType EmitDwarfUnwind; // Whether to emit compact-unwind for non-canonical entries. - // Note: maybe overriden by other constraints. + // Note: maybe overridden by other constraints. unsigned EmitCompactUnwindNonCanonical : 1; /// The name of the relocation model to use. diff --git a/builder/tools.go b/builder/tools.go index e1c6443c65..bd18aa09d2 100644 --- a/builder/tools.go +++ b/builder/tools.go @@ -49,7 +49,7 @@ func link(linker string, flags ...string) error { err := cmd.Run() if err != nil { if buf.Len() == 0 { - // The linker failed but ther was no output. + // The linker failed but there was no output. // Therefore, show some output anyway. return fmt.Errorf("failed to run linker: %w", err) } diff --git a/compileopts/config.go b/compileopts/config.go index 67c773de16..eca037503c 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -60,7 +60,7 @@ func (c *Config) GOOS() string { } // GOARCH returns the GOARCH of the target. This might not always be the actual -// archtecture: for example, the AVR target is not supported by the Go standard +// architecture: for example, the AVR target is not supported by the Go standard // library so such targets will usually pretend to be linux/arm. func (c *Config) GOARCH() string { return c.Target.GOARCH @@ -461,7 +461,7 @@ func (c *Config) BinaryFormat(ext string) string { // Programmer returns the flash method and OpenOCD interface name given a // particular configuration. It may either be all configured in the target JSON -// file or be modified using the -programmmer command-line option. +// file or be modified using the -programmer command-line option. func (c *Config) Programmer() (method, openocdInterface string) { switch c.Options.Programmer { case "": diff --git a/compiler/asserts.go b/compiler/asserts.go index 035fda6160..f07b73bc26 100644 --- a/compiler/asserts.go +++ b/compiler/asserts.go @@ -99,7 +99,7 @@ func (b *builder) createUnsafeSliceStringCheck(name string, ptr, len llvm.Value, // However, in practice, it is also necessary to check that the length is // not too big that a GEP wouldn't be possible without wrapping the pointer. // These two checks (non-negative and not too big) can be merged into one - // using an unsiged greater than. + // using an unsigned greater than. // Make sure the len value is at least as big as a uintptr. len = b.extendInteger(len, lenType, b.uintptrType) diff --git a/compiler/compiler.go b/compiler/compiler.go index e9697fae27..bc17250d99 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -242,7 +242,7 @@ func NewTargetMachine(config *Config) (llvm.TargetMachine, error) { } // Sizes returns a types.Sizes appropriate for the given target machine. It -// includes the correct int size and aligment as is necessary for the Go +// includes the correct int size and alignment as is necessary for the Go // typechecker. func Sizes(machine llvm.TargetMachine) types.Sizes { targetData := machine.CreateTargetData() diff --git a/compiler/gc.go b/compiler/gc.go index 9d568a174b..fc0e6e687f 100644 --- a/compiler/gc.go +++ b/compiler/gc.go @@ -78,7 +78,7 @@ func (b *builder) trackValue(value llvm.Value) { } } -// trackPointer creates a call to runtime.trackPointer, bitcasting the poitner +// trackPointer creates a call to runtime.trackPointer, bitcasting the pointer // first if needed. The input value must be of LLVM pointer type. func (b *builder) trackPointer(value llvm.Value) { b.createRuntimeCall("trackPointer", []llvm.Value{value, b.stackChainAlloca}, "") diff --git a/compiler/interface.go b/compiler/interface.go index fc698c7a97..dffaeec0ad 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -86,7 +86,7 @@ func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token. // extractValueFromInterface extract the value from an interface value // (runtime._interface) under the assumption that it is of the type given in -// llvmType. The behavior is undefied if the interface is nil or llvmType +// llvmType. The behavior is undefined if the interface is nil or llvmType // doesn't match the underlying type of the interface. func (b *builder) extractValueFromInterface(itf llvm.Value, llvmType llvm.Type) llvm.Value { valuePtr := b.CreateExtractValue(itf, 1, "typeassert.value.ptr") diff --git a/compiler/llvm.go b/compiler/llvm.go index bdbf0ece16..59aaee8fdd 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -419,7 +419,7 @@ func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.In } } -// archFamily returns the archtecture from the LLVM triple but with some +// archFamily returns the architecture from the LLVM triple but with some // architecture names ("armv6", "thumbv7m", etc) merged into a single // architecture name ("arm"). func (c *compilerContext) archFamily() string { diff --git a/compiler/llvmutil/llvm.go b/compiler/llvmutil/llvm.go index 48fddffbe5..607e91e8d8 100644 --- a/compiler/llvmutil/llvm.go +++ b/compiler/llvmutil/llvm.go @@ -207,7 +207,7 @@ func AppendToGlobal(mod llvm.Module, globalName string, values ...llvm.Value) { used.SetLinkage(llvm.AppendingLinkage) } -// Return the LLVM major version. +// Version returns the LLVM major version. func Version() int { majorStr := strings.Split(llvm.Version, ".")[0] major, err := strconv.Atoi(majorStr) diff --git a/compiler/map.go b/compiler/map.go index 1c124c2b20..b4c5267233 100644 --- a/compiler/map.go +++ b/compiler/map.go @@ -326,7 +326,7 @@ func (b *builder) zeroUndefBytes(llvmType llvm.Type, ptr llvm.Value) error { if i < numFields-1 { nextOffset = b.targetData.ElementOffset(llvmStructType, i+1) } else { - // Last field? Next offset is the total size of the allcoate struct. + // Last field? Next offset is the total size of the allocate struct. nextOffset = b.targetData.TypeAllocSize(llvmStructType) } diff --git a/compiler/syscall.go b/compiler/syscall.go index d9d21c3cf5..d878df0270 100644 --- a/compiler/syscall.go +++ b/compiler/syscall.go @@ -145,7 +145,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) { // Also useful: // https://web.archive.org/web/20220529105937/https://www.linux-mips.org/wiki/Syscall // The syscall number goes in r2, the result also in r2. - // Register r7 is both an input paramter and an output parameter: if it + // Register r7 is both an input parameter and an output parameter: if it // is non-zero, the system call failed and r2 is the error code. // The code below implements the O32 syscall ABI, not the N32 ABI. It // could implement both at the same time if needed (like what appears to diff --git a/diagnostics/diagnostics.go b/diagnostics/diagnostics.go index e58becfb8a..1e363580a9 100644 --- a/diagnostics/diagnostics.go +++ b/diagnostics/diagnostics.go @@ -43,7 +43,7 @@ func CreateDiagnostics(err error) ProgramDiagnostic { if err == nil { return nil } - // Right now, the compiler will only show errors for the first pacakge that + // Right now, the compiler will only show errors for the first package that // fails to build. This is likely to change in the future. return ProgramDiagnostic{ createPackageDiagnostic(err), @@ -147,7 +147,7 @@ func createDiagnostics(err error) []Diagnostic { // last package fmt.Fprintln(buf, "\timports", pkgPath+": "+err.Err.Error()) } else { - // not the last pacakge + // not the last package fmt.Fprintln(buf, "\timports", pkgPath) } } diff --git a/go.mod b/go.mod index b054e77b63..bf85ef3ad1 100644 --- a/go.mod +++ b/go.mod @@ -7,14 +7,12 @@ require ( github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 github.com/chromedp/cdproto v0.0.0-20220113222801-0725d94bb6ee github.com/chromedp/chromedp v0.7.6 - github.com/client9/misspell v0.3.4 github.com/gofrs/flock v0.8.1 github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892 github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-tty v0.0.4 - github.com/mgechev/revive v1.3.7 github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 go.bug.st/serial v1.6.0 golang.org/x/net v0.26.0 @@ -25,23 +23,14 @@ require ( ) require ( - github.com/BurntSushi/toml v1.3.2 // indirect - github.com/chavacava/garif v0.1.0 // indirect github.com/chromedp/sysutil v1.0.0 // indirect github.com/creack/goselect v0.1.2 // indirect - github.com/fatih/color v1.16.0 // indirect - github.com/fatih/structtag v1.2.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/ws v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.9 // indirect - github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/spf13/afero v1.11.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect golang.org/x/text v0.16.0 // indirect ) diff --git a/go.sum b/go.sum index 8d6dc516ca..7a6d1f4a97 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,7 @@ -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/aykevl/go-wasm v0.0.2-0.20240312204833-50275154210c h1:4T0Vj1UkGgcpkRrmn7SbokebnlfxJcMZPgWtOYACAAA= github.com/aykevl/go-wasm v0.0.2-0.20240312204833-50275154210c/go.mod h1:7sXyiaA0WtSogCu67R2252fQpVmJMh9JWJ9ddtGkpWw= github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI= github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= -github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= -github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= github.com/chromedp/cdproto v0.0.0-20211126220118-81fa0469ad77/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U= github.com/chromedp/cdproto v0.0.0-20220113222801-0725d94bb6ee h1:+SFdIVfQpG0s0DHYzou0kgfE0n0ZjKPwbiRJsXrZegU= github.com/chromedp/cdproto v0.0.0-20220113222801-0725d94bb6ee/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U= @@ -13,17 +9,10 @@ github.com/chromedp/chromedp v0.7.6 h1:2juGaktzjwULlsn+DnvIZXFUckEp5xs+GOBroaea+ github.com/chromedp/chromedp v0.7.6/go.mod h1:ayT4YU/MGAALNfOg9gNrpGSAdnU51PMx+FCeuT1iXzo= github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= -github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= -github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= 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= @@ -51,41 +40,24 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-tty v0.0.4 h1:NVikla9X8MN0SQAqCYzpGyXv0jY7MNl3HOWD2dkle7E= github.com/mattn/go-tty v0.0.4/go.mod h1:u5GGXBtZU6RQoKV8gY5W6UhMudbR5vXnUe7j3pxse28= -github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 h1:zpIH83+oKzcpryru8ceC6BxnoG8TBrhgAvRg8obzup0= -github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= -github.com/mgechev/revive v1.3.7 h1:502QY0vQGe9KtYJ9FpxMz9rL+Fc/P13CI5POL4uHCcE= -github.com/mgechev/revive v1.3.7/go.mod h1:RJ16jUbF0OWC3co/+XTxmFNgEpUPwnnA0BRllX2aDNA= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5 h1:1SoBaSPudixRecmlHXb/GxmaD3fLMtHIDN13QujwQuc= github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= go.bug.st/serial v1.6.0 h1:mAbRGN4cKE2J5gMwsMHC2KQisdLRQssO9WSM+rbZJ8A= go.bug.st/serial v1.6.0/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -103,7 +75,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= tinygo.org/x/go-llvm v0.0.0-20240627184919-3b50c76783a8 h1:bLsZXRUBavt++CJlMN7sppNziqu3LyamESLhFJcpqFQ= diff --git a/internal/tools/go.mod b/internal/tools/go.mod new file mode 100644 index 0000000000..854c93fc0a --- /dev/null +++ b/internal/tools/go.mod @@ -0,0 +1,30 @@ +// TODO: remove this (by merging it into the top-level go.mod) +// once the top level go.mod specifies a go new enough to make our version of misspell happy. + +module tools + +go 1.21 + +require ( + github.com/golangci/misspell v0.6.0 + github.com/mgechev/revive v1.3.9 +) + +require ( + github.com/BurntSushi/toml v1.4.0 // indirect + github.com/chavacava/garif v0.1.0 // indirect + github.com/fatih/color v1.17.0 // indirect + github.com/fatih/structtag v1.2.0 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/spf13/afero v1.11.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.23.0 // indirect +) diff --git a/internal/tools/go.sum b/internal/tools/go.sum new file mode 100644 index 0000000000..dea0fb1f9c --- /dev/null +++ b/internal/tools/go.sum @@ -0,0 +1,56 @@ +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= +github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= +github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 h1:zpIH83+oKzcpryru8ceC6BxnoG8TBrhgAvRg8obzup0= +github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= +github.com/mgechev/revive v1.3.9 h1:18Y3R4a2USSBF+QZKFQwVkBROUda7uoBlkEuBD+YD1A= +github.com/mgechev/revive v1.3.9/go.mod h1:+uxEIr5UH0TjXWHTno3xh4u7eg6jDpXKzQccA9UGhHU= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= +golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/tools/tools.go b/internal/tools/tools.go index d592921a54..0cbb56edf7 100644 --- a/internal/tools/tools.go +++ b/internal/tools/tools.go @@ -5,9 +5,9 @@ package tools import ( - _ "github.com/client9/misspell" + _ "github.com/golangci/misspell" _ "github.com/mgechev/revive" ) -//go:generate go install github.com/client9/misspell/cmd/misspell +//go:generate go install github.com/golangci/misspell/cmd/misspell //go:generate go install github.com/mgechev/revive diff --git a/interp/README.md b/interp/README.md index c0a794cc75..26ab657c0c 100644 --- a/interp/README.md +++ b/interp/README.md @@ -28,7 +28,7 @@ All in all, this design provides several benefits: it should be a whole lot faster for loops as it doesn't have to call into LLVM (via CGo) for every operation. -As mentioned, this partial evaulator comes in three parts: a compiler, an +As mentioned, this partial evaluator comes in three parts: a compiler, an interpreter, and a memory manager. ## Compiler diff --git a/misspell.csv b/misspell.csv new file mode 100644 index 0000000000..7d247aea1d --- /dev/null +++ b/misspell.csv @@ -0,0 +1,22 @@ +acuire, acquire +adust, adjust +allcoate, allocate +archtecture, architecture +arcive, archive +configration, configuration +contants, constants +cricital, critical +evaulator, evaluator +freqency, frequency +frquency, frequency +implmented, implemented +interrput, interrupt +interrut, interrupt +poitner, pointer +probbably, probably +refection, reflection +satisifying, satisfying +simulataneously, simultaneously +suggets, suggests +undefied, undefined +unsiged, unsigned diff --git a/src/crypto/rand/rand_arc4random.go b/src/crypto/rand/rand_arc4random.go index 64a6218bd9..ada1a96192 100644 --- a/src/crypto/rand/rand_arc4random.go +++ b/src/crypto/rand/rand_arc4random.go @@ -3,7 +3,7 @@ // This implementation of crypto/rand uses the arc4random_buf function // (available on both MacOS and WASI) to generate random numbers. // -// Note: arc4random_buf (unlike what the name suggets) does not use the insecure +// Note: arc4random_buf (unlike what the name suggests) does not use the insecure // RC4 cipher. Instead, it uses a high-quality cipher, varying by the libc // implementation. diff --git a/src/crypto/tls/tls.go b/src/crypto/tls/tls.go index 75f4a25093..1520c30fca 100644 --- a/src/crypto/tls/tls.go +++ b/src/crypto/tls/tls.go @@ -101,7 +101,7 @@ type Dialer struct { // // The returned Conn, if any, will always be of type *Conn. func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { - return nil, errors.New("tls:DialContext not implmented") + return nil, errors.New("tls:DialContext not implemented") } // LoadX509KeyPair reads and parses a public/private key pair from a pair diff --git a/src/machine/machine_atmega1280.go b/src/machine/machine_atmega1280.go index 49564a3ce3..1b8cb848de 100644 --- a/src/machine/machine_atmega1280.go +++ b/src/machine/machine_atmega1280.go @@ -180,7 +180,7 @@ func (pwm PWM) Configure(config PWMConfig) error { // Set the PWM mode to fast PWM (mode = 3). avr.TCCR0A.Set(avr.TCCR0A_WGM00 | avr.TCCR0A_WGM01) // monotonic timer is using the same time as PWM:0 - // we must adust internal settings of monotonic timer when PWM:0 settings changed + // we must adjust internal settings of monotonic timer when PWM:0 settings changed adjustMonotonicTimer() } else { avr.TCCR2B.Set(prescaler) @@ -718,7 +718,7 @@ func (pwm PWM) Set(channel uint8, value uint32) { } } // monotonic timer is using the same time as PWM:0 - // we must adust internal settings of monotonic timer when PWM:0 settings changed + // we must adjust internal settings of monotonic timer when PWM:0 settings changed adjustMonotonicTimer() case 1: mask := interrupt.Disable() diff --git a/src/machine/machine_atmega328.go b/src/machine/machine_atmega328.go index e4b2bb06f9..c354ccb888 100644 --- a/src/machine/machine_atmega328.go +++ b/src/machine/machine_atmega328.go @@ -56,7 +56,7 @@ func (pwm PWM) Configure(config PWMConfig) error { // Set the PWM mode to fast PWM (mode = 3). avr.TCCR0A.Set(avr.TCCR0A_WGM00 | avr.TCCR0A_WGM01) // monotonic timer is using the same time as PWM:0 - // we must adust internal settings of monotonic timer when PWM:0 settings changed + // we must adjust internal settings of monotonic timer when PWM:0 settings changed adjustMonotonicTimer() } else { avr.TCCR2B.Set(prescaler) @@ -385,7 +385,7 @@ func (pwm PWM) Set(channel uint8, value uint32) { } } // monotonic timer is using the same time as PWM:0 - // we must adust internal settings of monotonic timer when PWM:0 settings changed + // we must adjust internal settings of monotonic timer when PWM:0 settings changed adjustMonotonicTimer() case 1: mask := interrupt.Disable() diff --git a/src/machine/machine_esp32c3.go b/src/machine/machine_esp32c3.go index b1ad0bc2fc..727fcc1e64 100644 --- a/src/machine/machine_esp32c3.go +++ b/src/machine/machine_esp32c3.go @@ -480,7 +480,7 @@ func (uart *UART) enableTransmitter() { uart.Bus.SetCONF0_TXFIFO_RST(0) // TXINFO empty threshold is when txfifo_empty_int interrupt produced after the amount of data in Tx-FIFO is less than this register value. uart.Bus.SetCONF1_TXFIFO_EMPTY_THRHD(uart_empty_thresh_default) - // we are not using interrut on TX since write we are waiting for FIFO to have space. + // we are not using interrupt on TX since write we are waiting for FIFO to have space. // uart.Bus.INT_ENA.SetBits(esp.UART_INT_ENA_TXFIFO_EMPTY_INT_ENA) } diff --git a/src/machine/machine_mimxrt1062.go b/src/machine/machine_mimxrt1062.go index 8c42c55645..74d01c7661 100644 --- a/src/machine/machine_mimxrt1062.go +++ b/src/machine/machine_mimxrt1062.go @@ -451,7 +451,7 @@ func (p Pin) getGPIO() (norm *nxp.GPIO_Type, fast *nxp.GPIO_Type) { } } -// getPad returns both the pad and mux configration registers for a given Pin. +// getPad returns both the pad and mux configuration registers for a given Pin. func (p Pin) getPad() (pad *volatile.Register32, mux *volatile.Register32) { switch p.getPort() { case portA: diff --git a/src/machine/machine_rp2040_i2c.go b/src/machine/machine_rp2040_i2c.go index 7ca2a87cf9..c808a07ca8 100644 --- a/src/machine/machine_rp2040_i2c.go +++ b/src/machine/machine_rp2040_i2c.go @@ -24,7 +24,7 @@ var ( // here: https://github.com/vmilea/pico_i2c_slave // Features: Taken from datasheet. -// Default controller mode, with target mode available (not simulataneously). +// Default controller mode, with target mode available (not simultaneously). // Default target address of RP2040: 0x055 // Supports 10-bit addressing in controller mode // 16-element transmit buffer diff --git a/src/machine/machine_stm32f103.go b/src/machine/machine_stm32f103.go index 66a74d04b6..545c431110 100644 --- a/src/machine/machine_stm32f103.go +++ b/src/machine/machine_stm32f103.go @@ -399,7 +399,7 @@ func (i2c *I2C) getFreqRange(config I2CConfig) uint32 { // pclk1 clock speed is main frequency divided by PCLK1 prescaler (div 2) pclk1 := CPUFrequency() / 2 - // set freqency range to PCLK1 clock speed in MHz + // set frequency range to PCLK1 clock speed in MHz // aka setting the value 36 means to use 36 MHz clock return pclk1 / 1000000 } diff --git a/src/machine/machine_stm32l0.go b/src/machine/machine_stm32l0.go index e3fcefb572..b7dc1581c3 100644 --- a/src/machine/machine_stm32l0.go +++ b/src/machine/machine_stm32l0.go @@ -256,10 +256,10 @@ func (spi SPI) getBaudRate(config SPIConfig) uint32 { } // set frequency dependent on PCLK prescaler. Since these are rather weird - // speeds due to the CPU freqency, pick a range up to that frquency for + // speeds due to the CPU frequency, pick a range up to that frequency for // clients to use more human-understandable numbers, e.g. nearest 100KHz - // These are based on APB2 clock frquency (84MHz on the discovery board) + // These are based on APB2 clock frequency (84MHz on the discovery board) // TODO: also include the MCU/APB clock setting in the equation switch { case localFrequency < 328125: diff --git a/src/machine/machine_stm32l4.go b/src/machine/machine_stm32l4.go index 4eb3588f7b..c22cf616b8 100644 --- a/src/machine/machine_stm32l4.go +++ b/src/machine/machine_stm32l4.go @@ -327,10 +327,10 @@ func (spi SPI) getBaudRate(config SPIConfig) uint32 { localFrequency := config.Frequency // set frequency dependent on PCLK prescaler. Since these are rather weird - // speeds due to the CPU freqency, pick a range up to that frquency for + // speeds due to the CPU frequency, pick a range up to that frequency for // clients to use more human-understandable numbers, e.g. nearest 100KHz - // These are based on 80MHz peripheral clock frquency + // These are based on 80MHz peripheral clock frequency switch { case localFrequency < 312500: conf = stm32.SPI_CR1_BR_Div256 diff --git a/src/os/exec.go b/src/os/exec.go index cf295e6fb7..1ea9dcbd8c 100644 --- a/src/os/exec.go +++ b/src/os/exec.go @@ -88,6 +88,7 @@ func (p *Process) Release() error { return p.release() } +// FindProcess looks for a running process by its pid. // Keep compatibility with golang and always succeed and return new proc with pid on Linux. func FindProcess(pid int) (*Process, error) { return findProcess(pid) diff --git a/src/os/os_anyos_test.go b/src/os/os_anyos_test.go index 67c609949e..7b4b383772 100644 --- a/src/os/os_anyos_test.go +++ b/src/os/os_anyos_test.go @@ -56,7 +56,7 @@ func TestStatBadDir(t *testing.T) { badDir := filepath.Join(dir, "not-exist/really-not-exist") _, err := Stat(badDir) if pe, ok := err.(*fs.PathError); !ok || !IsNotExist(err) || pe.Path != badDir { - t.Errorf("Mkdir error = %#v; want PathError for path %q satisifying IsNotExist", err, badDir) + t.Errorf("Mkdir error = %#v; want PathError for path %q satisfying IsNotExist", err, badDir) } } diff --git a/src/os/tempfile_test.go b/src/os/tempfile_test.go index d5ade369a0..88f6481c44 100644 --- a/src/os/tempfile_test.go +++ b/src/os/tempfile_test.go @@ -158,7 +158,7 @@ func TestMkdirTempBadDir(t *testing.T) { badDir := filepath.Join(dir, "not-exist") _, err = MkdirTemp(badDir, "foo") if pe, ok := err.(*fs.PathError); !ok || !IsNotExist(err) || pe.Path != badDir { - t.Errorf("TempDir error = %#v; want PathError for path %q satisifying IsNotExist", err, badDir) + t.Errorf("TempDir error = %#v; want PathError for path %q satisfying IsNotExist", err, badDir) } } diff --git a/src/reflect/type.go b/src/reflect/type.go index 1a0d4fc854..aa84c77a24 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -404,7 +404,7 @@ const ( // The base type struct. All type structs start with this. type rawType struct { - meta uint8 // metadata byte, contains kind and flags (see contants above) + meta uint8 // metadata byte, contains kind and flags (see constants above) } // All types that have an element type: named, chan, slice, array, map (but not diff --git a/src/reflect/value_test.go b/src/reflect/value_test.go index 40f0919bdc..508b358ad9 100644 --- a/src/reflect/value_test.go +++ b/src/reflect/value_test.go @@ -164,7 +164,7 @@ func TestTinyMap(t *testing.T) { // make sure we can get it out v2 := refut.MapIndex(ValueOf(unmarshalerText{"x", "y"})) if !v2.IsValid() || !v2.Bool() { - t.Errorf("Failed to look up map struct key with refection") + t.Errorf("Failed to look up map struct key with reflection") } // put in a key with reflection diff --git a/src/runtime/gc_stack_portable.go b/src/runtime/gc_stack_portable.go index f822d8482e..d35e16e30c 100644 --- a/src/runtime/gc_stack_portable.go +++ b/src/runtime/gc_stack_portable.go @@ -26,7 +26,7 @@ type stackChainObject struct { // // Therefore, we only need to scan the system stack. // It is relatively easy to scan the system stack while we're on it: we can -// simply read __stack_pointer and __global_base and scan the area inbetween. +// simply read __stack_pointer and __global_base and scan the area in between. // Unfortunately, it's hard to get the system stack pointer while we're on a // goroutine stack. But when we're on a goroutine stack, the system stack is in // the scheduler which means there shouldn't be anything on the system stack diff --git a/src/runtime/interrupt/interrupt_avr.go b/src/runtime/interrupt/interrupt_avr.go index 0af71a89e5..f27da8a048 100644 --- a/src/runtime/interrupt/interrupt_avr.go +++ b/src/runtime/interrupt/interrupt_avr.go @@ -27,7 +27,7 @@ func Disable() (state State) { // Restore restores interrupts to what they were before. Give the previous state // returned by Disable as a parameter. If interrupts were disabled before // calling Disable, this will not re-enable interrupts, allowing for nested -// cricital sections. +// critical sections. func Restore(state State) { // SREG is at I/O address 0x3f. device.AsmFull("out 0x3f, {state}", map[string]interface{}{ diff --git a/src/runtime/interrupt/interrupt_cortexm.go b/src/runtime/interrupt/interrupt_cortexm.go index a34dff7879..708d486bdf 100644 --- a/src/runtime/interrupt/interrupt_cortexm.go +++ b/src/runtime/interrupt/interrupt_cortexm.go @@ -46,7 +46,7 @@ func Disable() (state State) { // Restore restores interrupts to what they were before. Give the previous state // returned by Disable as a parameter. If interrupts were disabled before // calling Disable, this will not re-enable interrupts, allowing for nested -// cricital sections. +// critical sections. func Restore(state State) { arm.EnableInterrupts(uintptr(state)) } diff --git a/src/runtime/interrupt/interrupt_gameboyadvance.go b/src/runtime/interrupt/interrupt_gameboyadvance.go index 13f5fbe09d..2224066ed7 100644 --- a/src/runtime/interrupt/interrupt_gameboyadvance.go +++ b/src/runtime/interrupt/interrupt_gameboyadvance.go @@ -92,7 +92,7 @@ func Disable() (state State) { // Restore restores interrupts to what they were before. Give the previous state // returned by Disable as a parameter. If interrupts were disabled before // calling Disable, this will not re-enable interrupts, allowing for nested -// cricital sections. +// critical sections. func Restore(state State) { // Restore interrupts to the previous state. gba.INTERRUPT.PAUSE.Set(uint16(state)) diff --git a/src/runtime/interrupt/interrupt_none.go b/src/runtime/interrupt/interrupt_none.go index 255fca9ea8..2f4aae6c8a 100644 --- a/src/runtime/interrupt/interrupt_none.go +++ b/src/runtime/interrupt/interrupt_none.go @@ -21,7 +21,7 @@ func Disable() (state State) { // Restore restores interrupts to what they were before. Give the previous state // returned by Disable as a parameter. If interrupts were disabled before // calling Disable, this will not re-enable interrupts, allowing for nested -// cricital sections. +// critical sections. func Restore(state State) {} // In returns whether the system is currently in an interrupt. diff --git a/src/runtime/interrupt/interrupt_tinygoriscv.go b/src/runtime/interrupt/interrupt_tinygoriscv.go index 2b97a2f11a..fd21afcda9 100644 --- a/src/runtime/interrupt/interrupt_tinygoriscv.go +++ b/src/runtime/interrupt/interrupt_tinygoriscv.go @@ -23,7 +23,7 @@ func Disable() (state State) { // Restore restores interrupts to what they were before. Give the previous state // returned by Disable as a parameter. If interrupts were disabled before // calling Disable, this will not re-enable interrupts, allowing for nested -// cricital sections. +// critical sections. func Restore(state State) { riscv.EnableInterrupts(uintptr(state)) } diff --git a/src/runtime/interrupt/interrupt_xtensa.go b/src/runtime/interrupt/interrupt_xtensa.go index fe962f0451..69dc711836 100644 --- a/src/runtime/interrupt/interrupt_xtensa.go +++ b/src/runtime/interrupt/interrupt_xtensa.go @@ -23,7 +23,7 @@ func Disable() (state State) { // Restore restores interrupts to what they were before. Give the previous state // returned by Disable as a parameter. If interrupts were disabled before // calling Disable, this will not re-enable interrupts, allowing for nested -// cricital sections. +// critical sections. func Restore(state State) { device.AsmFull("wsr {state}, PS", map[string]interface{}{ "state": state, diff --git a/src/sync/mutex_test.go b/src/sync/mutex_test.go index 1e398e5aca..be24d93031 100644 --- a/src/sync/mutex_test.go +++ b/src/sync/mutex_test.go @@ -120,7 +120,7 @@ func TestRWMutexUncontended(t *testing.T) { mu.Lock() mu.Unlock() - // Acuire several read locks. + // Acquire several read locks. const n = 5 for i := 0; i < n; i++ { mu.RLock() From e27b2c4ad67f1cc3e7914ab1a1e82de95038dcd4 Mon Sep 17 00:00:00 2001 From: Dan Kegel Date: Mon, 12 Aug 2024 14:55:53 -0700 Subject: [PATCH 135/444] circleci: our version of misspell is no longer supported with go < 1.21, so disable lint in go 1.19 ci job I jumped through quite a few hoops to get test-llvm15-go119 to work, but this last hoop seems not worth jumping through: cd internal/tools && go generate -tags tools ./ /go/pkg/mod/github.com/golangci/misspell@v0.6.0/mime.go:10:2: package slices is not in GOROOT (/usr/local/go/src/slices) tools.go:12: running "go": exit status 1 make: *** [GNUmakefile:952: tools] Error 1 I mean, we could patch misspell to not use slices, or to use the prerelease slices, but... --- .circleci/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8d104de04e..f694a4ee11 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -104,6 +104,8 @@ jobs: steps: - test-linux: llvm: "15" + # "make lint" fails before go 1.21 because internal/tools/go.mod specifies packages that require go 1.21 + fmt-check: false resource_class: large test-llvm18-go122: docker: From a47ff02c82b14af7bb97965274edbe6d60fa6b6a Mon Sep 17 00:00:00 2001 From: Dan Kegel Date: Tue, 13 Aug 2024 11:39:41 -0700 Subject: [PATCH 136/444] make spell: add a few missing misspellings, fix format of .csv file, also fix *.md --- CHANGELOG.md | 4 +- GNUmakefile | 4 +- interp/interp.go | 2 +- misspell.csv | 48 ++++++++++++---------- src/machine/machine_rp2040_i2c.go | 2 +- src/runtime/interrupt/interrupt_esp32c3.go | 2 +- 6 files changed, 33 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb2de66477..956d50b6d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ - `builder`: keep un-wasm-opt'd .wasm if -work was passed - `builder`: make sure wasm-opt command line is printed if asked - `cgo`: implement shift operations in preprocessor macros - - `interp`: checking for methodset existance + - `interp`: checking for methodset existence * **standard library** - `machine`: add `__tinygo_spi_tx` function to simulator @@ -217,7 +217,7 @@ - `reflect`: add SetZero - `reflect`: fix iterating over maps with interface{} keys - `reflect`: implement Value.Grow - - `reflect`: remove unecessary heap allocations + - `reflect`: remove unnecessary heap allocations - `reflect`: use .key() instead of a type assert - `sync`: add implementation from upstream Go for OnceFunc, OnceValue, and OnceValues * **targets** diff --git a/GNUmakefile b/GNUmakefile index 504e1561af..68bb4430f5 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -963,11 +963,11 @@ lint: tools ## Lint source tree SPELLDIRSCMD=find . -depth 1 -type d | egrep -wv '.git|lib|llvm|src'; find src -depth 1 | egrep -wv 'device|internal|net|vendor'; find src/internal -depth 1 -type d | egrep -wv src/internal/wasi .PHONY: spell spell: tools ## Spellcheck source tree - misspell -error --dict misspell.csv -i 'ackward,devided,extint,rela' $$( $(SPELLDIRSCMD) ) + misspell -error --dict misspell.csv -i 'ackward,devided,extint,rela' $$( $(SPELLDIRSCMD) ) *.md .PHONY: spellfix spellfix: tools ## Same as spell, but fixes what it finds - misspell -w --dict misspell.csv -i 'ackward,devided,extint,rela' $$( $(SPELLDIRSCMD) ) + misspell -w --dict misspell.csv -i 'ackward,devided,extint,rela' $$( $(SPELLDIRSCMD) ) *.md # https://www.client9.com/self-documenting-makefiles/ .PHONY: help diff --git a/interp/interp.go b/interp/interp.go index 63a664920d..80afc39c74 100644 --- a/interp/interp.go +++ b/interp/interp.go @@ -52,7 +52,7 @@ func newRunner(mod llvm.Module, timeout time.Duration, debug bool) *runner { return &r } -// Dispose deallocates all alloated LLVM resources. +// Dispose deallocates all allocated LLVM resources. func (r *runner) dispose() { r.targetData.Dispose() r.targetData = llvm.TargetData{} diff --git a/misspell.csv b/misspell.csv index 7d247aea1d..d12a28ed9c 100644 --- a/misspell.csv +++ b/misspell.csv @@ -1,22 +1,26 @@ -acuire, acquire -adust, adjust -allcoate, allocate -archtecture, architecture -arcive, archive -configration, configuration -contants, constants -cricital, critical -evaulator, evaluator -freqency, frequency -frquency, frequency -implmented, implemented -interrput, interrupt -interrut, interrupt -poitner, pointer -probbably, probably -refection, reflection -satisifying, satisfying -simulataneously, simultaneously -suggets, suggests -undefied, undefined -unsiged, unsigned +acuire,acquire +addess,address +adust,adjust +allcoate,allocate +alloated,allocated +archtecture,architecture +arcive,archive +beconfigured,be configured +configration,configuration +contants,constants +cricital,critical +evaulator,evaluator +freqency,frequency +frquency,frequency +implmented,implemented +interrput,interrupt +interrut,interrupt +poitner,pointer +probbably,probably +refection,reflection +satisifying,satisfying +simulataneously,simultaneously +suggets,suggests +undefied,undefined +unecessary,unnecessary +unsiged,unsigned diff --git a/src/machine/machine_rp2040_i2c.go b/src/machine/machine_rp2040_i2c.go index c808a07ca8..b7dc63d2b2 100644 --- a/src/machine/machine_rp2040_i2c.go +++ b/src/machine/machine_rp2040_i2c.go @@ -36,7 +36,7 @@ var ( // GPIO config // Each controller must connect its clock SCL and data SDA to one pair of GPIOs. // The I2C standard requires that drivers drivea signal low, or when not driven the signal will be pulled high. -// This applies to SCL and SDA. The GPIO pads should beconfigured for: +// This applies to SCL and SDA. The GPIO pads should be configured for: // Pull-up enabled // Slew rate limited // Schmitt trigger enabled diff --git a/src/runtime/interrupt/interrupt_esp32c3.go b/src/runtime/interrupt/interrupt_esp32c3.go index b1a5bb1b3c..4e3e3dccfc 100644 --- a/src/runtime/interrupt/interrupt_esp32c3.go +++ b/src/runtime/interrupt/interrupt_esp32c3.go @@ -222,7 +222,7 @@ func handleException(mcause uintptr) { println("*** Exception: mcause:", mcause) switch uint32(mcause & 0x1f) { case 1: - println("*** virtual addess:", riscv.MTVAL.Get()) + println("*** virtual address:", riscv.MTVAL.Get()) case 2: println("*** opcode:", riscv.MTVAL.Get()) case 5: From a35b983a5dafbd29c054bf73215b2ee07d47f036 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 31 Jul 2024 14:06:21 +0200 Subject: [PATCH 137/444] linux: use -musleabi* instead of -gnueabi* (or nothing) This more accurately describes the libc we are using. This also adds the environment to _all_ Linux systems, not just ARM. And selects the correct ABI (soft/hardfloat) that's in use. I don't know whether it has any practical implications, but LLVM appears to be using this information so it seems wise to use the correct values. --- compileopts/target.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/compileopts/target.go b/compileopts/target.go index 9388b84960..b168da3c20 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -418,8 +418,15 @@ func defaultTarget(options *Options) (*TargetSpec, error) { spec.Triple = llvmarch + "-" + llvmvendor + "-" + llvmos if options.GOOS == "windows" { spec.Triple += "-gnu" - } else if options.GOARCH == "arm" { - spec.Triple += "-gnueabihf" + } else if options.GOOS == "linux" { + // We use musl on Linux (not glibc) so we should use -musleabi* instead + // of -gnueabi*. + // The *hf suffix selects between soft/hard floating point ABI. + if spec.SoftFloat { + spec.Triple += "-musleabi" + } else { + spec.Triple += "-musleabihf" + } } // Add extra assembly files (needed for the scheduler etc). From 4dc07d6cc3f06479faca543f94b286a047197b7e Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 31 Jul 2024 14:17:24 +0200 Subject: [PATCH 138/444] arm: support softfloat in GOARM environment variable This adds softfloat support to GOARM, as proposed here: https://github.com/golang/go/issues/61588 This is similar to https://github.com/tinygo-org/tinygo/pull/4189 but with a few differences: * It is based on the work to support MIPS softfloat. * It fixes the issue that the default changed to softfloat everywhere. This PR defaults to softfloat on ARMv5 and hardfloat on ARMv6 and ARMv7. * It also compiles the C libraries (compiler-rt, musl) using the same soft/hard floating point support. This is important because otherwise a GOARM=7,softfloat binary could still contain hardware floating point instructions. --- builder/builder_test.go | 9 ++++--- builder/library.go | 7 ++++++ compileopts/target.go | 52 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/builder/builder_test.go b/builder/builder_test.go index 92ed9bdcc7..3d714808fa 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -53,9 +53,12 @@ func TestClangAttributes(t *testing.T) { for _, options := range []*compileopts.Options{ {GOOS: "linux", GOARCH: "386"}, {GOOS: "linux", GOARCH: "amd64"}, - {GOOS: "linux", GOARCH: "arm", GOARM: "5"}, - {GOOS: "linux", GOARCH: "arm", GOARM: "6"}, - {GOOS: "linux", GOARCH: "arm", GOARM: "7"}, + {GOOS: "linux", GOARCH: "arm", GOARM: "5,softfloat"}, + {GOOS: "linux", GOARCH: "arm", GOARM: "6,softfloat"}, + {GOOS: "linux", GOARCH: "arm", GOARM: "7,softfloat"}, + {GOOS: "linux", GOARCH: "arm", GOARM: "5,hardfloat"}, + {GOOS: "linux", GOARCH: "arm", GOARM: "6,hardfloat"}, + {GOOS: "linux", GOARCH: "arm", GOARM: "7,hardfloat"}, {GOOS: "linux", GOARCH: "arm64"}, {GOOS: "linux", GOARCH: "mips", GOMIPS: "hardfloat"}, {GOOS: "linux", GOARCH: "mipsle", GOMIPS: "hardfloat"}, diff --git a/builder/library.go b/builder/library.go index b8d1834147..c79f7ce3f3 100644 --- a/builder/library.go +++ b/builder/library.go @@ -173,6 +173,13 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ // Use softfloat instead of floating point instructions. This is // supported on many architectures. args = append(args, "-msoft-float") + } else { + if strings.HasPrefix(target, "armv5") { + // On ARMv5 we need to explicitly enable hardware floating point + // instructions: Clang appears to assume the hardware doesn't have a + // FPU otherwise. + args = append(args, "-mfpu=vfpv2") + } } var once sync.Once diff --git a/compileopts/target.go b/compileopts/target.go index b168da3c20..3368e20c4e 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -267,18 +267,60 @@ func defaultTarget(options *Options) (*TargetSpec, error) { case "arm": spec.CPU = "generic" spec.CFlags = append(spec.CFlags, "-fno-unwind-tables", "-fno-asynchronous-unwind-tables") - switch options.GOARM { + subarch := strings.Split(options.GOARM, ",") + if len(subarch) > 2 { + return nil, fmt.Errorf("invalid GOARM=%s, must be of form ,[hardfloat|softfloat]", options.GOARM) + } + archLevel := subarch[0] + var fpu string + if len(subarch) >= 2 { + fpu = subarch[1] + } else { + // Pick the default fpu value: softfloat for armv5 and hardfloat + // above that. + if archLevel == "5" { + fpu = "softfloat" + } else { + fpu = "hardfloat" + } + } + switch fpu { + case "softfloat": + spec.CFlags = append(spec.CFlags, "-msoft-float") + spec.SoftFloat = true + case "hardfloat": + // Hardware floating point support is the default everywhere except + // on ARMv5 where it needs to be enabled explicitly. + if archLevel == "5" { + spec.CFlags = append(spec.CFlags, "-mfpu=vfpv2") + } + default: + return nil, fmt.Errorf("invalid extension GOARM=%s, must be softfloat or hardfloat", options.GOARM) + } + switch archLevel { case "5": llvmarch = "armv5" - spec.Features = "+armv5t,+strict-align,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + if spec.SoftFloat { + spec.Features = "+armv5t,+soft-float,+strict-align,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + } else { + spec.Features = "+armv5t,+fp64,+strict-align,+vfp2,+vfp2sp,-aes,-d32,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-neon,-sha2,-thumb-mode,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + } case "6": llvmarch = "armv6" - spec.Features = "+armv6,+dsp,+fp64,+strict-align,+vfp2,+vfp2sp,-aes,-d32,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-neon,-sha2,-thumb-mode,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + if spec.SoftFloat { + spec.Features = "+armv6,+dsp,+soft-float,+strict-align,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + } else { + spec.Features = "+armv6,+dsp,+fp64,+strict-align,+vfp2,+vfp2sp,-aes,-d32,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-neon,-sha2,-thumb-mode,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + } case "7": llvmarch = "armv7" - spec.Features = "+armv7-a,+d32,+dsp,+fp64,+neon,+vfp2,+vfp2sp,+vfp3,+vfp3d16,+vfp3d16sp,+vfp3sp,-aes,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-sha2,-thumb-mode,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + if spec.SoftFloat { + spec.Features = "+armv7-a,+dsp,+soft-float,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + } else { + spec.Features = "+armv7-a,+d32,+dsp,+fp64,+neon,+vfp2,+vfp2sp,+vfp3,+vfp3d16,+vfp3d16sp,+vfp3sp,-aes,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-sha2,-thumb-mode,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + } default: - return nil, fmt.Errorf("invalid GOARM=%s, must be 5, 6, or 7", options.GOARM) + return nil, fmt.Errorf("invalid GOARM=%s, must be of form ,[hardfloat|softfloat] where num is 5, 6, or 7", options.GOARM) } case "arm64": spec.CPU = "generic" From f1516ad3eefd79b951b52e33637ee67bfd80efff Mon Sep 17 00:00:00 2001 From: Unrud Date: Mon, 5 Aug 2024 19:23:13 +0200 Subject: [PATCH 139/444] machine/usb/descriptor: Fix encoding of values The limit for positive values is incorrect and leads to an overflow (e.g. 0xFF and 0xFFFF become -1) --- src/machine/usb/descriptor/hid.go | 7 ++- src/machine/usb/descriptor/hidreport.go | 78 ++++++++++--------------- 2 files changed, 34 insertions(+), 51 deletions(-) diff --git a/src/machine/usb/descriptor/hid.go b/src/machine/usb/descriptor/hid.go index d8f86a41e7..ea65f1ed97 100644 --- a/src/machine/usb/descriptor/hid.go +++ b/src/machine/usb/descriptor/hid.go @@ -103,7 +103,7 @@ var classHID = [ClassHIDTypeLen]byte{ 0x00, // CountryCode 0x01, // NumDescriptors 0x22, // ClassType - 0x90, // ClassLength L + 0x91, // ClassLength L 0x00, // ClassLength H } @@ -131,7 +131,7 @@ var CDCHID = Descriptor{ EndpointEP5OUT.Bytes(), }), HID: map[uint16][]byte{ - 2: Append([][]byte{ + 2: Append([][]byte{ // Update ClassLength in classHID whenever the array length is modified! HIDUsagePageGenericDesktop, HIDUsageDesktopKeyboard, HIDCollectionApplication, @@ -210,6 +210,7 @@ var CDCHID = Descriptor{ HIDReportSize(16), HIDReportCount(1), HIDInputDataAryAbs, - HIDCollectionEnd}), + HIDCollectionEnd, + }), }, } diff --git a/src/machine/usb/descriptor/hidreport.go b/src/machine/usb/descriptor/hidreport.go index 5819f6ad69..c6d46eec54 100644 --- a/src/machine/usb/descriptor/hidreport.go +++ b/src/machine/usb/descriptor/hidreport.go @@ -1,5 +1,7 @@ package descriptor +import "math" + const ( hidUsagePage = 0x05 hidUsage = 0x09 @@ -130,6 +132,28 @@ var ( HIDOutputConstVarAbs = []byte{hidOutput, 0x03} ) +func hidShortItem(tag byte, value uint32) []byte { + switch { + case value <= math.MaxUint8: + return []byte{tag | hidSizeValue1, byte(value)} + case value <= math.MaxUint16: + return []byte{tag | hidSizeValue2, byte(value), byte(value >> 8)} + default: + return []byte{tag | hidSizeValue4, byte(value), byte(value >> 8), byte(value >> 16), byte(value >> 24)} + } +} + +func hidShortItemSigned(tag byte, value int32) []byte { + switch { + case math.MinInt8 <= value && value <= math.MaxInt8: + return []byte{tag | hidSizeValue1, byte(value)} + case math.MinInt16 <= value && value <= math.MaxInt16: + return []byte{tag | hidSizeValue2, byte(value), byte(value >> 8)} + default: + return []byte{tag | hidSizeValue4, byte(value), byte(value >> 8), byte(value >> 16), byte(value >> 24)} + } +} + func HIDReportSize(size int) []byte { return []byte{hidReportSize, byte(size)} } @@ -143,69 +167,27 @@ func HIDReportID(id int) []byte { } func HIDLogicalMinimum(min int) []byte { - switch { - case min < -32767 || 65535 < min: - return []byte{hidLogicalMinimum + hidSizeValue4, uint8(min), uint8(min >> 8), uint8(min >> 16), uint8(min >> 24)} - case min < -127 || 255 < min: - return []byte{hidLogicalMinimum + hidSizeValue2, uint8(min), uint8(min >> 8)} - default: - return []byte{hidLogicalMinimum + hidSizeValue1, byte(min)} - } + return hidShortItemSigned(hidLogicalMinimum, int32(min)) } func HIDLogicalMaximum(max int) []byte { - switch { - case max < -32767 || 65535 < max: - return []byte{hidLogicalMaximum + hidSizeValue4, uint8(max), uint8(max >> 8), uint8(max >> 16), uint8(max >> 24)} - case max < -127 || 255 < max: - return []byte{hidLogicalMaximum + hidSizeValue2, uint8(max), uint8(max >> 8)} - default: - return []byte{hidLogicalMaximum + hidSizeValue1, byte(max)} - } + return hidShortItemSigned(hidLogicalMaximum, int32(max)) } func HIDUsageMinimum(min int) []byte { - switch { - case min < -32767 || 65535 < min: - return []byte{hidUsageMinimum + hidSizeValue4, uint8(min), uint8(min >> 8), uint8(min >> 16), uint8(min >> 24)} - case min < -127 || 255 < min: - return []byte{hidUsageMinimum + hidSizeValue2, uint8(min), uint8(min >> 8)} - default: - return []byte{hidUsageMinimum + hidSizeValue1, byte(min)} - } + return hidShortItem(hidUsageMinimum, uint32(min)) } func HIDUsageMaximum(max int) []byte { - switch { - case max < -32767 || 65535 < max: - return []byte{hidUsageMaximum + hidSizeValue4, uint8(max), uint8(max >> 8), uint8(max >> 16), uint8(max >> 24)} - case max < -127 || 255 < max: - return []byte{hidUsageMaximum + hidSizeValue2, uint8(max), uint8(max >> 8)} - default: - return []byte{hidUsageMaximum + hidSizeValue1, byte(max)} - } + return hidShortItem(hidUsageMaximum, uint32(max)) } func HIDPhysicalMinimum(min int) []byte { - switch { - case min < -32767 || 65535 < min: - return []byte{hidPhysicalMinimum + hidSizeValue4, uint8(min), uint8(min >> 8), uint8(min >> 16), uint8(min >> 24)} - case min < -127 || 255 < min: - return []byte{hidPhysicalMinimum + hidSizeValue2, uint8(min), uint8(min >> 8)} - default: - return []byte{hidPhysicalMinimum + hidSizeValue1, byte(min)} - } + return hidShortItemSigned(hidPhysicalMinimum, int32(min)) } func HIDPhysicalMaximum(max int) []byte { - switch { - case max < -32767 || 65535 < max: - return []byte{hidPhysicalMaximum + hidSizeValue4, uint8(max), uint8(max >> 8), uint8(max >> 16), uint8(max >> 24)} - case max < -127 || 255 < max: - return []byte{hidPhysicalMaximum + hidSizeValue2, uint8(max), uint8(max >> 8)} - default: - return []byte{hidPhysicalMaximum + hidSizeValue1, byte(max)} - } + return hidShortItemSigned(hidPhysicalMaximum, int32(max)) } func HIDUnitExponent(exp int) []byte { From 9a2fab887431bf048e82d1a373bcd5647d1ba5e1 Mon Sep 17 00:00:00 2001 From: Unrud Date: Mon, 5 Aug 2024 19:23:14 +0200 Subject: [PATCH 140/444] machine/usb/descriptor: Add more HID... functions --- src/machine/usb/descriptor/hidreport.go | 209 +++++++++++++----------- 1 file changed, 115 insertions(+), 94 deletions(-) diff --git a/src/machine/usb/descriptor/hidreport.go b/src/machine/usb/descriptor/hidreport.go index c6d46eec54..625578f8f9 100644 --- a/src/machine/usb/descriptor/hidreport.go +++ b/src/machine/usb/descriptor/hidreport.go @@ -3,22 +3,22 @@ package descriptor import "math" const ( - hidUsagePage = 0x05 - hidUsage = 0x09 + hidUsagePage = 0x04 + hidUsage = 0x08 hidLogicalMinimum = 0x14 hidLogicalMaximum = 0x24 hidUsageMinimum = 0x18 hidUsageMaximum = 0x28 hidPhysicalMinimum = 0x34 hidPhysicalMaximum = 0x44 - hidUnitExponent = 0x55 - hidUnit = 0x65 - hidCollection = 0xa1 - hidInput = 0x81 - hidOutput = 0x91 - hidReportSize = 0x75 - hidReportCount = 0x95 - hidReportID = 0x85 + hidUnitExponent = 0x54 + hidUnit = 0x64 + hidCollection = 0xA0 + hidInput = 0x80 + hidOutput = 0x90 + hidReportSize = 0x74 + hidReportCount = 0x94 + hidReportID = 0x84 ) const ( @@ -29,107 +29,107 @@ const ( ) var ( - HIDUsagePageGenericDesktop = []byte{hidUsagePage, 0x01} - HIDUsagePageSimulationControls = []byte{hidUsagePage, 0x02} - HIDUsagePageVRControls = []byte{hidUsagePage, 0x03} - HIDUsagePageSportControls = []byte{hidUsagePage, 0x04} - HIDUsagePageGameControls = []byte{hidUsagePage, 0x05} - HIDUsagePageGenericControls = []byte{hidUsagePage, 0x06} - HIDUsagePageKeyboard = []byte{hidUsagePage, 0x07} - HIDUsagePageLED = []byte{hidUsagePage, 0x08} - HIDUsagePageButton = []byte{hidUsagePage, 0x09} - HIDUsagePageOrdinal = []byte{hidUsagePage, 0x0A} - HIDUsagePageTelephony = []byte{hidUsagePage, 0x0B} - HIDUsagePageConsumer = []byte{hidUsagePage, 0x0C} - HIDUsagePageDigitizers = []byte{hidUsagePage, 0x0D} - HIDUsagePageHaptics = []byte{hidUsagePage, 0x0E} - HIDUsagePagePhysicalInput = []byte{hidUsagePage, 0x0F} - HIDUsagePageUnicode = []byte{hidUsagePage, 0x10} - HIDUsagePageSoC = []byte{hidUsagePage, 0x11} - HIDUsagePageEyeHeadTrackers = []byte{hidUsagePage, 0x12} - HIDUsagePageAuxDisplay = []byte{hidUsagePage, 0x14} - HIDUsagePageSensors = []byte{hidUsagePage, 0x20} - HIDUsagePageMedicalInstrument = []byte{hidUsagePage, 0x40} - HIDUsagePageBrailleDisplay = []byte{hidUsagePage, 0x41} - HIDUsagePageLighting = []byte{hidUsagePage, 0x59} - HIDUsagePageMonitor = []byte{hidUsagePage, 0x80} - HIDUsagePageMonitorEnum = []byte{hidUsagePage, 0x81} - HIDUsagePageVESA = []byte{hidUsagePage, 0x82} - HIDUsagePagePower = []byte{hidUsagePage, 0x84} - HIDUsagePageBatterySystem = []byte{hidUsagePage, 0x85} - HIDUsagePageBarcodeScanner = []byte{hidUsagePage, 0x8C} - HIDUsagePageScales = []byte{hidUsagePage, 0x8D} - HIDUsagePageMagneticStripe = []byte{hidUsagePage, 0x8E} - HIDUsagePageCameraControl = []byte{hidUsagePage, 0x90} - HIDUsagePageArcade = []byte{hidUsagePage, 0x91} - HIDUsagePageGaming = []byte{hidUsagePage, 0x92} + HIDUsagePageGenericDesktop = HIDUsagePage(0x01) + HIDUsagePageSimulationControls = HIDUsagePage(0x02) + HIDUsagePageVRControls = HIDUsagePage(0x03) + HIDUsagePageSportControls = HIDUsagePage(0x04) + HIDUsagePageGameControls = HIDUsagePage(0x05) + HIDUsagePageGenericControls = HIDUsagePage(0x06) + HIDUsagePageKeyboard = HIDUsagePage(0x07) + HIDUsagePageLED = HIDUsagePage(0x08) + HIDUsagePageButton = HIDUsagePage(0x09) + HIDUsagePageOrdinal = HIDUsagePage(0x0A) + HIDUsagePageTelephony = HIDUsagePage(0x0B) + HIDUsagePageConsumer = HIDUsagePage(0x0C) + HIDUsagePageDigitizers = HIDUsagePage(0x0D) + HIDUsagePageHaptics = HIDUsagePage(0x0E) + HIDUsagePagePhysicalInput = HIDUsagePage(0x0F) + HIDUsagePageUnicode = HIDUsagePage(0x10) + HIDUsagePageSoC = HIDUsagePage(0x11) + HIDUsagePageEyeHeadTrackers = HIDUsagePage(0x12) + HIDUsagePageAuxDisplay = HIDUsagePage(0x14) + HIDUsagePageSensors = HIDUsagePage(0x20) + HIDUsagePageMedicalInstrument = HIDUsagePage(0x40) + HIDUsagePageBrailleDisplay = HIDUsagePage(0x41) + HIDUsagePageLighting = HIDUsagePage(0x59) + HIDUsagePageMonitor = HIDUsagePage(0x80) + HIDUsagePageMonitorEnum = HIDUsagePage(0x81) + HIDUsagePageVESA = HIDUsagePage(0x82) + HIDUsagePagePower = HIDUsagePage(0x84) + HIDUsagePageBatterySystem = HIDUsagePage(0x85) + HIDUsagePageBarcodeScanner = HIDUsagePage(0x8C) + HIDUsagePageScales = HIDUsagePage(0x8D) + HIDUsagePageMagneticStripe = HIDUsagePage(0x8E) + HIDUsagePageCameraControl = HIDUsagePage(0x90) + HIDUsagePageArcade = HIDUsagePage(0x91) + HIDUsagePageGaming = HIDUsagePage(0x92) ) var ( - HIDUsageDesktopPointer = []byte{hidUsage, 0x01} - HIDUsageDesktopMouse = []byte{hidUsage, 0x02} - HIDUsageDesktopJoystick = []byte{hidUsage, 0x04} - HIDUsageDesktopGamepad = []byte{hidUsage, 0x05} - HIDUsageDesktopKeyboard = []byte{hidUsage, 0x06} - HIDUsageDesktopKeypad = []byte{hidUsage, 0x07} - HIDUsageDesktopMultiaxis = []byte{hidUsage, 0x08} - HIDUsageDesktopTablet = []byte{hidUsage, 0x09} - HIDUsageDesktopWaterCooling = []byte{hidUsage, 0x0A} - HIDUsageDesktopChassis = []byte{hidUsage, 0x0B} - HIDUsageDesktopWireless = []byte{hidUsage, 0x0C} - HIDUsageDesktopPortable = []byte{hidUsage, 0x0D} - HIDUsageDesktopSystemMultiaxis = []byte{hidUsage, 0x0E} - HIDUsageDesktopSpatial = []byte{hidUsage, 0x0F} - HIDUsageDesktopAssistive = []byte{hidUsage, 0x10} - HIDUsageDesktopDock = []byte{hidUsage, 0x11} - HIDUsageDesktopDockable = []byte{hidUsage, 0x12} - HIDUsageDesktopCallState = []byte{hidUsage, 0x13} - HIDUsageDesktopX = []byte{hidUsage, 0x30} - HIDUsageDesktopY = []byte{hidUsage, 0x31} - HIDUsageDesktopZ = []byte{hidUsage, 0x32} - HIDUsageDesktopRx = []byte{hidUsage, 0x33} - HIDUsageDesktopRy = []byte{hidUsage, 0x34} - HIDUsageDesktopRz = []byte{hidUsage, 0x35} - HIDUsageDesktopSlider = []byte{hidUsage, 0x36} - HIDUsageDesktopDial = []byte{hidUsage, 0x37} - HIDUsageDesktopWheel = []byte{hidUsage, 0x38} - HIDUsageDesktopHatSwitch = []byte{hidUsage, 0x39} - HIDUsageDesktopCountedBuffer = []byte{hidUsage, 0x3A} + HIDUsageDesktopPointer = HIDUsage(0x01) + HIDUsageDesktopMouse = HIDUsage(0x02) + HIDUsageDesktopJoystick = HIDUsage(0x04) + HIDUsageDesktopGamepad = HIDUsage(0x05) + HIDUsageDesktopKeyboard = HIDUsage(0x06) + HIDUsageDesktopKeypad = HIDUsage(0x07) + HIDUsageDesktopMultiaxis = HIDUsage(0x08) + HIDUsageDesktopTablet = HIDUsage(0x09) + HIDUsageDesktopWaterCooling = HIDUsage(0x0A) + HIDUsageDesktopChassis = HIDUsage(0x0B) + HIDUsageDesktopWireless = HIDUsage(0x0C) + HIDUsageDesktopPortable = HIDUsage(0x0D) + HIDUsageDesktopSystemMultiaxis = HIDUsage(0x0E) + HIDUsageDesktopSpatial = HIDUsage(0x0F) + HIDUsageDesktopAssistive = HIDUsage(0x10) + HIDUsageDesktopDock = HIDUsage(0x11) + HIDUsageDesktopDockable = HIDUsage(0x12) + HIDUsageDesktopCallState = HIDUsage(0x13) + HIDUsageDesktopX = HIDUsage(0x30) + HIDUsageDesktopY = HIDUsage(0x31) + HIDUsageDesktopZ = HIDUsage(0x32) + HIDUsageDesktopRx = HIDUsage(0x33) + HIDUsageDesktopRy = HIDUsage(0x34) + HIDUsageDesktopRz = HIDUsage(0x35) + HIDUsageDesktopSlider = HIDUsage(0x36) + HIDUsageDesktopDial = HIDUsage(0x37) + HIDUsageDesktopWheel = HIDUsage(0x38) + HIDUsageDesktopHatSwitch = HIDUsage(0x39) + HIDUsageDesktopCountedBuffer = HIDUsage(0x3A) ) var ( - HIDUsageConsumerControl = []byte{hidUsage, 0x01} - HIDUsageConsumerNumericKeypad = []byte{hidUsage, 0x02} - HIDUsageConsumerProgrammableButtons = []byte{hidUsage, 0x03} - HIDUsageConsumerMicrophone = []byte{hidUsage, 0x04} - HIDUsageConsumerHeadphone = []byte{hidUsage, 0x05} - HIDUsageConsumerGraphicEqualizer = []byte{hidUsage, 0x06} + HIDUsageConsumerControl = HIDUsage(0x01) + HIDUsageConsumerNumericKeypad = HIDUsage(0x02) + HIDUsageConsumerProgrammableButtons = HIDUsage(0x03) + HIDUsageConsumerMicrophone = HIDUsage(0x04) + HIDUsageConsumerHeadphone = HIDUsage(0x05) + HIDUsageConsumerGraphicEqualizer = HIDUsage(0x06) ) var ( - HIDCollectionPhysical = []byte{hidCollection, 0x00} - HIDCollectionApplication = []byte{hidCollection, 0x01} - HIDCollectionEnd = []byte{0xc0} + HIDCollectionPhysical = HIDCollection(0x00) + HIDCollectionApplication = HIDCollection(0x01) + HIDCollectionEnd = []byte{0xC0} ) var ( // Input (Data,Ary,Abs), Key arrays (6 bytes) - HIDInputDataAryAbs = []byte{hidInput, 0x00} + HIDInputDataAryAbs = HIDInput(0x00) // Input (Data, Variable, Absolute), Modifier byte - HIDInputDataVarAbs = []byte{hidInput, 0x02} + HIDInputDataVarAbs = HIDInput(0x02) // Input (Const,Var,Abs), Modifier byte - HIDInputConstVarAbs = []byte{hidInput, 0x03} + HIDInputConstVarAbs = HIDInput(0x03) // Input (Data, Variable, Relative), 2 position bytes (X & Y) - HIDInputDataVarRel = []byte{hidInput, 0x06} + HIDInputDataVarRel = HIDInput(0x06) // Output (Data, Variable, Absolute), Modifier byte - HIDOutputDataVarAbs = []byte{hidOutput, 0x02} + HIDOutputDataVarAbs = HIDOutput(0x02) // Output (Const, Variable, Absolute), Modifier byte - HIDOutputConstVarAbs = []byte{hidOutput, 0x03} + HIDOutputConstVarAbs = HIDOutput(0x03) ) func hidShortItem(tag byte, value uint32) []byte { @@ -155,15 +155,15 @@ func hidShortItemSigned(tag byte, value int32) []byte { } func HIDReportSize(size int) []byte { - return []byte{hidReportSize, byte(size)} + return hidShortItem(hidReportSize, uint32(size)) } func HIDReportCount(count int) []byte { - return []byte{hidReportCount, byte(count)} + return hidShortItem(hidReportCount, uint32(count)) } func HIDReportID(id int) []byte { - return []byte{hidReportID, byte(id)} + return hidShortItem(hidReportID, uint32(id)) } func HIDLogicalMinimum(min int) []byte { @@ -191,9 +191,30 @@ func HIDPhysicalMaximum(max int) []byte { } func HIDUnitExponent(exp int) []byte { - return []byte{hidUnitExponent, byte(exp)} + // 4 Bit two's complement + return hidShortItem(hidUnitExponent, uint32(exp&0xF)) } -func HIDUnit(unit int) []byte { - return []byte{hidUnit, byte(unit)} +func HIDUnit(unit uint32) []byte { + return hidShortItem(hidUnit, unit) +} + +func HIDUsagePage(id uint16) []byte { + return hidShortItem(hidUsagePage, uint32(id)) +} + +func HIDUsage(id uint32) []byte { + return hidShortItem(hidUsage, id) +} + +func HIDCollection(id uint32) []byte { + return hidShortItem(hidCollection, id) +} + +func HIDInput(flags uint32) []byte { + return hidShortItem(hidInput, flags) +} + +func HIDOutput(flags uint32) []byte { + return hidShortItem(hidOutput, flags) } From c38bf27091b97af51c185e16538d73ba8b7a59fe Mon Sep 17 00:00:00 2001 From: Unrud Date: Mon, 5 Aug 2024 19:23:14 +0200 Subject: [PATCH 141/444] machine/usb/hid/joystick: Allow more hat switches --- src/machine/usb/hid/joystick/state.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/machine/usb/hid/joystick/state.go b/src/machine/usb/hid/joystick/state.go index 7cb47a77c7..08265ab128 100644 --- a/src/machine/usb/hid/joystick/state.go +++ b/src/machine/usb/hid/joystick/state.go @@ -67,6 +67,10 @@ func (c Definitions) Descriptor() []byte { func (c Definitions) NewState() State { bufSize := 1 + hatSwitches := make([]HatDirection, c.HatSwitchCnt) + for i := range hatSwitches { + hatSwitches[i] = HatCenter + } axises := make([]*AxisValue, 0, len(c.AxisDefs)) for _, v := range c.AxisDefs { @@ -77,16 +81,14 @@ func (c Definitions) NewState() State { } btnSize := (c.ButtonCnt + 7) / 8 bufSize += btnSize - if c.HatSwitchCnt > 0 { - bufSize++ - } + bufSize += (len(hatSwitches) + 1) / 2 bufSize += len(axises) * 2 initBuf := make([]byte, bufSize) initBuf[0] = c.ReportID return State{ buf: initBuf, Buttons: make([]byte, btnSize), - HatSwitches: make([]HatDirection, c.HatSwitchCnt), + HatSwitches: hatSwitches, Axises: axises, } } @@ -103,7 +105,11 @@ func (s State) MarshalBinary() ([]byte, error) { s.buf = append(s.buf, s.Buttons...) if len(s.HatSwitches) > 0 { hat := byte(0) - for _, v := range s.HatSwitches { + for i, v := range s.HatSwitches { + if i != 0 && i%2 == 0 { + s.buf = append(s.buf, hat) + hat = 0 + } hat <<= 4 hat |= byte(v & 0xf) } From 7fc64a27b4a0b5819746d5f58746ae83181670e4 Mon Sep 17 00:00:00 2001 From: Unrud Date: Mon, 5 Aug 2024 19:23:15 +0200 Subject: [PATCH 142/444] machine/usb/descriptor: Drop second joystick hat --- src/machine/usb/descriptor/joystick.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/machine/usb/descriptor/joystick.go b/src/machine/usb/descriptor/joystick.go index 03bde25904..0fe67a8172 100644 --- a/src/machine/usb/descriptor/joystick.go +++ b/src/machine/usb/descriptor/joystick.go @@ -95,14 +95,9 @@ var JoystickDefaultHIDReport = Append([][]byte{ HIDReportSize(4), HIDInputDataVarAbs, HIDUsageDesktopHatSwitch, - HIDLogicalMinimum(0), - HIDLogicalMaximum(7), - HIDPhysicalMinimum(0), - HIDPhysicalMaximum(315), - HIDUnit(0x14), HIDReportCount(1), HIDReportSize(4), - HIDInputDataVarAbs, + HIDInputConstVarAbs, HIDUsageDesktopPointer, HIDLogicalMinimum(-32767), HIDLogicalMaximum(32767), From 815784bd9692afe9ac5d58a37a282757de734d9a Mon Sep 17 00:00:00 2001 From: Unrud Date: Mon, 5 Aug 2024 19:23:15 +0200 Subject: [PATCH 143/444] machine/usb/descriptor: Reset joystick physical --- src/machine/usb/descriptor/joystick.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/machine/usb/descriptor/joystick.go b/src/machine/usb/descriptor/joystick.go index 0fe67a8172..65756e0d63 100644 --- a/src/machine/usb/descriptor/joystick.go +++ b/src/machine/usb/descriptor/joystick.go @@ -101,6 +101,9 @@ var JoystickDefaultHIDReport = Append([][]byte{ HIDUsageDesktopPointer, HIDLogicalMinimum(-32767), HIDLogicalMaximum(32767), + HIDPhysicalMinimum(0), + HIDPhysicalMaximum(0), + HIDUnit(0), HIDReportCount(6), HIDReportSize(16), HIDCollectionPhysical, From 194396d71570318eb581245c49778341474d8ef6 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 12 Aug 2024 18:45:22 +0200 Subject: [PATCH 144/444] unix: print a message when a fatal signal happens Print a message for SIGBUS, SIGSEGV, and SIGILL when they happen. These signals are always fatal, but it's very useful to know which of them happened. Also, it prints the location in the binary which can then be parsed by `tinygo run` (see https://github.com/tinygo-org/tinygo/pull/4383). While this does add some extra binary size, it's for Linux and MacOS (systems that typically have plenty of RAM/storage) and could be very useful when debugging some low-level crash such as a runtime bug. --- compileopts/target.go | 4 +++ lib/macos-minimal-sdk | 2 +- src/runtime/arch_386.go | 7 ++++- src/runtime/arch_amd64.go | 7 ++++- src/runtime/arch_arm.go | 7 ++++- src/runtime/arch_arm64.go | 7 ++++- src/runtime/arch_mips.go | 7 ++++- src/runtime/arch_mipsle.go | 7 ++++- src/runtime/os_darwin.go | 8 ++++++ src/runtime/os_linux.go | 6 ++++ src/runtime/runtime_unix.c | 56 +++++++++++++++++++++++++++++++++++++ src/runtime/runtime_unix.go | 51 +++++++++++++++++++++++++++++++++ 12 files changed, 162 insertions(+), 7 deletions(-) create mode 100644 src/runtime/runtime_unix.c diff --git a/compileopts/target.go b/compileopts/target.go index 3368e20c4e..501d99f119 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -388,6 +388,8 @@ func defaultTarget(options *Options) (*TargetSpec, error) { "-arch", llvmarch, "-platform_version", "macos", platformVersion, platformVersion, ) + spec.ExtraFiles = append(spec.ExtraFiles, + "src/runtime/runtime_unix.c") case "linux": spec.Linker = "ld.lld" spec.RTLib = "compiler-rt" @@ -407,6 +409,8 @@ func defaultTarget(options *Options) (*TargetSpec, error) { // proper threading. spec.CFlags = append(spec.CFlags, "-mno-outline-atomics") } + spec.ExtraFiles = append(spec.ExtraFiles, + "src/runtime/runtime_unix.c") case "windows": spec.Linker = "ld.lld" spec.Libc = "mingw-w64" diff --git a/lib/macos-minimal-sdk b/lib/macos-minimal-sdk index ebb736fda2..91ac2eabd8 160000 --- a/lib/macos-minimal-sdk +++ b/lib/macos-minimal-sdk @@ -1 +1 @@ -Subproject commit ebb736fda2bec7cea38dcda807518b835a539525 +Subproject commit 91ac2eabd80f10d95cb4255c78999d9d2c45a3be diff --git a/src/runtime/arch_386.go b/src/runtime/arch_386.go index 4e9cce72ba..90ec8e8baf 100644 --- a/src/runtime/arch_386.go +++ b/src/runtime/arch_386.go @@ -9,7 +9,12 @@ const deferExtraRegs = 0 const callInstSize = 5 // "call someFunction" is 5 bytes -const linux_MAP_ANONYMOUS = 0x20 +const ( + linux_MAP_ANONYMOUS = 0x20 + linux_SIGBUS = 7 + linux_SIGILL = 4 + linux_SIGSEGV = 11 +) // Align on word boundary. func align(ptr uintptr) uintptr { diff --git a/src/runtime/arch_amd64.go b/src/runtime/arch_amd64.go index 3bb03e3c71..436d6e3849 100644 --- a/src/runtime/arch_amd64.go +++ b/src/runtime/arch_amd64.go @@ -9,7 +9,12 @@ const deferExtraRegs = 0 const callInstSize = 5 // "call someFunction" is 5 bytes -const linux_MAP_ANONYMOUS = 0x20 +const ( + linux_MAP_ANONYMOUS = 0x20 + linux_SIGBUS = 7 + linux_SIGILL = 4 + linux_SIGSEGV = 11 +) // Align a pointer. // Note that some amd64 instructions (like movaps) expect 16-byte aligned diff --git a/src/runtime/arch_arm.go b/src/runtime/arch_arm.go index e28e854102..ea6b540d2a 100644 --- a/src/runtime/arch_arm.go +++ b/src/runtime/arch_arm.go @@ -11,7 +11,12 @@ const deferExtraRegs = 0 const callInstSize = 4 // "bl someFunction" is 4 bytes -const linux_MAP_ANONYMOUS = 0x20 +const ( + linux_MAP_ANONYMOUS = 0x20 + linux_SIGBUS = 7 + linux_SIGILL = 4 + linux_SIGSEGV = 11 +) // Align on the maximum alignment for this platform (double). func align(ptr uintptr) uintptr { diff --git a/src/runtime/arch_arm64.go b/src/runtime/arch_arm64.go index 4e798e36b1..6d3c856cf6 100644 --- a/src/runtime/arch_arm64.go +++ b/src/runtime/arch_arm64.go @@ -9,7 +9,12 @@ const deferExtraRegs = 0 const callInstSize = 4 // "bl someFunction" is 4 bytes -const linux_MAP_ANONYMOUS = 0x20 +const ( + linux_MAP_ANONYMOUS = 0x20 + linux_SIGBUS = 7 + linux_SIGILL = 4 + linux_SIGSEGV = 11 +) // Align on word boundary. func align(ptr uintptr) uintptr { diff --git a/src/runtime/arch_mips.go b/src/runtime/arch_mips.go index bfaf890ae5..5a7d05c898 100644 --- a/src/runtime/arch_mips.go +++ b/src/runtime/arch_mips.go @@ -9,7 +9,12 @@ const deferExtraRegs = 0 const callInstSize = 8 // "jal someFunc" is 4 bytes, plus a MIPS delay slot -const linux_MAP_ANONYMOUS = 0x800 +const ( + linux_MAP_ANONYMOUS = 0x800 + linux_SIGBUS = 10 + linux_SIGILL = 4 + linux_SIGSEGV = 11 +) // It appears that MIPS has a maximum alignment of 8 bytes. func align(ptr uintptr) uintptr { diff --git a/src/runtime/arch_mipsle.go b/src/runtime/arch_mipsle.go index b6bf7d5169..498cf862b7 100644 --- a/src/runtime/arch_mipsle.go +++ b/src/runtime/arch_mipsle.go @@ -9,7 +9,12 @@ const deferExtraRegs = 0 const callInstSize = 8 // "jal someFunc" is 4 bytes, plus a MIPS delay slot -const linux_MAP_ANONYMOUS = 0x800 +const ( + linux_MAP_ANONYMOUS = 0x800 + linux_SIGBUS = 10 + linux_SIGILL = 4 + linux_SIGSEGV = 11 +) // It appears that MIPS has a maximum alignment of 8 bytes. func align(ptr uintptr) uintptr { diff --git a/src/runtime/os_darwin.go b/src/runtime/os_darwin.go index eeb192dda8..9255fb90f2 100644 --- a/src/runtime/os_darwin.go +++ b/src/runtime/os_darwin.go @@ -22,6 +22,14 @@ const ( clock_MONOTONIC_RAW = 4 ) +// Source: +// https://opensource.apple.com/source/xnu/xnu-7195.141.2/bsd/sys/signal.h.auto.html +const ( + sig_SIGBUS = 10 + sig_SIGILL = 4 + sig_SIGSEGV = 11 +) + // https://opensource.apple.com/source/xnu/xnu-7195.141.2/EXTERNAL_HEADERS/mach-o/loader.h.auto.html type machHeader struct { magic uint32 diff --git a/src/runtime/os_linux.go b/src/runtime/os_linux.go index 403f00246a..df5870a2df 100644 --- a/src/runtime/os_linux.go +++ b/src/runtime/os_linux.go @@ -23,6 +23,12 @@ const ( clock_MONOTONIC_RAW = 4 ) +const ( + sig_SIGBUS = linux_SIGBUS + sig_SIGILL = linux_SIGILL + sig_SIGSEGV = linux_SIGSEGV +) + // For the definition of the various header structs, see: // https://refspecs.linuxfoundation.org/elf/elf.pdf // Also useful: diff --git a/src/runtime/runtime_unix.c b/src/runtime/runtime_unix.c new file mode 100644 index 0000000000..79dd7ce915 --- /dev/null +++ b/src/runtime/runtime_unix.c @@ -0,0 +1,56 @@ +//go:build none + +// This file is included on Darwin and Linux (despite the //go:build line above). + +#define _GNU_SOURCE +#define _XOPEN_SOURCE +#include +#include +#include +#include +#include + +void tinygo_handle_fatal_signal(int sig, uintptr_t addr); + +static void signal_handler(int sig, siginfo_t *info, void *context) { + ucontext_t* uctx = context; + uintptr_t addr = 0; + #if __APPLE__ + #if __arm64__ + addr = uctx->uc_mcontext->__ss.__pc; + #elif __x86_64__ + addr = uctx->uc_mcontext->__ss.__rip; + #else + #error unknown architecture + #endif + #elif __linux__ + // Note: this can probably be simplified using the MC_PC macro in musl, + // but this works for now. + #if __arm__ + addr = uctx->uc_mcontext.arm_pc; + #elif __i386__ + addr = uctx->uc_mcontext.gregs[REG_EIP]; + #elif __x86_64__ + addr = uctx->uc_mcontext.gregs[REG_RIP]; + #else // aarch64, mips, maybe others + addr = uctx->uc_mcontext.pc; + #endif + #else + #error unknown platform + #endif + tinygo_handle_fatal_signal(sig, addr); +} + +void tinygo_register_fatal_signals(void) { + struct sigaction act = { 0 }; + // SA_SIGINFO: we want the 2 extra parameters + // SA_RESETHAND: only catch the signal once (the handler will re-raise the signal) + act.sa_flags = SA_SIGINFO | SA_RESETHAND; + act.sa_sigaction = &signal_handler; + + // Register the signal handler for common issues. There are more signals, + // which can be added if needed. + sigaction(SIGBUS, &act, NULL); + sigaction(SIGILL, &act, NULL); + sigaction(SIGSEGV, &act, NULL); +} diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index 8c5a42ff7c..ba5d5a5938 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -26,6 +26,9 @@ func abort() //export exit func exit(code int) +//export raise +func raise(sig int32) + //export clock_gettime func libc_clock_gettime(clk_id int32, ts *timespec) @@ -74,6 +77,10 @@ func main(argc int32, argv *unsafe.Pointer) int { main_argc = argc main_argv = argv + // Register some fatal signals, so that we can print slightly better error + // messages. + tinygo_register_fatal_signals() + // Obtain the initial stack pointer right before calling the run() function. // The run function has been moved to a separate (non-inlined) function so // that the correct stack pointer is read. @@ -119,6 +126,50 @@ func runMain() { run() } +//export tinygo_register_fatal_signals +func tinygo_register_fatal_signals() + +// Print fatal errors when they happen, including the instruction location. +// With the particular formatting below, `tinygo run` can extract the location +// where the signal happened and try to show the source location based on DWARF +// information. +// +//export tinygo_handle_fatal_signal +func tinygo_handle_fatal_signal(sig int32, addr uintptr) { + if panicStrategy() == panicStrategyTrap { + trap() + } + + // Print signal including the faulting instruction. + if addr != 0 { + printstring("panic: runtime error at ") + printptr(addr) + } else { + printstring("panic: runtime error") + } + printstring(": caught signal ") + switch sig { + case sig_SIGBUS: + println("SIGBUS") + case sig_SIGILL: + println("SIGILL") + case sig_SIGSEGV: + println("SIGSEGV") + default: + println(sig) + } + + // TODO: it might be interesting to also print the invalid address for + // SIGSEGV and SIGBUS. + + // Do *not* abort here, instead raise the same signal again. The signal is + // registered with SA_RESETHAND which means it executes only once. So when + // we raise the signal again below, the signal isn't handled specially but + // is handled in the default way (probably exiting the process, maybe with a + // core dump). + raise(sig) +} + //go:extern environ var environ *unsafe.Pointer From 5368dd22c5c8bb7c7ae0e854569dd2ebcca504bb Mon Sep 17 00:00:00 2001 From: Dan Kegel Date: Wed, 14 Aug 2024 13:52:51 -0700 Subject: [PATCH 145/444] misspell.csv: add new misspellings; also check in result of 'make spellfix'. --- CHANGELOG.md | 2 +- builder/build.go | 2 +- loader/loader.go | 2 +- misspell.csv | 16 ++++++++++++++-- src/machine/machine_k210.go | 4 ++-- src/runtime/panic.go | 2 +- src/runtime/runtime_avr.go | 2 +- src/runtime/runtime_mimxrt1062_clock.go | 2 +- src/runtime/runtime_nintendoswitch.go | 4 ++-- src/runtime/runtime_windows.go | 2 +- src/runtime/scheduler.go | 2 +- src/syscall/libc_wasip2.go | 2 +- 12 files changed, 27 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 956d50b6d8..c066df9fdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1896,7 +1896,7 @@ - allow packages like github.com/tinygo-org/tinygo/src/\* by aliasing it - remove `//go:volatile` support It has been replaced with the runtime/volatile package. - - allow poiners in map keys + - allow pointers in map keys - support non-constant syscall numbers - implement non-blocking selects - add support for the `-tags` flag diff --git a/builder/build.go b/builder/build.go index f728cde79d..780dc8df49 100644 --- a/builder/build.go +++ b/builder/build.go @@ -1259,7 +1259,7 @@ func determineStackSizes(mod llvm.Module, executable string) ([]string, map[stri } // Goroutines need to be started and finished and take up some stack space - // that way. This can be measured by measuing the stack size of + // that way. This can be measured by measuring the stack size of // tinygo_startTask. if numFuncs := len(functions["tinygo_startTask"]); numFuncs != 1 { return nil, nil, fmt.Errorf("expected exactly one definition of tinygo_startTask, got %d", numFuncs) diff --git a/loader/loader.go b/loader/loader.go index fe75e6c9b4..d10485707f 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -180,7 +180,7 @@ func Load(config *compileopts.Config, inputPkg string, typeChecker types.Config) if len(fields) >= 2 { // There is some file/line/column information. if n, err := strconv.Atoi(fields[len(fields)-2]); err == nil { - // Format: filename.go:line:colum + // Format: filename.go:line:column pos.Filename = strings.Join(fields[:len(fields)-2], ":") pos.Line = n pos.Column, _ = strconv.Atoi(fields[len(fields)-1]) diff --git a/misspell.csv b/misspell.csv index d12a28ed9c..82d4be429c 100644 --- a/misspell.csv +++ b/misspell.csv @@ -1,3 +1,4 @@ +acces,access acuire,acquire addess,address adust,adjust @@ -5,22 +6,33 @@ allcoate,allocate alloated,allocated archtecture,architecture arcive,archive +ardiuno,arduino beconfigured,be configured +calcluate,calculate +colum,column configration,configuration contants,constants cricital,critical +deffered,deferred evaulator,evaluator +evironment,environment freqency,frequency frquency,frequency implmented,implemented -interrput,interrupt interrut,interrupt +interupt,interrupt +measuing,measuring +orignal,original +overrided,overridden +poiners,pointers poitner,pointer -probbably,probably +recogized,recognized refection,reflection +requries,requires satisifying,satisfying simulataneously,simultaneously suggets,suggests +transmition,transmission undefied,undefined unecessary,unnecessary unsiged,unsigned diff --git a/src/machine/machine_k210.go b/src/machine/machine_k210.go index e0821670ba..28d5098e75 100644 --- a/src/machine/machine_k210.go +++ b/src/machine/machine_k210.go @@ -49,7 +49,7 @@ const ( var ( errUnsupportedSPIController = errors.New("SPI controller not supported. Use SPI0 or SPI1.") - errI2CTxAbort = errors.New("I2C transmition has been aborted.") + errI2CTxAbort = errors.New("I2C transmission has been aborted.") ) func (p Pin) setFPIOAIOPull(pull fpioaPullMode) { @@ -619,7 +619,7 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) error { dataLen -= fifoLen } - // Wait for transmition to complete. + // Wait for transmission to complete. for i2c.Bus.STATUS.HasBits(kendryte.I2C_STATUS_ACTIVITY) || !i2c.Bus.STATUS.HasBits(kendryte.I2C_STATUS_TFE) { } diff --git a/src/runtime/panic.go b/src/runtime/panic.go index 062305f15c..8cc62aeebe 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -155,7 +155,7 @@ func nilMapPanic() { runtimePanicAt(returnAddress(0), "assignment to entry in nil map") } -// Panic when trying to acces an array or slice out of bounds. +// Panic when trying to access an array or slice out of bounds. func lookupPanic() { runtimePanicAt(returnAddress(0), "index out of range") } diff --git a/src/runtime/runtime_avr.go b/src/runtime/runtime_avr.go index a2e11104e0..43d35d7b9c 100644 --- a/src/runtime/runtime_avr.go +++ b/src/runtime/runtime_avr.go @@ -104,7 +104,7 @@ func exit(code int) { func abort() { // Disable interrupts and go to sleep. - // This can never be awoken except for reset, and is recogized as termination by simavr. + // This can never be awoken except for reset, and is recognized as termination by simavr. avr.Asm("cli") for { avr.Asm("sleep") diff --git a/src/runtime/runtime_mimxrt1062_clock.go b/src/runtime/runtime_mimxrt1062_clock.go index 23d6c3cafd..707308b9fe 100644 --- a/src/runtime/runtime_mimxrt1062_clock.go +++ b/src/runtime/runtime_mimxrt1062_clock.go @@ -14,7 +14,7 @@ const ( // Note from Teensyduino (cores/teensy4/startup.c): // -// | ARM SysTick is used for most Ardiuno timing functions, delay(), millis(), +// | ARM SysTick is used for most Arduino timing functions, delay(), millis(), // | micros(). SysTick can run from either the ARM core clock, or from an // | "external" clock. NXP documents it as "24 MHz XTALOSC can be the external // | clock source of SYSTICK" (RT1052 ref manual, rev 1, page 411). However, diff --git a/src/runtime/runtime_nintendoswitch.go b/src/runtime/runtime_nintendoswitch.go index 10724e40f6..7d67a86264 100644 --- a/src/runtime/runtime_nintendoswitch.go +++ b/src/runtime/runtime_nintendoswitch.go @@ -172,9 +172,9 @@ func setupEnv() { func setupHeap() { if heapStart != 0 { if debugInit { - print("Heap already overrided by hblauncher") + print("Heap already overridden by hblauncher") } - // Already overrided + // Already overridden return } diff --git a/src/runtime/runtime_windows.go b/src/runtime/runtime_windows.go index 97485294c5..4b0b8f65b8 100644 --- a/src/runtime/runtime_windows.go +++ b/src/runtime/runtime_windows.go @@ -129,7 +129,7 @@ func nanosecondsToTicks(ns int64) timeUnit { } func sleepTicks(d timeUnit) { - // Calcluate milliseconds from ticks (which have a resolution of 100ns), + // Calculate milliseconds from ticks (which have a resolution of 100ns), // rounding up. milliseconds := int64(d+9_999) / 10_000 for milliseconds != 0 { diff --git a/src/runtime/scheduler.go b/src/runtime/scheduler.go index 7367eed353..618f6a94fc 100644 --- a/src/runtime/scheduler.go +++ b/src/runtime/scheduler.go @@ -67,7 +67,7 @@ func deadlock() { // Goexit terminates the currently running goroutine. No other goroutines are affected. // -// Unlike the main Go implementation, no deffered calls will be run. +// Unlike the main Go implementation, no deferred calls will be run. // //go:inline func Goexit() { diff --git a/src/syscall/libc_wasip2.go b/src/syscall/libc_wasip2.go index 3aadf877f3..7123f2db20 100644 --- a/src/syscall/libc_wasip2.go +++ b/src/syscall/libc_wasip2.go @@ -97,7 +97,7 @@ func read(fd int32, buf *byte, count uint) int { type wasiFile struct { d types.Descriptor - oflag int32 // orignal open flags: O_RDONLY, O_WRONLY, O_RDWR + oflag int32 // original open flags: O_RDONLY, O_WRONLY, O_RDWR offset int64 // current fd offset; updated with each read/write refs int } From c82ff057f578a08538b644f87ed3b3c7b95f0046 Mon Sep 17 00:00:00 2001 From: Dan Kegel Date: Wed, 14 Aug 2024 14:00:07 -0700 Subject: [PATCH 146/444] Fix 'numer of', since 'NUMER' by itself is valid abbrev for numerator. Oddly, misspell was not able to detect this one, even though in theory it allows detecting multiword typos, so the fix was done by hand. --- misspell.csv | 1 + src/runtime/runtime_nrf.go | 2 +- src/runtime/runtime_nrf52840.go | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/misspell.csv b/misspell.csv index 82d4be429c..965191c303 100644 --- a/misspell.csv +++ b/misspell.csv @@ -22,6 +22,7 @@ implmented,implemented interrut,interrupt interupt,interrupt measuing,measuring +numer of,number of orignal,original overrided,overridden poiners,pointers diff --git a/src/runtime/runtime_nrf.go b/src/runtime/runtime_nrf.go index 2f58c3518c..729c6bb20f 100644 --- a/src/runtime/runtime_nrf.go +++ b/src/runtime/runtime_nrf.go @@ -103,7 +103,7 @@ func nanosecondsToTicks(ns int64) timeUnit { return timeUnit(ns * 64 / 1953125) } -// Monotonically increasing numer of ticks since start. +// Monotonically increasing number of ticks since start. func ticks() timeUnit { // For some ways of capturing the time atomically, see this thread: // https://www.eevblog.com/forum/microcontrollers/correct-timing-by-timer-overflow-count/msg749617/#msg749617 diff --git a/src/runtime/runtime_nrf52840.go b/src/runtime/runtime_nrf52840.go index 47f9a8db1e..41c36fe5f0 100644 --- a/src/runtime/runtime_nrf52840.go +++ b/src/runtime/runtime_nrf52840.go @@ -106,7 +106,7 @@ func nanosecondsToTicks(ns int64) timeUnit { return timeUnit(ns * 64 / 1953125) } -// Monotonically increasing numer of ticks since start. +// Monotonically increasing number of ticks since start. func ticks() timeUnit { // For some ways of capturing the time atomically, see this thread: // https://www.eevblog.com/forum/microcontrollers/correct-timing-by-timer-overflow-count/msg749617/#msg749617 From 5d82a7eee6568effb8f6daff1a28c80b97a9d109 Mon Sep 17 00:00:00 2001 From: Dan Kegel Date: Wed, 14 Aug 2024 14:30:56 -0700 Subject: [PATCH 147/444] misspell.csv: add another misspelling. Fix by hand, since spellfix is too timid to fix in non-whitespace context? --- misspell.csv | 1 + transform/interrupt.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/misspell.csv b/misspell.csv index 965191c303..f6d58a8246 100644 --- a/misspell.csv +++ b/misspell.csv @@ -19,6 +19,7 @@ evironment,environment freqency,frequency frquency,frequency implmented,implemented +interrput,interrupt interrut,interrupt interupt,interrupt measuing,measuring diff --git a/transform/interrupt.go b/transform/interrupt.go index 043eebb84c..8c3ed4b510 100644 --- a/transform/interrupt.go +++ b/transform/interrupt.go @@ -137,7 +137,7 @@ func LowerInterrupts(mod llvm.Module) []error { user.ReplaceAllUsesWith(llvm.ConstInt(user.Type(), uint64(num), true)) } - // The runtime/interrput.handle struct can finally be removed. + // The runtime/interrupt.handle struct can finally be removed. // It would probably be eliminated anyway by a globaldce pass but it's // better to do it now to be sure. handler.EraseFromParentAsGlobal() From d3e67cf18c5ed61be3e43bb02c10327638c8b9ed Mon Sep 17 00:00:00 2001 From: Dan Kegel Date: Wed, 14 Aug 2024 14:36:16 -0700 Subject: [PATCH 148/444] make spellfix: fix top level files, too. Do manual fix in GNUmakefile, since spellchecking that is just too meta. --- GNUmakefile | 8 ++++---- main.go | 4 ++-- misspell.csv | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 68bb4430f5..bc0e1e48f8 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -358,11 +358,11 @@ endif # compress/flate appears to hang on wasi # crypto/hmac fails on wasi, it exits with a "slice out of range" panic # debug/plan9obj requires os.ReadAt, which is not yet supported on windows -# image requires recover(), which is not yet supported on wasi +# image requires recover(), which is not yet supported on wasi # io/ioutil requires os.ReadDir, which is not yet supported on windows or wasi # mime/quotedprintable requires syscall.Faccessat # strconv requires recover() which is not yet supported on wasi -# text/tabwriter requries recover(), which is not yet supported on wasi +# text/tabwriter requires recover(), which is not yet supported on wasi # text/template/parse requires recover(), which is not yet supported on wasi # testing/fstest requires os.ReadDir, which is not yet supported on windows or wasi @@ -963,11 +963,11 @@ lint: tools ## Lint source tree SPELLDIRSCMD=find . -depth 1 -type d | egrep -wv '.git|lib|llvm|src'; find src -depth 1 | egrep -wv 'device|internal|net|vendor'; find src/internal -depth 1 -type d | egrep -wv src/internal/wasi .PHONY: spell spell: tools ## Spellcheck source tree - misspell -error --dict misspell.csv -i 'ackward,devided,extint,rela' $$( $(SPELLDIRSCMD) ) *.md + misspell -error --dict misspell.csv -i 'ackward,devided,extint,rela' $$( $(SPELLDIRSCMD) ) *.go *.md .PHONY: spellfix spellfix: tools ## Same as spell, but fixes what it finds - misspell -w --dict misspell.csv -i 'ackward,devided,extint,rela' $$( $(SPELLDIRSCMD) ) *.md + misspell -w --dict misspell.csv -i 'ackward,devided,extint,rela' $$( $(SPELLDIRSCMD) ) *.go *.md # https://www.client9.com/self-documenting-makefiles/ .PHONY: help diff --git a/main.go b/main.go index cbe4b9dd48..b83ea85f88 100644 --- a/main.go +++ b/main.go @@ -808,7 +808,7 @@ func Run(pkgName string, options *compileopts.Options, cmdArgs []string) error { // buildAndRun builds and runs the given program, writing output to stdout and // errors to os.Stderr. It takes care of emulators (qemu, wasmtime, etc) and -// passes command line arguments and evironment variables in a way appropriate +// passes command line arguments and environment variables in a way appropriate // for the given emulator. func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, cmdArgs, environmentVars []string, timeout time.Duration, run func(cmd *exec.Cmd, result builder.BuildResult) error) (builder.BuildResult, error) { @@ -1631,7 +1631,7 @@ func main() { for i := range bufs { err := bufs[i].flush(os.Stdout, os.Stderr) if err != nil { - // There was an error writing to stdout or stderr, so we probbably cannot print this. + // There was an error writing to stdout or stderr, so we probably cannot print this. select { case fail <- struct{}{}: default: diff --git a/misspell.csv b/misspell.csv index f6d58a8246..9962ee11fd 100644 --- a/misspell.csv +++ b/misspell.csv @@ -28,6 +28,7 @@ orignal,original overrided,overridden poiners,pointers poitner,pointer +probbably,probably recogized,recognized refection,reflection requries,requires From bf8d6460da54f607ca07afa66be8d0f0503ef709 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 2 Jul 2024 17:20:07 +0200 Subject: [PATCH 149/444] os/user: use stdlib version of this package I tried implementing enough CGo support to get the native os/user package to work. But I hit a few bugs, probably in CGo itself. Then I realized I could just as well set the osusergo build tag to disable CGo for this specific case. This actually gets the os/user package to work correctly on Linux (I confirmed it returns my name/uid/homedir etc). On other systems, it probably just returns an error if it can't determine these kinds of things. But that's no worse than the current behavior which just doesn't do anything at all. --- GNUmakefile | 2 ++ compileopts/config.go | 1 + loader/goroot.go | 1 - src/os/user/user.go | 59 ------------------------------------------- 4 files changed, 3 insertions(+), 60 deletions(-) delete mode 100644 src/os/user/user.go diff --git a/GNUmakefile b/GNUmakefile index bc0e1e48f8..aaaf130d96 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -378,6 +378,7 @@ TEST_PACKAGES_LINUX := \ io/ioutil \ mime/quotedprintable \ net \ + os/user \ strconv \ testing/fstest \ text/tabwriter \ @@ -388,6 +389,7 @@ TEST_PACKAGES_DARWIN := $(TEST_PACKAGES_LINUX) TEST_PACKAGES_WINDOWS := \ compress/flate \ crypto/hmac \ + os/user \ strconv \ text/template/parse \ $(nil) diff --git a/compileopts/config.go b/compileopts/config.go index eca037503c..18d3c9e4d8 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -84,6 +84,7 @@ func (c *Config) BuildTags() []string { tags = append(tags, []string{ "tinygo", // that's the compiler "purego", // to get various crypto packages to work + "osusergo", // to get os/user to work "math_big_pure_go", // to get math/big to work "gc." + c.GC(), "scheduler." + c.Scheduler(), // used inside the runtime package "serial." + c.Serial()}...) // used inside the machine package diff --git a/loader/goroot.go b/loader/goroot.go index 8661bf67e0..739819bce8 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -247,7 +247,6 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { "net/": true, "net/http/": false, "os/": true, - "os/user/": false, "reflect/": false, "runtime/": false, "sync/": true, diff --git a/src/os/user/user.go b/src/os/user/user.go deleted file mode 100644 index ee63625f2b..0000000000 --- a/src/os/user/user.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package user - -import "errors" - -// User represents a user account. -type User struct { - // Uid is the user ID. - // On POSIX systems, this is a decimal number representing the uid. - // On Windows, this is a security identifier (SID) in a string format. - // On Plan 9, this is the contents of /dev/user. - Uid string - // Gid is the primary group ID. - // On POSIX systems, this is a decimal number representing the gid. - // On Windows, this is a SID in a string format. - // On Plan 9, this is the contents of /dev/user. - Gid string - // Username is the login name. - Username string - // Name is the user's real or display name. - // It might be blank. - // On POSIX systems, this is the first (or only) entry in the GECOS field - // list. - // On Windows, this is the user's display name. - // On Plan 9, this is the contents of /dev/user. - Name string - // HomeDir is the path to the user's home directory (if they have one). - HomeDir string -} - -// Current returns the current user. -// -// The first call will cache the current user information. -// Subsequent calls will return the cached value and will not reflect -// changes to the current user. -func Current() (*User, error) { - return nil, errors.New("user: Current not implemented") -} - -// Lookup always returns an error. -func Lookup(username string) (*User, error) { - return nil, errors.New("user: Lookup not implemented") -} - -// Group represents a grouping of users. -// -// On POSIX systems Gid contains a decimal number representing the group ID. -type Group struct { - Gid string // group ID - Name string // group name -} - -// LookupGroup always returns an error. -func LookupGroup(name string) (*Group, error) { - return nil, errors.New("user: LookupGroup not implemented") -} From 9932f2e1268d38b413ed69dc396df437810ce69b Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Wed, 14 Aug 2024 12:08:36 -0700 Subject: [PATCH 150/444] builder: os.SEEK_CUR -> io.SeekCurrent --- builder/ar.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/ar.go b/builder/ar.go index 807595b792..245f08ffb3 100644 --- a/builder/ar.go +++ b/builder/ar.go @@ -150,7 +150,7 @@ func makeArchive(arfile *os.File, objs []string) error { } // Keep track of the start of the symbol table. - symbolTableStart, err := arfile.Seek(0, os.SEEK_CUR) + symbolTableStart, err := arfile.Seek(0, io.SeekCurrent) if err != nil { return err } @@ -172,7 +172,7 @@ func makeArchive(arfile *os.File, objs []string) error { // Store the start index, for when we'll update the symbol table with // the correct file start indices. - offset, err := arfile.Seek(0, os.SEEK_CUR) + offset, err := arfile.Seek(0, io.SeekCurrent) if err != nil { return err } From 4d60d679d3859622bd689cd4a562bcde0e8e3865 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Wed, 14 Aug 2024 12:09:01 -0700 Subject: [PATCH 151/444] compiler: fixup Sprintf uses --- compiler/symbol.go | 2 +- main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/symbol.go b/compiler/symbol.go index 5ebdee1471..83bb4ccb26 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -353,7 +353,7 @@ func (c *compilerContext) checkWasmImport(f *ssa.Function, pragma string) { } if f.Blocks != nil { // Defined functions cannot be exported. - c.addError(f.Pos(), fmt.Sprintf("can only use //go:wasmimport on declarations")) + c.addError(f.Pos(), "can only use //go:wasmimport on declarations") return } if f.Signature.Results().Len() > 1 { diff --git a/main.go b/main.go index b83ea85f88..8f788efb86 100644 --- a/main.go +++ b/main.go @@ -958,7 +958,7 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c err = run(cmd, result) if err != nil { if ctx != nil && ctx.Err() == context.DeadlineExceeded { - stdout.Write([]byte(fmt.Sprintf("--- timeout of %s exceeded, terminating...\n", timeout))) + fmt.Fprintf(stdout, "--- timeout of %s exceeded, terminating...\n", timeout) err = ctx.Err() } return result, &commandError{"failed to run compiled binary", result.Binary, err} From b51cda9721b626afd8b3d16c8444deed912f941d Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 22 Jun 2024 17:25:46 +0200 Subject: [PATCH 152/444] sync/atomic: add And* and Or* compiler intrinsics needed for Go 1.23 --- compiler/atomic.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/compiler/atomic.go b/compiler/atomic.go index 4ba69c3960..496e3a2c9f 100644 --- a/compiler/atomic.go +++ b/compiler/atomic.go @@ -15,6 +15,16 @@ func (b *builder) createAtomicOp(name string) llvm.Value { oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpAdd, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true) // Return the new value, not the original value returned by atomicrmw. return b.CreateAdd(oldVal, val, "") + case "AndInt32", "AndInt64", "AndUint32", "AndUint64", "AndUintptr": + ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) + val := b.getValue(b.fn.Params[1], getPos(b.fn)) + oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpAnd, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true) + return oldVal + case "OrInt32", "OrInt64", "OrUint32", "OrUint64", "OrUintptr": + ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) + val := b.getValue(b.fn.Params[1], getPos(b.fn)) + oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpOr, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true) + return oldVal case "SwapInt32", "SwapInt64", "SwapUint32", "SwapUint64", "SwapUintptr", "SwapPointer": ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) val := b.getValue(b.fn.Params[1], getPos(b.fn)) From b8048112df62edfba34e7c22977845179a4b1c31 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 22 Jun 2024 17:54:47 +0200 Subject: [PATCH 153/444] internal/bytealg: add CompareString This function was added to Go many years ago, but is starting to be used in packages in Go 1.23. --- src/internal/bytealg/bytealg.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/internal/bytealg/bytealg.go b/src/internal/bytealg/bytealg.go index 4cf442e8e6..33ece2bbae 100644 --- a/src/internal/bytealg/bytealg.go +++ b/src/internal/bytealg/bytealg.go @@ -42,6 +42,31 @@ func Compare(a, b []byte) int { } } +// This function was copied from the Go 1.23 source tree (with runtime_cmpstring +// manually inlined). +func CompareString(a, b string) int { + l := len(a) + if len(b) < l { + l = len(b) + } + for i := 0; i < l; i++ { + c1, c2 := a[i], b[i] + if c1 < c2 { + return -1 + } + if c1 > c2 { + return +1 + } + } + if len(a) < len(b) { + return -1 + } + if len(a) > len(b) { + return +1 + } + return 0 +} + // Count the number of instances of a byte in a slice. func Count(b []byte, c byte) int { // Use a simple implementation, as there is no intrinsic that does this like we want. From 560fd0a558772d73ca9b7f26459447e709b515a6 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 23 Jun 2024 14:42:25 +0200 Subject: [PATCH 154/444] unique: implement custom version of unique package This version probably isn't as fast as the upstream version, but it is good enough for now. It also doesn't free unreferenced handles like the upstream version. --- GNUmakefile | 1 + loader/goroot.go | 1 + src/unique/handle.go | 74 ++++++++++++++++++++++++++++++++++++++ src/unique/handle_test.go | 76 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 152 insertions(+) create mode 100644 src/unique/handle.go create mode 100644 src/unique/handle_test.go diff --git a/GNUmakefile b/GNUmakefile index aaaf130d96..1fe2a5a6c2 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -344,6 +344,7 @@ TEST_PACKAGES_FAST = \ unicode \ unicode/utf16 \ unicode/utf8 \ + unique \ $(nil) # Assume this will go away before Go2, so only check minor version. diff --git a/loader/goroot.go b/loader/goroot.go index 739819bce8..7325db5b4b 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -251,6 +251,7 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { "runtime/": false, "sync/": true, "testing/": true, + "unique/": false, } if goMinor >= 19 { diff --git a/src/unique/handle.go b/src/unique/handle.go new file mode 100644 index 0000000000..67c925d2de --- /dev/null +++ b/src/unique/handle.go @@ -0,0 +1,74 @@ +// Package unique implements the upstream Go unique package for TinyGo. +// +// It is not a full implementation: while it should behave the same way, it +// doesn't free unreferenced uniqued objects. +package unique + +import ( + "sync" + "unsafe" +) + +var ( + // We use a two-level map because that way it's easier to store and retrieve + // values. + globalMap map[unsafe.Pointer]any // map value type is always map[T]Handle[T] + + globalMapMutex sync.Mutex +) + +// Unique handle for the given value. Comparing two handles is cheap. +type Handle[T comparable] struct { + value *T +} + +// Value returns a shallow copy of the T value that produced the Handle. +func (h Handle[T]) Value() T { + return *h.value +} + +// Make a new unqique handle for the given value. +func Make[T comparable](value T) Handle[T] { + // Very simple implementation of the unique package. This is much, *much* + // simpler than the upstream implementation. Sadly it's not possible to + // reuse the upstream version because it relies on implementation details of + // the upstream runtime. + // It probably isn't as efficient as the upstream version, but the first + // goal here is compatibility. If the performance is a problem, it can be + // optimized later. + + globalMapMutex.Lock() + + // The map isn't initialized at program startup (and after a test run), so + // create it. + if globalMap == nil { + globalMap = make(map[unsafe.Pointer]any) + } + + // Retrieve the type-specific map, creating it if not yet present. + typeptr, _ := decomposeInterface(value) + var typeSpecificMap map[T]Handle[T] + if typeSpecificMapValue, ok := globalMap[typeptr]; !ok { + typeSpecificMap = make(map[T]Handle[T]) + globalMap[typeptr] = typeSpecificMap + } else { + typeSpecificMap = typeSpecificMapValue.(map[T]Handle[T]) + } + + // Retrieve the handle for the value, creating it if it isn't created yet. + var handle Handle[T] + if h, ok := typeSpecificMap[value]; !ok { + var clone T = value + handle.value = &clone + typeSpecificMap[value] = handle + } else { + handle = h + } + + globalMapMutex.Unlock() + + return handle +} + +//go:linkname decomposeInterface runtime.decomposeInterface +func decomposeInterface(i interface{}) (unsafe.Pointer, unsafe.Pointer) diff --git a/src/unique/handle_test.go b/src/unique/handle_test.go new file mode 100644 index 0000000000..864b11f232 --- /dev/null +++ b/src/unique/handle_test.go @@ -0,0 +1,76 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file is a copy of src/unique/handle_test.go in upstream Go, but with +// some parts removed that rely on Go runtime implementation details. + +package unique + +import ( + "fmt" + "reflect" + "testing" +) + +// Set up special types. Because the internal maps are sharded by type, +// this will ensure that we're not overlapping with other tests. +type testString string +type testIntArray [4]int +type testEface any +type testStringArray [3]string +type testStringStruct struct { + a string +} +type testStringStructArrayStruct struct { + s [2]testStringStruct +} +type testStruct struct { + z float64 + b string +} + +func TestHandle(t *testing.T) { + testHandle[testString](t, "foo") + testHandle[testString](t, "bar") + testHandle[testString](t, "") + testHandle[testIntArray](t, [4]int{7, 77, 777, 7777}) + //testHandle[testEface](t, nil) // requires Go 1.20 + testHandle[testStringArray](t, [3]string{"a", "b", "c"}) + testHandle[testStringStruct](t, testStringStruct{"x"}) + testHandle[testStringStructArrayStruct](t, testStringStructArrayStruct{ + s: [2]testStringStruct{testStringStruct{"y"}, testStringStruct{"z"}}, + }) + testHandle[testStruct](t, testStruct{0.5, "184"}) +} + +func testHandle[T comparable](t *testing.T, value T) { + name := reflect.TypeFor[T]().Name() + t.Run(fmt.Sprintf("%s/%#v", name, value), func(t *testing.T) { + t.Parallel() + + v0 := Make(value) + v1 := Make(value) + + if v0.Value() != v1.Value() { + t.Error("v0.Value != v1.Value") + } + if v0.Value() != value { + t.Errorf("v0.Value not %#v", value) + } + if v0 != v1 { + t.Error("v0 != v1") + } + + drainMaps(t) + }) +} + +// drainMaps ensures that the internal maps are drained. +func drainMaps(t *testing.T) { + t.Helper() + + globalMapMutex.Lock() + globalMap = nil + globalMapMutex.Unlock() +} From db2a06a9bb4a16850511294b7418fca6422e668b Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 22 Jun 2024 17:56:29 +0200 Subject: [PATCH 155/444] internal/abi: implement initial version of this package This package can never be a full version as seen in upstream Go, because TinyGo is very different. But it is necessary to define so that no code can accidentally use this package (now or in the future). It currently defines: - NoEscape which is needed by strings.Builder since Go 1.23. - FuncPCABI* which is needed by internal/syscall/unix on MacOS. --- compiler/symbol.go | 2 ++ loader/goroot.go | 1 + src/internal/abi/abi.go | 2 ++ src/internal/abi/escape.go | 10 ++++++++++ src/internal/abi/funcpc.go | 10 ++++++++++ 5 files changed, 25 insertions(+) create mode 100644 src/internal/abi/abi.go create mode 100644 src/internal/abi/escape.go create mode 100644 src/internal/abi/funcpc.go diff --git a/compiler/symbol.go b/compiler/symbol.go index 83bb4ccb26..37c9878591 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -139,6 +139,8 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) // On *nix systems, the "abort" functuion in libc is used to handle fatal panics. // Mark it as noreturn so LLVM can optimize away code. llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noreturn"), 0)) + case "internal/abi.NoEscape": + llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) case "runtime.alloc": // Tell the optimizer that runtime.alloc is an allocator, meaning that it // returns values that are never null and never alias to an existing value. diff --git a/loader/goroot.go b/loader/goroot.go index 7325db5b4b..09edab3203 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -236,6 +236,7 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { "device/": false, "examples/": false, "internal/": true, + "internal/abi/": false, "internal/binary/": false, "internal/bytealg/": false, "internal/cm/": false, diff --git a/src/internal/abi/abi.go b/src/internal/abi/abi.go new file mode 100644 index 0000000000..ee8e212a83 --- /dev/null +++ b/src/internal/abi/abi.go @@ -0,0 +1,2 @@ +// Package abi exposes low-level details of the Go compiler/runtime +package abi diff --git a/src/internal/abi/escape.go b/src/internal/abi/escape.go new file mode 100644 index 0000000000..0ecdf80308 --- /dev/null +++ b/src/internal/abi/escape.go @@ -0,0 +1,10 @@ +package abi + +import "unsafe" + +// Tell the compiler the given pointer doesn't escape. +// The compiler knows about this function and will give the nocapture parameter +// attribute. +func NoEscape(p unsafe.Pointer) unsafe.Pointer { + return p +} diff --git a/src/internal/abi/funcpc.go b/src/internal/abi/funcpc.go new file mode 100644 index 0000000000..1f99b80950 --- /dev/null +++ b/src/internal/abi/funcpc.go @@ -0,0 +1,10 @@ +package abi + +// These two signatures are present to satisfy the expectation of some programs +// (in particular internal/syscall/unix on MacOS). They do not currently have an +// implementation, in part because TinyGo doesn't use ABI0 or ABIInternal (it +// uses a C-like calling convention). + +func FuncPCABI0(f interface{}) uintptr + +func FuncPCABIInternal(f interface{}) uintptr From e865db232bad0a2a5152749edd6a9ce32557c8b7 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 24 Jun 2024 16:19:07 +0200 Subject: [PATCH 156/444] runtime: implement timers for Go 1.23 There were a number of changes in time.Timer/time.Ticker that need a separate implementation for Go 1.22 and Go 1.23. --- src/runtime/scheduler.go | 3 +- src/runtime/time.go | 30 ++--------- src/runtime/{timer.go => time_go122.go} | 32 ++++++++++++ src/runtime/time_go123.go | 68 +++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 28 deletions(-) rename src/runtime/{timer.go => time_go122.go} (60%) create mode 100644 src/runtime/time_go123.go diff --git a/src/runtime/scheduler.go b/src/runtime/scheduler.go index 618f6a94fc..30b2da8a62 100644 --- a/src/runtime/scheduler.go +++ b/src/runtime/scheduler.go @@ -181,12 +181,13 @@ func scheduler() { // Check for expired timers to trigger. if timerQueue != nil && now >= timerQueue.whenTicks() { scheduleLog("--- timer awoke") + delay := ticksToNanoseconds(now - timerQueue.whenTicks()) // Pop timer from queue. tn := timerQueue timerQueue = tn.next tn.next = nil // Run the callback stored in this timer node. - tn.callback(tn) + tn.callback(tn, delay) } t := runqueue.Pop() diff --git a/src/runtime/time.go b/src/runtime/time.go index 4fa3a418b5..50bf61cf3c 100644 --- a/src/runtime/time.go +++ b/src/runtime/time.go @@ -4,7 +4,7 @@ package runtime type timerNode struct { next *timerNode timer *timer - callback func(*timerNode) + callback func(node *timerNode, delta int64) } // whenTicks returns the (absolute) time when this timer should trigger next. @@ -12,17 +12,6 @@ func (t *timerNode) whenTicks() timeUnit { return nanosecondsToTicks(t.timer.when) } -// Defined in the time package, implemented here in the runtime. -// -//go:linkname startTimer time.startTimer -func startTimer(tim *timer) { - addTimer(&timerNode{ - timer: tim, - callback: timerCallback, - }) - scheduleLog("adding timer") -} - // timerCallback is called when a timer expires. It makes sure to call the // callback in the time package and to re-add the timer to the queue if this is // a ticker (repeating timer). @@ -32,11 +21,11 @@ func startTimer(tim *timer) { // dependency causes timerQueue not to get optimized away. // If timerQueue doesn't get optimized away, small programs (that don't call // time.NewTimer etc) would still pay the cost of these timers. -func timerCallback(tn *timerNode) { +func timerCallback(tn *timerNode, delta int64) { // Run timer function (implemented in the time package). // The seq parameter to the f function is not used in the time // package so is left zero. - tn.timer.f(tn.timer.arg, 0) + tn.timer.callCallback(delta) // If this is a periodic timer (a ticker), re-add it to the queue. if tn.timer.period != 0 { @@ -44,16 +33,3 @@ func timerCallback(tn *timerNode) { addTimer(tn) } } - -//go:linkname stopTimer time.stopTimer -func stopTimer(tim *timer) bool { - return removeTimer(tim) -} - -//go:linkname resetTimer time.resetTimer -func resetTimer(tim *timer, when int64) bool { - tim.when = when - removed := removeTimer(tim) - startTimer(tim) - return removed -} diff --git a/src/runtime/timer.go b/src/runtime/time_go122.go similarity index 60% rename from src/runtime/timer.go rename to src/runtime/time_go122.go index 134f9b9ac9..2994c27220 100644 --- a/src/runtime/timer.go +++ b/src/runtime/time_go122.go @@ -1,9 +1,13 @@ +//go:build !go1.23 + // Portions copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package runtime +// Time functions for Go 1.22 and below. + type puintptr uintptr // Package time knows the layout of this structure. @@ -31,3 +35,31 @@ type timer struct { // The status field holds one of the values below. status uint32 } + +func (tim *timer) callCallback(delta int64) { + tim.f(tim.arg, 0) +} + +// Defined in the time package, implemented here in the runtime. +// +//go:linkname startTimer time.startTimer +func startTimer(tim *timer) { + addTimer(&timerNode{ + timer: tim, + callback: timerCallback, + }) + scheduleLog("adding timer") +} + +//go:linkname stopTimer time.stopTimer +func stopTimer(tim *timer) bool { + return removeTimer(tim) +} + +//go:linkname resetTimer time.resetTimer +func resetTimer(tim *timer, when int64) bool { + tim.when = when + removed := removeTimer(tim) + startTimer(tim) + return removed +} diff --git a/src/runtime/time_go123.go b/src/runtime/time_go123.go new file mode 100644 index 0000000000..cfef4d3934 --- /dev/null +++ b/src/runtime/time_go123.go @@ -0,0 +1,68 @@ +//go:build go1.23 + +package runtime + +import "unsafe" + +// Time functions for Go 1.23 and above. + +// This is the timer that's used internally inside the runtime. +type timer struct { + // When to call the timer, and the interval for the ticker. + when int64 + period int64 + + // Callback from the time package. + f func(arg any, seq uintptr, delta int64) + arg any +} + +func (tim *timer) callCallback(delta int64) { + tim.f(tim.arg, 0, delta) +} + +// This is the struct used internally in the runtime. The first two fields are +// the same as time.Timer and time.Ticker so it can be used as-is in the time +// package. +type timeTimer struct { + c unsafe.Pointer // <-chan time.Time + init bool + timer +} + +//go:linkname newTimer time.newTimer +func newTimer(when, period int64, f func(arg any, seq uintptr, delta int64), arg any, c unsafe.Pointer) *timeTimer { + tim := &timeTimer{ + c: c, + init: true, + timer: timer{ + when: when, + period: period, + f: f, + arg: arg, + }, + } + scheduleLog("new timer") + addTimer(&timerNode{ + timer: &tim.timer, + callback: timerCallback, + }) + return tim +} + +//go:linkname stopTimer time.stopTimer +func stopTimer(tim *timeTimer) bool { + return removeTimer(&tim.timer) +} + +//go:linkname resetTimer time.resetTimer +func resetTimer(t *timeTimer, when, period int64) bool { + t.timer.when = when + t.timer.period = period + removed := removeTimer(&t.timer) + addTimer(&timerNode{ + timer: &t.timer, + callback: timerCallback, + }) + return removed +} From e300e90a637a6107fb18389fb4091c12fd3054b2 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 24 Jun 2024 16:24:22 +0200 Subject: [PATCH 157/444] reflect: implement Type.Overflow* functions They're already implemented for the Value equivalents. But since Go 1.23, such methods also exist for reflect.Type. --- src/reflect/all_test.go | 41 ++++++++++++++++++++++++- src/reflect/type.go | 68 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 1 deletion(-) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index 4c2fcc48be..436bc00341 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -4897,7 +4897,7 @@ func TestComparable(t *testing.T) { } } -func TestOverflow(t *testing.T) { +func TestValueOverflow(t *testing.T) { if ovf := V(float64(0)).OverflowFloat(1e300); ovf { t.Errorf("%v wrongly overflows float64", 1e300) } @@ -4936,6 +4936,45 @@ func TestOverflow(t *testing.T) { } } +func TestTypeOverflow(t *testing.T) { + if ovf := TypeFor[float64]().OverflowFloat(1e300); ovf { + t.Errorf("%v wrongly overflows float64", 1e300) + } + + maxFloat32 := float64((1<<24 - 1) << (127 - 23)) + if ovf := TypeFor[float32]().OverflowFloat(maxFloat32); ovf { + t.Errorf("%v wrongly overflows float32", maxFloat32) + } + ovfFloat32 := float64((1<<24-1)<<(127-23) + 1<<(127-52)) + if ovf := TypeFor[float32]().OverflowFloat(ovfFloat32); !ovf { + t.Errorf("%v should overflow float32", ovfFloat32) + } + if ovf := TypeFor[float32]().OverflowFloat(-ovfFloat32); !ovf { + t.Errorf("%v should overflow float32", -ovfFloat32) + } + + maxInt32 := int64(0x7fffffff) + if ovf := TypeFor[int32]().OverflowInt(maxInt32); ovf { + t.Errorf("%v wrongly overflows int32", maxInt32) + } + if ovf := TypeFor[int32]().OverflowInt(-1 << 31); ovf { + t.Errorf("%v wrongly overflows int32", -int64(1)<<31) + } + ovfInt32 := int64(1 << 31) + if ovf := TypeFor[int32]().OverflowInt(ovfInt32); !ovf { + t.Errorf("%v should overflow int32", ovfInt32) + } + + maxUint32 := uint64(0xffffffff) + if ovf := TypeFor[uint32]().OverflowUint(maxUint32); ovf { + t.Errorf("%v wrongly overflows uint32", maxUint32) + } + ovfUint32 := uint64(1 << 32) + if ovf := TypeFor[uint32]().OverflowUint(ovfUint32); !ovf { + t.Errorf("%v should overflow uint32", ovfUint32) + } +} + /* func checkSameType(t *testing.T, x Type, y any) { diff --git a/src/reflect/type.go b/src/reflect/type.go index aa84c77a24..5dd59b3296 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -392,6 +392,22 @@ type Type interface { // It panics if the type's Kind is not Func. // It panics if i is not in the range [0, NumOut()). Out(i int) Type + + // OverflowComplex reports whether the complex128 x cannot be represented by type t. + // It panics if t's Kind is not Complex64 or Complex128. + OverflowComplex(x complex128) bool + + // OverflowFloat reports whether the float64 x cannot be represented by type t. + // It panics if t's Kind is not Float32 or Float64. + OverflowFloat(x float64) bool + + // OverflowInt reports whether the int64 x cannot be represented by type t. + // It panics if t's Kind is not Int, Int8, Int16, Int32, or Int64. + OverflowInt(x int64) bool + + // OverflowUint reports whether the uint64 x cannot be represented by type t. + // It panics if t's Kind is not Uint, Uintptr, Uint8, Uint16, Uint32, or Uint64. + OverflowUint(x uint64) bool } // Constants for the 'meta' byte. @@ -1081,6 +1097,58 @@ func (t rawType) Out(i int) Type { panic("unimplemented: (reflect.Type).Out()") } +// OverflowComplex reports whether the complex128 x cannot be represented by type t. +// It panics if t's Kind is not Complex64 or Complex128. +func (t rawType) OverflowComplex(x complex128) bool { + k := t.Kind() + switch k { + case Complex64: + return overflowFloat32(real(x)) || overflowFloat32(imag(x)) + case Complex128: + return false + } + panic("reflect: OverflowComplex of non-complex type") +} + +// OverflowFloat reports whether the float64 x cannot be represented by type t. +// It panics if t's Kind is not Float32 or Float64. +func (t rawType) OverflowFloat(x float64) bool { + k := t.Kind() + switch k { + case Float32: + return overflowFloat32(x) + case Float64: + return false + } + panic("reflect: OverflowFloat of non-float type") +} + +// OverflowInt reports whether the int64 x cannot be represented by type t. +// It panics if t's Kind is not Int, Int8, Int16, Int32, or Int64. +func (t rawType) OverflowInt(x int64) bool { + k := t.Kind() + switch k { + case Int, Int8, Int16, Int32, Int64: + bitSize := t.Size() * 8 + trunc := (x << (64 - bitSize)) >> (64 - bitSize) + return x != trunc + } + panic("reflect: OverflowInt of non-int type") +} + +// OverflowUint reports whether the uint64 x cannot be represented by type t. +// It panics if t's Kind is not Uint, Uintptr, Uint8, Uint16, Uint32, or Uint64. +func (t rawType) OverflowUint(x uint64) bool { + k := t.Kind() + switch k { + case Uint, Uintptr, Uint8, Uint16, Uint32, Uint64: + bitSize := t.Size() * 8 + trunc := (x << (64 - bitSize)) >> (64 - bitSize) + return x != trunc + } + panic("reflect: OverflowUint of non-uint type") +} + func (t rawType) Method(i int) Method { panic("unimplemented: (reflect.Type).Method()") } From 250426c1e525f4600db9ff2aa6ef0d2e791b98e8 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 24 Jun 2024 16:34:12 +0200 Subject: [PATCH 158/444] sync: add Map.Clear() This was added in Go 1.23 and is needed for the net/mail package. --- src/sync/map_go123.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/sync/map_go123.go diff --git a/src/sync/map_go123.go b/src/sync/map_go123.go new file mode 100644 index 0000000000..b7bd61e103 --- /dev/null +++ b/src/sync/map_go123.go @@ -0,0 +1,13 @@ +//go:build go1.23 + +package sync + +// Go 1.23 added the Clear() method. The clear() function is added in Go 1.21, +// so this method can be moved to map.go once we drop support for Go 1.20 and +// below. + +func (m *Map) Clear() { + m.lock.Lock() + defer m.lock.Unlock() + clear(m.m) +} From 1d1f4fc401b449e325f6c2c04001db14801cd398 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 15 Aug 2024 19:23:42 +0200 Subject: [PATCH 159/444] syscall: add all MacOS errno values ELOOP is used starting with Go 1.23. But I figured I could just add the whole set. --- src/syscall/syscall_libc_darwin.go | 126 +++++++++++++++++++++++++---- 1 file changed, 109 insertions(+), 17 deletions(-) diff --git a/src/syscall/syscall_libc_darwin.go b/src/syscall/syscall_libc_darwin.go index d64f1061f3..f0d297a776 100644 --- a/src/syscall/syscall_libc_darwin.go +++ b/src/syscall/syscall_libc_darwin.go @@ -57,24 +57,116 @@ const ( O_NONBLOCK = 0x4 ) -// Source: https://opensource.apple.com/source/xnu/xnu-7195.81.3/bsd/sys/errno.h.auto.html +// Source: https://opensource.apple.com/source/xnu/xnu-7195.141.2/bsd/sys/errno.h.auto.html const ( - EPERM Errno = 1 - ENOENT Errno = 2 - EACCES Errno = 13 - EEXIST Errno = 17 - EINTR Errno = 4 - ENOTDIR Errno = 20 - EISDIR Errno = 21 - EINVAL Errno = 22 - EMFILE Errno = 24 - EROFS Errno = 30 - EPIPE Errno = 32 - EAGAIN Errno = 35 - ENOTCONN Errno = 57 - ETIMEDOUT Errno = 60 - ENOSYS Errno = 78 - EWOULDBLOCK Errno = EAGAIN + EPERM Errno = 1 + ENOENT Errno = 2 + ESRCH Errno = 3 + EINTR Errno = 4 + EIO Errno = 5 + ENXIO Errno = 6 + E2BIG Errno = 7 + ENOEXEC Errno = 8 + EBADF Errno = 9 + ECHILD Errno = 10 + EDEADLK Errno = 11 + ENOMEM Errno = 12 + EACCES Errno = 13 + EFAULT Errno = 14 + ENOTBLK Errno = 15 + EBUSY Errno = 16 + EEXIST Errno = 17 + EXDEV Errno = 18 + ENODEV Errno = 19 + ENOTDIR Errno = 20 + EISDIR Errno = 21 + EINVAL Errno = 22 + ENFILE Errno = 23 + EMFILE Errno = 24 + ENOTTY Errno = 25 + ETXTBSY Errno = 26 + EFBIG Errno = 27 + ENOSPC Errno = 28 + ESPIPE Errno = 29 + EROFS Errno = 30 + EMLINK Errno = 31 + EPIPE Errno = 32 + EDOM Errno = 33 + ERANGE Errno = 34 + EAGAIN Errno = 35 + EWOULDBLOCK Errno = EAGAIN + EINPROGRESS Errno = 36 + EALREADY Errno = 37 + ENOTSOCK Errno = 38 + EDESTADDRREQ Errno = 39 + EMSGSIZE Errno = 40 + EPROTOTYPE Errno = 41 + ENOPROTOOPT Errno = 42 + EPROTONOSUPPORT Errno = 43 + ESOCKTNOSUPPORT Errno = 44 + ENOTSUP Errno = 45 + EPFNOSUPPORT Errno = 46 + EAFNOSUPPORT Errno = 47 + EADDRINUSE Errno = 48 + EADDRNOTAVAIL Errno = 49 + ENETDOWN Errno = 50 + ENETUNREACH Errno = 51 + ENETRESET Errno = 52 + ECONNABORTED Errno = 53 + ECONNRESET Errno = 54 + ENOBUFS Errno = 55 + EISCONN Errno = 56 + ENOTCONN Errno = 57 + ESHUTDOWN Errno = 58 + ETOOMANYREFS Errno = 59 + ETIMEDOUT Errno = 60 + ECONNREFUSED Errno = 61 + ELOOP Errno = 62 + ENAMETOOLONG Errno = 63 + EHOSTDOWN Errno = 64 + EHOSTUNREACH Errno = 65 + ENOTEMPTY Errno = 66 + EPROCLIM Errno = 67 + EUSERS Errno = 68 + EDQUOT Errno = 69 + ESTALE Errno = 70 + EREMOTE Errno = 71 + EBADRPC Errno = 72 + ERPCMISMATCH Errno = 73 + EPROGUNAVAIL Errno = 74 + EPROGMISMATCH Errno = 75 + EPROCUNAVAIL Errno = 76 + ENOLCK Errno = 77 + ENOSYS Errno = 78 + EFTYPE Errno = 79 + EAUTH Errno = 80 + ENEEDAUTH Errno = 81 + EPWROFF Errno = 82 + EDEVERR Errno = 83 + EOVERFLOW Errno = 84 + EBADEXEC Errno = 85 + EBADARCH Errno = 86 + ESHLIBVERS Errno = 87 + EBADMACHO Errno = 88 + ECANCELED Errno = 89 + EIDRM Errno = 90 + ENOMSG Errno = 91 + EILSEQ Errno = 92 + ENOATTR Errno = 93 + EBADMSG Errno = 94 + EMULTIHOP Errno = 95 + ENODATA Errno = 96 + ENOLINK Errno = 97 + ENOSR Errno = 98 + ENOSTR Errno = 99 + EPROTO Errno = 100 + ETIME Errno = 101 + EOPNOTSUPP Errno = 102 + ENOPOLICY Errno = 103 + ENOTRECOVERABLE Errno = 104 + EOWNERDEAD Errno = 105 + EQFULL Errno = 106 + ELAST Errno = 106 ) type Signal int From 8b626e6ea7e98887fbd48e3c55216fdd9327fac6 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 15 Aug 2024 19:55:39 +0200 Subject: [PATCH 160/444] compiler: add support for Go 1.23 range-over-func --- compiler/compiler.go | 2 +- compiler/symbol.go | 2 +- main_test.go | 3 +++ testdata/go1.23/go.mod | 3 +++ testdata/go1.23/main.go | 18 ++++++++++++++++++ testdata/go1.23/out.txt | 11 +++++++++++ 6 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 testdata/go1.23/go.mod create mode 100644 testdata/go1.23/main.go create mode 100644 testdata/go1.23/out.txt diff --git a/compiler/compiler.go b/compiler/compiler.go index bc17250d99..201605d78c 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1963,7 +1963,7 @@ func (b *builder) getValue(expr ssa.Value, pos token.Pos) llvm.Value { return value } else { // indicates a compiler bug - panic("local has not been parsed: " + expr.String()) + panic("SSA value not previously found in function: " + expr.String()) } } } diff --git a/compiler/symbol.go b/compiler/symbol.go index 37c9878591..29f0095208 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -218,7 +218,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) // should be created right away. // 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" { + if fn.Synthetic != "" && fn.Synthetic != "package initializer" && fn.Synthetic != "generic function" && fn.Synthetic != "range-over-func yield" { irbuilder := c.ctx.NewBuilder() b := newBuilder(c, irbuilder, fn) b.createFunction() diff --git a/main_test.go b/main_test.go index 4c11f7b18d..e70e9f7fdc 100644 --- a/main_test.go +++ b/main_test.go @@ -97,6 +97,9 @@ func TestBuild(t *testing.T) { if minor >= 22 { tests = append(tests, "go1.22/") } + if minor >= 23 { + tests = append(tests, "go1.23/") + } if *testTarget != "" { // This makes it possible to run one specific test (instead of all), diff --git a/testdata/go1.23/go.mod b/testdata/go1.23/go.mod new file mode 100644 index 0000000000..c0ad79b6d5 --- /dev/null +++ b/testdata/go1.23/go.mod @@ -0,0 +1,3 @@ +module github.com/tinygo-org/tinygo/testdata/go1.23 + +go 1.23 diff --git a/testdata/go1.23/main.go b/testdata/go1.23/main.go new file mode 100644 index 0000000000..01782d235e --- /dev/null +++ b/testdata/go1.23/main.go @@ -0,0 +1,18 @@ +package main + +func main() { + testFuncRange(counter) +} + +func testFuncRange(f func(yield func(int) bool)) { + for i := range f { + println(i) + } + println("go1.23 has lift-off!") +} + +func counter(yield func(int) bool) { + for i := 10; i >= 1; i-- { + yield(i) + } +} diff --git a/testdata/go1.23/out.txt b/testdata/go1.23/out.txt new file mode 100644 index 0000000000..aeeb7d40e1 --- /dev/null +++ b/testdata/go1.23/out.txt @@ -0,0 +1,11 @@ +10 +9 +8 +7 +6 +5 +4 +3 +2 +1 +go1.23 has lift-off! From b6c53a6f0e4b4adca49a2011698bbb690c688eb9 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 16 Aug 2024 21:46:55 +0200 Subject: [PATCH 161/444] darwin: work around a linker error for the mime/quotedprintable tests This is needed for Go 1.23 support. These functions should ideally get implemented. Until that's done, just panic here. Apparently panicking in internal/abi.FuncPCABI0 is enough to fix all linker errors for mime/quotedprintable. --- src/internal/abi/funcpc.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/internal/abi/funcpc.go b/src/internal/abi/funcpc.go index 1f99b80950..a926c392f8 100644 --- a/src/internal/abi/funcpc.go +++ b/src/internal/abi/funcpc.go @@ -5,6 +5,10 @@ package abi // implementation, in part because TinyGo doesn't use ABI0 or ABIInternal (it // uses a C-like calling convention). -func FuncPCABI0(f interface{}) uintptr +func FuncPCABI0(f interface{}) uintptr { + panic("unimplemented: internal/abi.FuncPCABI0") +} -func FuncPCABIInternal(f interface{}) uintptr +func FuncPCABIInternal(f interface{}) uintptr { + panic("unimplemented: internal/abi.FuncPCABIInternal") +} From 7e284a37fccc970e05db124863c66c26c8e0ff40 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 22 Jun 2024 17:04:44 +0200 Subject: [PATCH 162/444] ci: use Go 1.23 --- .circleci/config.yml | 6 +++--- .github/workflows/build-macos.yml | 4 ++-- .github/workflows/linux.yml | 8 ++++---- .github/workflows/windows.yml | 8 ++++---- Dockerfile | 4 ++-- GNUmakefile | 2 -- builder/config.go | 4 ++-- main_test.go | 7 ++++++- src/syscall/syscall_libc_darwin.go | 1 + src/syscall/syscall_libc_wasi.go | 4 ++++ testdata/errors/types.go | 2 +- 11 files changed, 29 insertions(+), 21 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f694a4ee11..826240d9a2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -107,9 +107,9 @@ jobs: # "make lint" fails before go 1.21 because internal/tools/go.mod specifies packages that require go 1.21 fmt-check: false resource_class: large - test-llvm18-go122: + test-llvm18-go123: docker: - - image: golang:1.22-bullseye + - image: golang:1.23-bullseye steps: - test-linux: llvm: "18" @@ -122,4 +122,4 @@ workflows: # least the smoke tests still pass. - test-llvm15-go119 # This tests LLVM 18 support when linking against system libraries. - - test-llvm18-go122 + - test-llvm18-go123 diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 17fe3852a6..5fef339dd7 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -37,7 +37,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version: '1.23' cache: true - name: Restore LLVM source cache uses: actions/cache/restore@v4 @@ -145,7 +145,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version: '1.23' 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 589eb71fe6..ab150317d3 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.22-alpine + image: golang:1.23-alpine steps: - name: Install apk dependencies # tar: needed for actions/cache@v4 @@ -141,7 +141,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version: '1.23' cache: true - name: Install wasmtime uses: bytecodealliance/actions/wasmtime/setup@v1 @@ -184,7 +184,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version: '1.23' cache: true - name: Install Node.js uses: actions/setup-node@v4 @@ -307,7 +307,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version: '1.23' 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 0d144304d5..5287e108ea 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -35,7 +35,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version: '1.23' cache: true - name: Restore cached LLVM source uses: actions/cache/restore@v4 @@ -143,7 +143,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version: '1.23' cache: true - name: Download TinyGo build uses: actions/download-artifact@v4 @@ -173,7 +173,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version: '1.23' cache: true - name: Download TinyGo build uses: actions/download-artifact@v4 @@ -209,7 +209,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version: '1.23' cache: true - name: Download TinyGo build uses: actions/download-artifact@v4 diff --git a/Dockerfile b/Dockerfile index eb99e0e952..9a9effac2b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # tinygo-llvm stage obtains the llvm source for TinyGo -FROM golang:1.22 AS tinygo-llvm +FROM golang:1.23 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.22 AS tinygo-compiler +FROM golang:1.23 AS tinygo-compiler # Copy tinygo build. COPY --from=tinygo-compiler-build /tinygo/build/release/tinygo /tinygo diff --git a/GNUmakefile b/GNUmakefile index 1fe2a5a6c2..94d9c357d2 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -370,7 +370,6 @@ endif # Additional standard library packages that pass tests on individual platforms TEST_PACKAGES_LINUX := \ archive/zip \ - bytes \ compress/flate \ crypto/hmac \ debug/dwarf \ @@ -381,7 +380,6 @@ TEST_PACKAGES_LINUX := \ net \ os/user \ strconv \ - testing/fstest \ text/tabwriter \ text/template/parse diff --git a/builder/config.go b/builder/config.go index a82450a638..40c6f0c9a7 100644 --- a/builder/config.go +++ b/builder/config.go @@ -27,10 +27,10 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) { if err != nil { return nil, err } - if major != 1 || minor < 19 || minor > 22 { + if major != 1 || minor < 19 || minor > 23 { // 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.19 through 1.22, got go%d.%d", major, minor) + return nil, fmt.Errorf("requires go version 1.19 through 1.23, got go%d.%d", major, minor) } return &compileopts.Config{ diff --git a/main_test.go b/main_test.go index e70e9f7fdc..836a4b1730 100644 --- a/main_test.go +++ b/main_test.go @@ -215,7 +215,7 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { } } if options.GOOS == "linux" && (options.GOARCH == "mips" || options.GOARCH == "mipsle") { - if name == "atomic.go" { + if name == "atomic.go" || name == "timers.go" { // 64-bit atomic operations aren't currently supported on MIPS. continue } @@ -246,6 +246,11 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { // some compiler changes). continue + case "timers.go": + // Crashes starting with Go 1.23. + // Bug: https://github.com/llvm/llvm-project/issues/104032 + continue + default: } } diff --git a/src/syscall/syscall_libc_darwin.go b/src/syscall/syscall_libc_darwin.go index f0d297a776..3b0341880a 100644 --- a/src/syscall/syscall_libc_darwin.go +++ b/src/syscall/syscall_libc_darwin.go @@ -55,6 +55,7 @@ const ( F_GETFL = 0x3 F_SETFL = 0x4 O_NONBLOCK = 0x4 + TIOCSPGRP = 0x80047476 ) // Source: https://opensource.apple.com/source/xnu/xnu-7195.141.2/bsd/sys/errno.h.auto.html diff --git a/src/syscall/syscall_libc_wasi.go b/src/syscall/syscall_libc_wasi.go index 2d83a87147..06169bb2b9 100644 --- a/src/syscall/syscall_libc_wasi.go +++ b/src/syscall/syscall_libc_wasi.go @@ -104,6 +104,9 @@ const ( // ../../lib/wasi-libc/expected/wasm32-wasi/predefined-macros.txt F_GETFL = 3 F_SETFL = 4 + + // ../../lib/wasi-libc/libc-top-half/musl/arch/generic/bits/ioctl.h + TIOCSPGRP = 0x5410 ) // These values are needed as a stub until Go supports WASI as a full target. @@ -114,6 +117,7 @@ const ( SYS_FCNTL SYS_FCNTL64 SYS_FSTATAT64 + SYS_IOCTL SYS_OPENAT SYS_UNLINKAT PATH_MAX = 4096 diff --git a/testdata/errors/types.go b/testdata/errors/types.go index 6bd949f0c4..a74fb4a335 100644 --- a/testdata/errors/types.go +++ b/testdata/errors/types.go @@ -7,6 +7,6 @@ func main() { } // ERROR: # command-line-arguments -// ERROR: types.go:4:6: a declared and not used +// ERROR: types.go:4:6: declared and not used: a // ERROR: types.go:5:6: cannot use "foobar" (untyped string constant) as int value in assignment // ERROR: types.go:6:2: undefined: nonexisting From 16950780d38941c96fe9fdbeede457bb858c2423 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Fri, 16 Aug 2024 17:06:04 +0200 Subject: [PATCH 163/444] targets: remove import-memory flag from wasm-unknown target to fix #4319 Signed-off-by: deadprogram --- targets/wasm-unknown.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/targets/wasm-unknown.json b/targets/wasm-unknown.json index a3f6313c19..93a4d391e0 100644 --- a/targets/wasm-unknown.json +++ b/targets/wasm-unknown.json @@ -19,8 +19,7 @@ "ldflags": [ "--stack-first", "--no-demangle", - "--no-entry", - "--import-memory" + "--no-entry" ], "extra-files": [ "src/runtime/asm_tinygowasm.S" From 16cd500000174dce35a8ab6da2e7b98fc3d839cd Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 19 Aug 2024 19:56:43 +0200 Subject: [PATCH 164/444] ci: update apt repo for sizediff toolchain This was still at jammy (22.04), while the CI container was noble (24.04). Somehow this didn't break, but it certainly isn't ideal to install packages across Ubuntu versions! --- .github/workflows/sizediff-install-pkgs.sh | 2 +- .github/workflows/sizediff.yml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sizediff-install-pkgs.sh b/.github/workflows/sizediff-install-pkgs.sh index 31edc57506..e77600d184 100755 --- a/.github/workflows/sizediff-install-pkgs.sh +++ b/.github/workflows/sizediff-install-pkgs.sh @@ -2,7 +2,7 @@ # still works after checking out the dev branch (that is, when going from LLVM # 16 to LLVM 17 for example, both Clang 16 and Clang 17 are installed). -echo 'deb https://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main' | sudo tee /etc/apt/sources.list.d/llvm.list +echo 'deb https://apt.llvm.org/noble/ llvm-toolchain-noble-18 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 --no-install-recommends -y \ diff --git a/.github/workflows/sizediff.yml b/.github/workflows/sizediff.yml index 78535016ee..b9c40b1ea0 100644 --- a/.github/workflows/sizediff.yml +++ b/.github/workflows/sizediff.yml @@ -9,6 +9,8 @@ concurrency: jobs: sizediff: + # Note: when updating the Ubuntu version, also update the Ubuntu version in + # sizediff-install-pkgs.sh runs-on: ubuntu-24.04 permissions: pull-requests: write From ef4f46f1d1550beb62324d750c496b2b4a7f76d0 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 19 Aug 2024 20:33:24 +0200 Subject: [PATCH 165/444] all: version 0.33.0 --- CHANGELOG.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ goenv/version.go | 2 +- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c066df9fdb..551951dfa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,63 @@ +0.33.0 +--- + +* **general** + - use latest version of x/tools + - add chromeos 9p support for flashing + - sort compiler error messages by source position in a package + - don't include prebuilt libraries in the release to simplify packaging and reduce the release tarball size + - show runtime panic addresses for `tinygo run` + - support Go 1.23 (including all new language features) + - `test`: support GOOS/GOARCH pairs in the `-target` flag + - `test`: remove message after test binary built +* **compiler** + - remove unused registers for x86_64 linux syscalls + - remove old atomics workaround for AVR (not necessary in modern LLVM versions) + - support `golang.org/x/sys/unix` syscalls + - `builder`: remove workaround for generics race condition + - `builder`: add package ID to compiler and optimization error messages + - `builder`: show better error messages for some common linker errors + - `cgo`: support preprocessor macros passed on the command line + - `cgo`: use absolute paths for error messages + - `cgo`: add support for printf + - `loader`: handle `go list` errors inside TinyGo (for better error messages) + - `transform`: fix incorrect alignment of heap-to-stack transform + - `transform`: use thinlto-pre-link passes (instead of the full pipeline) to speed up compilation speed slightly +* **standard library** + - `crypto/tls`: add CipherSuiteName and some extra fields to ConnectionSTate + - `internal/abi`: implement initial version of this package + - `machine`: use new `internal/binary` package + - `machine`: rewrite Reply() to fix sending long replies in I2C Target Mode + - `machine/usb/descriptor`: Reset joystick physical + - `machine/usb/descriptor`: Drop second joystick hat + - `machine/usb/descriptor`: Add more HID... functions + - `machine/usb/descriptor`: Fix encoding of values + - `machine/usb/hid/joystick`: Allow more hat switches + - `os`: add `Chown`, `Truncate` + - `os/user`: use stdlib version of this package + - `reflect`: return correct name for the `unsafe.Pointer` type + - `reflect`: implement `Type.Overflow*` functions + - `runtime`: implement dummy `getAuxv` to satisfy golang.org/x/sys/ + - `runtime`: don't zero out new allocations for `-gc=leaking` when they are already zeroed + - `runtime`: simplify slice growing/appending code + - `runtime`: print a message when a fatal signal like SIGSEGV happens + - `runtime/debug`: add `GoVersion` to `debug.BuildInfo` + - `sync`: add `Map.Clear()` + - `sync/atomic`: add And* and Or* compiler intrinsics needed for Go 1.23 + - `syscall`: add `Fork` and `Execve` + - `syscall`: add all MacOS errno values + - `testing`: stub out `T.Deadline` + - `unique`: implement custom (naive) version of the unique package +* **targets** + - `arm`: support `GOARM=*,softfloat` (softfloat support for ARM v5, v6, and v7) + - `mips`: add linux/mipsle (and experimental linux/mips) support + - `mips`: add `GOMIPS=softfloat` support + - `wasip2`: add WASI preview 2 support + - `wasm/js`: add `node:` prefix in `require()` call of wasm_exec.js + - `wasm-unknown`: make sure the `os` package can be imported + - `wasm-unknown`: remove import-memory flag + + 0.32.0 --- diff --git a/goenv/version.go b/goenv/version.go index bed4e26f34..bd773f20a5 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -9,7 +9,7 @@ import ( // Version of TinyGo. // Update this value before release of new version of software. -const version = "0.33.0-dev" +const version = "0.33.0" var ( // This variable is set at build time using -ldflags parameters. From 336b9b33ab9dc51235519d0a7ce2116fe44fbe4e Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Wed, 21 Aug 2024 02:03:02 +0200 Subject: [PATCH 166/444] tinygo: detect GOOS=wasip1 for relative WASI paths via config instead of target name (#4423) --- main.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 8f788efb86..51e62aaaee 100644 --- a/main.go +++ b/main.go @@ -305,13 +305,11 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options // // Ex. run --dir=.. --dir=../.. --dir=../../.. var dirs []string - switch config.Options.Target { + switch config.Target.GOOS { case "wasip1": dirs = dirsToModuleRootRel(result.MainDir, result.ModuleRoot) - case "wasip2": - dirs = dirsToModuleRootAbs(result.MainDir, result.ModuleRoot) default: - return fmt.Errorf("unknown GOOS target: %v", config.Options.Target) + dirs = dirsToModuleRootAbs(result.MainDir, result.ModuleRoot) } args := []string{"run"} From 83c98a23cea4d3d59d228ffa60f69303fd57dbb0 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 21 Aug 2024 16:30:29 +0200 Subject: [PATCH 167/444] mips: fix crash with GOMIPS=softfloat `defer` and `GOMIPS=softfloat` together would result in a crash. This patch fixes this issue. --- compiler/defer.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/compiler/defer.go b/compiler/defer.go index df8686957a..677df8f2a1 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -16,6 +16,7 @@ package compiler import ( "go/types" "strconv" + "strings" "github.com/tinygo-org/tinygo/compiler/llvmutil" "golang.org/x/tools/go/ssa" @@ -198,7 +199,13 @@ jal 1f addiu $$ra, 8 sw $$ra, 4($$5) .set at` - constraints = "={$4},{$5},~{$1},~{$2},~{$3},~{$5},~{$6},~{$7},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$16},~{$17},~{$18},~{$19},~{$20},~{$21},~{$22},~{$23},~{$24},~{$25},~{$26},~{$27},~{$28},~{$29},~{$30},~{$31},~{$f0},~{$f1},~{$f2},~{$f3},~{$f4},~{$f5},~{$f6},~{$f7},~{$f8},~{$f9},~{$f10},~{$f11},~{$f12},~{$f13},~{$f14},~{$f15},~{$f16},~{$f17},~{$f18},~{$f19},~{$f20},~{$f21},~{$f22},~{$f23},~{$f24},~{$f25},~{$f26},~{$f27},~{$f28},~{$f29},~{$f30},~{$f31},~{memory}" + constraints = "={$4},{$5},~{$1},~{$2},~{$3},~{$5},~{$6},~{$7},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$16},~{$17},~{$18},~{$19},~{$20},~{$21},~{$22},~{$23},~{$24},~{$25},~{$26},~{$27},~{$28},~{$29},~{$30},~{$31},~{memory}" + if !strings.Contains(b.Features, "+soft-float") { + // Using floating point registers together with GOMIPS=softfloat + // results in a crash: "This value type is not natively supported!" + // So only add them when using hardfloat. + constraints += ",~{$f0},~{$f1},~{$f2},~{$f3},~{$f4},~{$f5},~{$f6},~{$f7},~{$f8},~{$f9},~{$f10},~{$f11},~{$f12},~{$f13},~{$f14},~{$f15},~{$f16},~{$f17},~{$f18},~{$f19},~{$f20},~{$f21},~{$f22},~{$f23},~{$f24},~{$f25},~{$f26},~{$f27},~{$f28},~{$f29},~{$f30},~{$f31}" + } case "riscv32": asmString = ` la a2, 1f From 1ef1aa78628070f618b732bb68136049511ede4c Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 22 Aug 2024 15:16:15 +0200 Subject: [PATCH 168/444] mips: fix big-endian (GOARCH=mips) support I made an awkward mistake, mixing up GOOS and GOARCH. So here is a fix, with an associated test. --- compileopts/target.go | 2 +- main_test.go | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/compileopts/target.go b/compileopts/target.go index 501d99f119..26a91086b9 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -338,7 +338,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) { case "mips", "mipsle": spec.CPU = "mips32r2" spec.CFlags = append(spec.CFlags, "-fno-pic") - if options.GOOS == "mips" { + if options.GOARCH == "mips" { llvmarch = "mips" // big endian } else { llvmarch = "mipsel" // little endian diff --git a/main_test.go b/main_test.go index 836a4b1730..40002ee871 100644 --- a/main_test.go +++ b/main_test.go @@ -177,6 +177,14 @@ func TestBuild(t *testing.T) { }) } } + t.Run("MIPS big-endian", func(t *testing.T) { + // Run a single test for GOARCH=mips to see whether it works at all. + // Big-endian MIPS isn't fully supported yet, but simple examples + // should work. + t.Parallel() + options := optionsFromOSARCH("linux/mips/softfloat", sema) + runTest("alias.go", options, t, nil, nil) + }) t.Run("WebAssembly", func(t *testing.T) { t.Parallel() runPlatTests(optionsFromTarget("wasm", sema), tests, t) From d144b116111aa5ab8b14672d0692cacae35ec80c Mon Sep 17 00:00:00 2001 From: deadprogram Date: Fri, 23 Aug 2024 12:44:39 +0200 Subject: [PATCH 169/444] fix: add missing Truncate() function stub to os/file for bare-metal systems Signed-off-by: deadprogram --- src/os/file_other.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/os/file_other.go b/src/os/file_other.go index d359b0fb67..977fcac12d 100644 --- a/src/os/file_other.go +++ b/src/os/file_other.go @@ -135,3 +135,17 @@ func Readlink(name string) (string, error) { func tempDir() string { return "/tmp" } + +// Truncate is unsupported on this system. +func Truncate(filename string, size int64) (err error) { + return ErrUnsupported +} + +// Truncate is unsupported on this system. +func (f *File) Truncate(size int64) (err error) { + if f.handle == nil { + return ErrClosed + } + + return Truncate(f.name, size) +} From 753f4b38b49c32b4b604dc5065653910923dfb3e Mon Sep 17 00:00:00 2001 From: sago35 Date: Wed, 21 Aug 2024 09:03:46 +0900 Subject: [PATCH 170/444] version: update to 0.34.0-dev --- goenv/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/goenv/version.go b/goenv/version.go index bd773f20a5..1db5a630af 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -9,7 +9,7 @@ import ( // Version of TinyGo. // Update this value before release of new version of software. -const version = "0.33.0" +const version = "0.34.0-dev" var ( // This variable is set at build time using -ldflags parameters. From 105fe9b25df656b0683174245c2c1f6ccaf5fd66 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 18 Aug 2024 19:33:04 +0200 Subject: [PATCH 171/444] darwin: replace custom syscall package with Go native syscall package This required a few compiler and runtime tricks to work, but I ran a bunch of tests and it seems fine. (CI will of course do more exhaustive testing). The main benefit here is that we don't need to maintain the darwin version of the syscall package, and reduce extra risks for bugs (because we reuse the well-tested syscall package). For example, Go 1.23 needed a bunch of new constants in the syscall package. That would have been avoided if we had used the native syscall package on MacOS. --- compileopts/target.go | 1 + compiler/compiler.go | 9 +- compiler/syscall.go | 62 ++++ lib/macos-minimal-sdk | 2 +- loader/goroot.go | 2 +- src/internal/abi/funcpc.go | 10 +- src/os/dir_darwin.go | 5 +- src/os/file_darwin.go | 7 + src/os/file_notdarwin.go | 9 + src/os/file_unix.go | 2 +- src/runtime/os_darwin.c | 29 +- src/runtime/os_darwin.go | 102 +++++- src/syscall/env_libc.go | 2 +- src/syscall/errno_other.go | 2 +- src/syscall/mmap_unix_test.go | 2 +- src/syscall/syscall_libc.go | 2 +- src/syscall/syscall_libc_darwin.go | 432 ----------------------- src/syscall/syscall_libc_darwin_amd64.go | 43 --- src/syscall/syscall_libc_darwin_arm64.go | 37 -- 19 files changed, 229 insertions(+), 531 deletions(-) create mode 100644 src/os/file_darwin.go create mode 100644 src/os/file_notdarwin.go delete mode 100644 src/syscall/syscall_libc_darwin.go delete mode 100644 src/syscall/syscall_libc_darwin_amd64.go delete mode 100644 src/syscall/syscall_libc_darwin_arm64.go diff --git a/compileopts/target.go b/compileopts/target.go index 26a91086b9..c394fa8ca8 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -389,6 +389,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) { "-platform_version", "macos", platformVersion, platformVersion, ) spec.ExtraFiles = append(spec.ExtraFiles, + "src/runtime/os_darwin.c", "src/runtime/runtime_unix.c") case "linux": spec.Linker = "ld.lld" diff --git a/compiler/compiler.go b/compiler/compiler.go index 201605d78c..6756fe9693 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1847,7 +1847,9 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) case strings.HasPrefix(name, "(device/riscv.CSR)."): return b.emitCSROperation(instr) case strings.HasPrefix(name, "syscall.Syscall") || strings.HasPrefix(name, "syscall.RawSyscall") || strings.HasPrefix(name, "golang.org/x/sys/unix.Syscall") || strings.HasPrefix(name, "golang.org/x/sys/unix.RawSyscall"): - return b.createSyscall(instr) + if b.GOOS != "darwin" { + return b.createSyscall(instr) + } case strings.HasPrefix(name, "syscall.rawSyscallNoError") || strings.HasPrefix(name, "golang.org/x/sys/unix.RawSyscallNoError"): return b.createRawSyscallNoError(instr) case name == "runtime.supportsRecover": @@ -1865,6 +1867,11 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) return llvm.ConstInt(b.ctx.Int8Type(), panicStrategy, false), nil case name == "runtime/interrupt.New": return b.createInterruptGlobal(instr) + case name == "internal/abi.FuncPCABI0": + retval := b.createDarwinFuncPCABI0Call(instr) + if !retval.IsNil() { + return retval, nil + } } calleeType, callee = b.getFunction(fn) diff --git a/compiler/syscall.go b/compiler/syscall.go index d878df0270..aa40ad1a55 100644 --- a/compiler/syscall.go +++ b/compiler/syscall.go @@ -5,6 +5,7 @@ package compiler import ( "strconv" + "strings" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" @@ -329,3 +330,64 @@ func (b *builder) createRawSyscallNoError(call *ssa.CallCommon) (llvm.Value, err retval = b.CreateInsertValue(retval, llvm.ConstInt(b.uintptrType, 0, false), 1, "") return retval, nil } + +// Lower a call to internal/abi.FuncPCABI0 on MacOS. +// This function is called like this: +// +// syscall(abi.FuncPCABI0(libc_mkdir_trampoline), uintptr(unsafe.Pointer(_p0)), uintptr(mode), 0) +// +// So we'll want to return a function pointer (as uintptr) that points to the +// libc function. Specifically, we _don't_ want to point to the trampoline +// function (which is implemented in Go assembly which we can't read), but +// rather to the actually intended function. For this we're going to assume that +// all the functions follow a specific pattern: libc__trampoline. +// +// The return value is the function pointer as an uintptr, or a nil value if +// this isn't possible (and a regular call should be made as fallback). +func (b *builder) createDarwinFuncPCABI0Call(instr *ssa.CallCommon) llvm.Value { + if b.GOOS != "darwin" { + // This has only been tested on MacOS (and only seems to be used there). + return llvm.Value{} + } + + // Check that it uses a function call like syscall.libc_*_trampoline + itf := instr.Args[0].(*ssa.MakeInterface) + calledFn := itf.X.(*ssa.Function) + if pkgName := calledFn.Pkg.Pkg.Path(); pkgName != "syscall" && pkgName != "internal/syscall/unix" { + return llvm.Value{} + } + if !strings.HasPrefix(calledFn.Name(), "libc_") || !strings.HasSuffix(calledFn.Name(), "_trampoline") { + + return llvm.Value{} + } + + // Extract the libc function name. + name := strings.TrimPrefix(strings.TrimSuffix(calledFn.Name(), "_trampoline"), "libc_") + if name == "open" { + // Special case: open() is a variadic function and can't be called like + // a regular function. Therefore, we need to use a wrapper implemented + // in C. + name = "syscall_libc_open" + } + if b.GOARCH == "amd64" { + if name == "fdopendir" || name == "readdir_r" { + // Hack to support amd64, which needs the $INODE64 suffix. + // This is also done in upstream Go: + // https://github.com/golang/go/commit/096ab3c21b88ccc7d411379d09fe6274e3159467 + name += "$INODE64" + } + } + + // Obtain the C function. + // Use a simple function (no parameters or return value) because all we need + // is the address of the function. + llvmFn := b.mod.NamedFunction(name) + if llvmFn.IsNil() { + llvmFnType := llvm.FunctionType(b.ctx.VoidType(), nil, false) + llvmFn = llvm.AddFunction(b.mod, name, llvmFnType) + } + + // Cast the function pointer to a uintptr (because that's what + // abi.FuncPCABI0 returns). + return b.CreatePtrToInt(llvmFn, b.uintptrType, "") +} diff --git a/lib/macos-minimal-sdk b/lib/macos-minimal-sdk index 91ac2eabd8..4e4113e3b1 160000 --- a/lib/macos-minimal-sdk +++ b/lib/macos-minimal-sdk @@ -1 +1 @@ -Subproject commit 91ac2eabd80f10d95cb4255c78999d9d2c45a3be +Subproject commit 4e4113e3b1244b8fdc5e1486577f25e22d63f36e diff --git a/loader/goroot.go b/loader/goroot.go index 09edab3203..c7ac029d3d 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -218,7 +218,7 @@ func listGorootMergeLinks(goroot, tinygoroot string, overrides map[string]bool) // with the TinyGo version. This is the case on some targets. func needsSyscallPackage(buildTags []string) bool { for _, tag := range buildTags { - if tag == "baremetal" || tag == "darwin" || tag == "nintendoswitch" || tag == "tinygo.wasm" { + if tag == "baremetal" || tag == "nintendoswitch" || tag == "tinygo.wasm" { return true } } diff --git a/src/internal/abi/funcpc.go b/src/internal/abi/funcpc.go index a926c392f8..0661ed7178 100644 --- a/src/internal/abi/funcpc.go +++ b/src/internal/abi/funcpc.go @@ -4,11 +4,9 @@ package abi // (in particular internal/syscall/unix on MacOS). They do not currently have an // implementation, in part because TinyGo doesn't use ABI0 or ABIInternal (it // uses a C-like calling convention). +// Calls to FuncPCABI0 however are treated specially by the compiler when +// compiling for MacOS. -func FuncPCABI0(f interface{}) uintptr { - panic("unimplemented: internal/abi.FuncPCABI0") -} +func FuncPCABI0(f interface{}) uintptr -func FuncPCABIInternal(f interface{}) uintptr { - panic("unimplemented: internal/abi.FuncPCABIInternal") -} +func FuncPCABIInternal(f interface{}) uintptr diff --git a/src/os/dir_darwin.go b/src/os/dir_darwin.go index 3883a45c6a..645c91f8c1 100644 --- a/src/os/dir_darwin.go +++ b/src/os/dir_darwin.go @@ -135,7 +135,7 @@ func darwinOpenDir(fd syscallFd) (uintptr, string, error) { } var dir uintptr for { - dir, err = syscall.Fdopendir(fd2) + dir, err = fdopendir(fd2) if err != syscall.EINTR { break } @@ -149,6 +149,9 @@ func darwinOpenDir(fd syscallFd) (uintptr, string, error) { // Implemented in syscall/syscall_libc_darwin_*.go. +//go:linkname fdopendir syscall.fdopendir +func fdopendir(fd int) (dir uintptr, err error) + //go:linkname closedir syscall.closedir func closedir(dir uintptr) (err error) diff --git a/src/os/file_darwin.go b/src/os/file_darwin.go new file mode 100644 index 0000000000..8d96b7296e --- /dev/null +++ b/src/os/file_darwin.go @@ -0,0 +1,7 @@ +package os + +import "syscall" + +func pipe(p []int) error { + return syscall.Pipe(p) +} diff --git a/src/os/file_notdarwin.go b/src/os/file_notdarwin.go new file mode 100644 index 0000000000..d59a8eb6c1 --- /dev/null +++ b/src/os/file_notdarwin.go @@ -0,0 +1,9 @@ +//go:build (linux && !baremetal && !wasm_unknown) || wasip1 || wasip2 + +package os + +import "syscall" + +func pipe(p []int) error { + return syscall.Pipe2(p, syscall.O_CLOEXEC) +} diff --git a/src/os/file_unix.go b/src/os/file_unix.go index d8d086e8dc..efbd8ef672 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -70,7 +70,7 @@ func Truncate(name string, size int64) error { func Pipe() (r *File, w *File, err error) { var p [2]int - err = handleSyscallError(syscall.Pipe2(p[:], syscall.O_CLOEXEC)) + err = handleSyscallError(pipe(p[:])) if err != nil { return } diff --git a/src/runtime/os_darwin.c b/src/runtime/os_darwin.c index d5f6c807a7..5d7cd7c71d 100644 --- a/src/runtime/os_darwin.c +++ b/src/runtime/os_darwin.c @@ -1,7 +1,32 @@ -// Wrapper function because 'open' is a variadic function and variadic functions -// use a different (incompatible) calling convention on darwin/arm64. +//go:build none + +// This file is included in the build, despite the //go:build line above. #include + +// Wrapper function because 'open' is a variadic function and variadic functions +// use a different (incompatible) calling convention on darwin/arm64. +// This function is referenced from the compiler, when it sees a +// syscall.libc_open_trampoline function. int syscall_libc_open(const char *pathname, int flags, mode_t mode) { return open(pathname, flags, mode); } + +// The following functions are called by the runtime because Go can't call +// function pointers directly. + +int tinygo_syscall(int (*fn)(uintptr_t a1, uintptr_t a2, uintptr_t a3), uintptr_t a1, uintptr_t a2, uintptr_t a3) { + return fn(a1, a2, a3); +} + +uintptr_t tinygo_syscallX(uintptr_t (*fn)(uintptr_t a1, uintptr_t a2, uintptr_t a3), uintptr_t a1, uintptr_t a2, uintptr_t a3) { + return fn(a1, a2, a3); +} + +int tinygo_syscall6(int (*fn)(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6), uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6) { + return fn(a1, a2, a3, a4, a5, a6); +} + +uintptr_t tinygo_syscall6X(uintptr_t (*fn)(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6), uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6) { + return fn(a1, a2, a3, a4, a5, a6); +} diff --git a/src/runtime/os_darwin.go b/src/runtime/os_darwin.go index 9255fb90f2..8338d0f18b 100644 --- a/src/runtime/os_darwin.go +++ b/src/runtime/os_darwin.go @@ -4,8 +4,6 @@ package runtime import "unsafe" -import "C" // dummy import so that os_darwin.c works - const GOOS = "darwin" const ( @@ -127,7 +125,107 @@ func hardwareRand() (n uint64, ok bool) { return n, true } +//go:linkname syscall_Getpagesize syscall.Getpagesize +func syscall_Getpagesize() int { + return int(libc_getpagesize()) +} + +// Call "system calls" (actually: libc functions) in a special way. +// - Most calls calls return a C int (which is 32-bits), and -1 on failure. +// - syscallX* is for functions that return a 64-bit integer (and also return +// -1 on failure). +// - syscallPtr is for functions that return a pointer on success or NULL on +// failure. +// - rawSyscall seems to avoid some stack modifications, which isn't relevant +// to TinyGo. + +//go:linkname syscall_syscall syscall.syscall +func syscall_syscall(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) { + // For TinyGo we don't need to do anything special to call C functions. + return syscall_rawSyscall(fn, a1, a2, a3) +} + +//go:linkname syscall_rawSyscall syscall.rawSyscall +func syscall_rawSyscall(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) { + result := call_syscall(fn, a1, a2, a3) + r1 = uintptr(result) + if result == -1 { + // Syscall returns -1 on failure. + err = uintptr(*libc___error()) + } + return +} + +//go:linkname syscall_syscallX syscall.syscallX +func syscall_syscallX(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) { + r1 = call_syscallX(fn, a1, a2, a3) + if int64(r1) == -1 { + // Syscall returns -1 on failure. + err = uintptr(*libc___error()) + } + return +} + +//go:linkname syscall_syscallPtr syscall.syscallPtr +func syscall_syscallPtr(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) { + r1 = call_syscallX(fn, a1, a2, a3) + if r1 == 0 { + // Syscall returns a pointer on success, or NULL on failure. + err = uintptr(*libc___error()) + } + return +} + +//go:linkname syscall_syscall6 syscall.syscall6 +func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) { + result := call_syscall6(fn, a1, a2, a3, a4, a5, a6) + r1 = uintptr(result) + if result == -1 { + // Syscall returns -1 on failure. + err = uintptr(*libc___error()) + } + return +} + +//go:linkname syscall_syscall6X syscall.syscall6X +func syscall_syscall6X(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) { + r1 = call_syscall6X(fn, a1, a2, a3, a4, a5, a6) + if int64(r1) == -1 { + // Syscall returns -1 on failure. + err = uintptr(*libc___error()) + } + return +} + // uint32_t arc4random(void); // //export arc4random func libc_arc4random() uint32 + +// int getpagesize(void); +// +//export getpagesize +func libc_getpagesize() int32 + +// This function returns the error location in the darwin ABI. +// Discovered by compiling the following code using Clang: +// +// #include +// int getErrno() { +// return errno; +// } +// +//export __error +func libc___error() *int32 + +//export tinygo_syscall +func call_syscall(fn, a1, a2, a3 uintptr) int32 + +//export tinygo_syscallX +func call_syscallX(fn, a1, a2, a3 uintptr) uintptr + +//export tinygo_syscall6 +func call_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) int32 + +//export tinygo_syscall6X +func call_syscall6X(fn, a1, a2, a3, a4, a5, a6 uintptr) uintptr diff --git a/src/syscall/env_libc.go b/src/syscall/env_libc.go index f558901cf4..4ad078dc54 100644 --- a/src/syscall/env_libc.go +++ b/src/syscall/env_libc.go @@ -1,4 +1,4 @@ -//go:build darwin || nintendoswitch || wasip1 +//go:build nintendoswitch || wasip1 package syscall diff --git a/src/syscall/errno_other.go b/src/syscall/errno_other.go index b988fbc1e0..3a06ac018f 100644 --- a/src/syscall/errno_other.go +++ b/src/syscall/errno_other.go @@ -1,4 +1,4 @@ -//go:build !wasip1 && !wasip2 && !darwin +//go:build !wasip1 && !wasip2 package syscall diff --git a/src/syscall/mmap_unix_test.go b/src/syscall/mmap_unix_test.go index 1946265d8c..62748a9395 100644 --- a/src/syscall/mmap_unix_test.go +++ b/src/syscall/mmap_unix_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build darwin || linux +//go:build linux package syscall_test diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index 2321292d98..67cf6681f7 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -1,4 +1,4 @@ -//go:build darwin || nintendoswitch || wasip1 || wasip2 +//go:build nintendoswitch || wasip1 || wasip2 package syscall diff --git a/src/syscall/syscall_libc_darwin.go b/src/syscall/syscall_libc_darwin.go deleted file mode 100644 index 3b0341880a..0000000000 --- a/src/syscall/syscall_libc_darwin.go +++ /dev/null @@ -1,432 +0,0 @@ -//go:build darwin - -package syscall - -import ( - "internal/itoa" - "unsafe" -) - -// This file defines errno and constants to match the darwin libsystem ABI. -// Values have been copied from src/syscall/zerrors_darwin_amd64.go. - -// This function returns the error location in the darwin ABI. -// Discovered by compiling the following code using Clang: -// -// #include -// int getErrno() { -// return errno; -// } -// -//export __error -func libc___error() *int32 - -// getErrno returns the current C errno. It may not have been caused by the last -// call, so it should only be relied upon when the last call indicates an error -// (for example, by returning -1). -func getErrno() Errno { - errptr := libc___error() - return Errno(uintptr(*errptr)) -} - -func (e Errno) Is(target error) bool { - switch target.Error() { - case "permission denied": - return e == EACCES || e == EPERM - case "file already exists": - return e == EEXIST - case "file does not exist": - return e == ENOENT - } - return false -} - -// Source: upstream zerrors_darwin_amd64.go -const ( - DT_BLK = 0x6 - DT_CHR = 0x2 - DT_DIR = 0x4 - DT_FIFO = 0x1 - DT_LNK = 0xa - DT_REG = 0x8 - DT_SOCK = 0xc - DT_UNKNOWN = 0x0 - DT_WHT = 0xe - F_GETFL = 0x3 - F_SETFL = 0x4 - O_NONBLOCK = 0x4 - TIOCSPGRP = 0x80047476 -) - -// Source: https://opensource.apple.com/source/xnu/xnu-7195.141.2/bsd/sys/errno.h.auto.html -const ( - EPERM Errno = 1 - ENOENT Errno = 2 - ESRCH Errno = 3 - EINTR Errno = 4 - EIO Errno = 5 - ENXIO Errno = 6 - E2BIG Errno = 7 - ENOEXEC Errno = 8 - EBADF Errno = 9 - ECHILD Errno = 10 - EDEADLK Errno = 11 - ENOMEM Errno = 12 - EACCES Errno = 13 - EFAULT Errno = 14 - ENOTBLK Errno = 15 - EBUSY Errno = 16 - EEXIST Errno = 17 - EXDEV Errno = 18 - ENODEV Errno = 19 - ENOTDIR Errno = 20 - EISDIR Errno = 21 - EINVAL Errno = 22 - ENFILE Errno = 23 - EMFILE Errno = 24 - ENOTTY Errno = 25 - ETXTBSY Errno = 26 - EFBIG Errno = 27 - ENOSPC Errno = 28 - ESPIPE Errno = 29 - EROFS Errno = 30 - EMLINK Errno = 31 - EPIPE Errno = 32 - EDOM Errno = 33 - ERANGE Errno = 34 - EAGAIN Errno = 35 - EWOULDBLOCK Errno = EAGAIN - EINPROGRESS Errno = 36 - EALREADY Errno = 37 - ENOTSOCK Errno = 38 - EDESTADDRREQ Errno = 39 - EMSGSIZE Errno = 40 - EPROTOTYPE Errno = 41 - ENOPROTOOPT Errno = 42 - EPROTONOSUPPORT Errno = 43 - ESOCKTNOSUPPORT Errno = 44 - ENOTSUP Errno = 45 - EPFNOSUPPORT Errno = 46 - EAFNOSUPPORT Errno = 47 - EADDRINUSE Errno = 48 - EADDRNOTAVAIL Errno = 49 - ENETDOWN Errno = 50 - ENETUNREACH Errno = 51 - ENETRESET Errno = 52 - ECONNABORTED Errno = 53 - ECONNRESET Errno = 54 - ENOBUFS Errno = 55 - EISCONN Errno = 56 - ENOTCONN Errno = 57 - ESHUTDOWN Errno = 58 - ETOOMANYREFS Errno = 59 - ETIMEDOUT Errno = 60 - ECONNREFUSED Errno = 61 - ELOOP Errno = 62 - ENAMETOOLONG Errno = 63 - EHOSTDOWN Errno = 64 - EHOSTUNREACH Errno = 65 - ENOTEMPTY Errno = 66 - EPROCLIM Errno = 67 - EUSERS Errno = 68 - EDQUOT Errno = 69 - ESTALE Errno = 70 - EREMOTE Errno = 71 - EBADRPC Errno = 72 - ERPCMISMATCH Errno = 73 - EPROGUNAVAIL Errno = 74 - EPROGMISMATCH Errno = 75 - EPROCUNAVAIL Errno = 76 - ENOLCK Errno = 77 - ENOSYS Errno = 78 - EFTYPE Errno = 79 - EAUTH Errno = 80 - ENEEDAUTH Errno = 81 - EPWROFF Errno = 82 - EDEVERR Errno = 83 - EOVERFLOW Errno = 84 - EBADEXEC Errno = 85 - EBADARCH Errno = 86 - ESHLIBVERS Errno = 87 - EBADMACHO Errno = 88 - ECANCELED Errno = 89 - EIDRM Errno = 90 - ENOMSG Errno = 91 - EILSEQ Errno = 92 - ENOATTR Errno = 93 - EBADMSG Errno = 94 - EMULTIHOP Errno = 95 - ENODATA Errno = 96 - ENOLINK Errno = 97 - ENOSR Errno = 98 - ENOSTR Errno = 99 - EPROTO Errno = 100 - ETIME Errno = 101 - EOPNOTSUPP Errno = 102 - ENOPOLICY Errno = 103 - ENOTRECOVERABLE Errno = 104 - EOWNERDEAD Errno = 105 - EQFULL Errno = 106 - ELAST Errno = 106 -) - -type Signal int - -// Source: https://opensource.apple.com/source/xnu/xnu-7195.81.3/bsd/sys/signal.h -const ( - SIGINT Signal = 2 /* interrupt */ - SIGQUIT Signal = 3 /* quit */ - SIGILL Signal = 4 /* illegal instruction (not reset when caught) */ - SIGTRAP Signal = 5 /* trace trap (not reset when caught) */ - SIGABRT Signal = 6 /* abort() */ - SIGFPE Signal = 8 /* floating point exception */ - SIGKILL Signal = 9 /* kill (cannot be caught or ignored) */ - SIGBUS Signal = 10 /* bus error */ - SIGSEGV Signal = 11 /* segmentation violation */ - SIGPIPE Signal = 13 /* write on a pipe with no one to read it */ - SIGTERM Signal = 15 /* software termination signal from kill */ - SIGCHLD Signal = 20 /* to parent on child stop or exit */ -) - -func (s Signal) Signal() {} - -func (s Signal) String() string { - if 0 <= s && int(s) < len(signals) { - str := signals[s] - if str != "" { - return str - } - } - return "signal " + itoa.Itoa(int(s)) -} - -var signals = [...]string{} - -const ( - Stdin = 0 - Stdout = 1 - Stderr = 2 -) - -const ( - O_RDONLY = 0x0 - O_WRONLY = 0x1 - O_RDWR = 0x2 - O_APPEND = 0x8 - O_SYNC = 0x80 - O_CREAT = 0x200 - O_TRUNC = 0x400 - O_EXCL = 0x800 - - O_CLOEXEC = 0x01000000 -) - -// Source: https://opensource.apple.com/source/xnu/xnu-7195.81.3/bsd/sys/mman.h.auto.html -const ( - PROT_NONE = 0x00 // no permissions - PROT_READ = 0x01 // pages can be read - PROT_WRITE = 0x02 // pages can be written - PROT_EXEC = 0x04 // pages can be executed - - MAP_SHARED = 0x0001 // share changes - MAP_PRIVATE = 0x0002 // changes are private - - MAP_FILE = 0x0000 // map from file (default) - MAP_ANON = 0x1000 // allocated from memory, swap space - MAP_ANONYMOUS = MAP_ANON -) - -type Timespec struct { - Sec int64 - Nsec int64 -} - -// Unix returns the time stored in ts as seconds plus nanoseconds. -func (ts *Timespec) Unix() (sec int64, nsec int64) { - return int64(ts.Sec), int64(ts.Nsec) -} - -// Source: upstream ztypes_darwin_amd64.go -type Dirent struct { - Ino uint64 - Seekoff uint64 - Reclen uint16 - Namlen uint16 - Type uint8 - Name [1024]int8 - Pad_cgo_0 [3]byte -} - -type Stat_t struct { - Dev int32 - Mode uint16 - Nlink uint16 - Ino uint64 - Uid uint32 - Gid uint32 - Rdev int32 - Pad_cgo_0 [4]byte - Atimespec Timespec - Mtimespec Timespec - Ctimespec Timespec - Btimespec Timespec - Size int64 - Blocks int64 - Blksize int32 - Flags uint32 - Gen uint32 - Lspare int32 - Qspare [2]int64 -} - -// Source: https://github.com/apple/darwin-xnu/blob/main/bsd/sys/_types/_s_ifmt.h -const ( - S_IEXEC = 0x40 - S_IFBLK = 0x6000 - S_IFCHR = 0x2000 - S_IFDIR = 0x4000 - S_IFIFO = 0x1000 - S_IFLNK = 0xa000 - S_IFMT = 0xf000 - S_IFREG = 0x8000 - S_IFSOCK = 0xc000 - S_IFWHT = 0xe000 - S_IREAD = 0x100 - S_IRGRP = 0x20 - S_IROTH = 0x4 - S_IRUSR = 0x100 - S_IRWXG = 0x38 - S_IRWXO = 0x7 - S_IRWXU = 0x1c0 - S_ISGID = 0x400 - S_ISTXT = 0x200 - S_ISUID = 0x800 - S_ISVTX = 0x200 - S_IWGRP = 0x10 - S_IWOTH = 0x2 - S_IWRITE = 0x80 - S_IWUSR = 0x80 - S_IXGRP = 0x8 - S_IXOTH = 0x1 - S_IXUSR = 0x40 -) - -func Stat(path string, p *Stat_t) (err error) { - data := cstring(path) - n := libc_stat(&data[0], unsafe.Pointer(p)) - - if n < 0 { - err = getErrno() - } - return -} - -func Fstat(fd int, p *Stat_t) (err error) { - n := libc_fstat(int32(fd), unsafe.Pointer(p)) - - if n < 0 { - err = getErrno() - } - return -} - -func Lstat(path string, p *Stat_t) (err error) { - data := cstring(path) - n := libc_lstat(&data[0], unsafe.Pointer(p)) - if n < 0 { - err = getErrno() - } - return -} - -func Fdopendir(fd int) (dir uintptr, err error) { - r0 := libc_fdopendir(int32(fd)) - dir = uintptr(r0) - if dir == 0 { - err = getErrno() - } - return -} - -func Pipe2(fds []int, flags int) (err error) { - // Mac only has Pipe, which ignores the flags argument - buf := make([]int32, 2) - fail := int(libc_pipe(&buf[0])) - if fail < 0 { - err = getErrno() - } else { - fds[0] = int(buf[0]) - fds[1] = int(buf[1]) - } - return -} - -func Chmod(path string, mode uint32) (err error) { - data := cstring(path) - fail := int(libc_chmod(&data[0], mode)) - if fail < 0 { - err = getErrno() - } - return -} - -func closedir(dir uintptr) (err error) { - e := libc_closedir(unsafe.Pointer(dir)) - if e != 0 { - err = getErrno() - } - return -} - -func readdir_r(dir uintptr, entry *Dirent, result **Dirent) (err error) { - e1 := libc_readdir_r(unsafe.Pointer(dir), unsafe.Pointer(entry), unsafe.Pointer(result)) - if e1 != 0 { - err = getErrno() - } - return -} - -func Getpagesize() int { - return int(libc_getpagesize()) -} - -// The following RawSockAddr* types have been copied from the Go source tree and -// are here purely to fix build errors. - -type RawSockaddr struct { - Len uint8 - Family uint8 - Data [14]int8 -} - -type RawSockaddrInet4 struct { - Len uint8 - Family uint8 - Port uint16 - Addr [4]byte /* in_addr */ - Zero [8]int8 -} - -type RawSockaddrInet6 struct { - Len uint8 - Family uint8 - Port uint16 - Flowinfo uint32 - Addr [16]byte /* in6_addr */ - Scope_id uint32 -} - -// int pipe(int32 *fds); -// -//export pipe -func libc_pipe(fds *int32) int32 - -// int getpagesize(); -// -//export getpagesize -func libc_getpagesize() int32 - -// int open(const char *pathname, int flags, mode_t mode); -// -//export syscall_libc_open -func libc_open(pathname *byte, flags int32, mode uint32) int32 diff --git a/src/syscall/syscall_libc_darwin_amd64.go b/src/syscall/syscall_libc_darwin_amd64.go deleted file mode 100644 index 1f5528ec54..0000000000 --- a/src/syscall/syscall_libc_darwin_amd64.go +++ /dev/null @@ -1,43 +0,0 @@ -//go:build darwin - -package syscall - -import ( - "unsafe" -) - -// The odd $INODE64 suffix is an Apple compatibility feature, see -// __DARWIN_INODE64 in /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sys/cdefs.h -// and https://assert.cc/posts/darwin_use_64_bit_inode_vs_ctypes/ -// Without it, you get the old, smaller struct stat from mac os 10.2 or so. -// It not needed on arm64. - -// struct DIR * buf fdopendir(int fd); -// -//export fdopendir$INODE64 -func libc_fdopendir(fd int32) unsafe.Pointer - -// int closedir(struct DIR * buf); -// -//export closedir -func libc_closedir(unsafe.Pointer) int32 - -// int readdir_r(struct DIR * buf, struct dirent *entry, struct dirent **result); -// -//export readdir_r$INODE64 -func libc_readdir_r(unsafe.Pointer, unsafe.Pointer, unsafe.Pointer) int32 - -// int stat(const char *path, struct stat * buf); -// -//export stat$INODE64 -func libc_stat(pathname *byte, ptr unsafe.Pointer) int32 - -// int fstat(int fd, struct stat * buf); -// -//export fstat$INODE64 -func libc_fstat(fd int32, ptr unsafe.Pointer) int32 - -// int lstat(const char *path, struct stat * buf); -// -//export lstat$INODE64 -func libc_lstat(pathname *byte, ptr unsafe.Pointer) int32 diff --git a/src/syscall/syscall_libc_darwin_arm64.go b/src/syscall/syscall_libc_darwin_arm64.go deleted file mode 100644 index f9ce3c4e39..0000000000 --- a/src/syscall/syscall_libc_darwin_arm64.go +++ /dev/null @@ -1,37 +0,0 @@ -//go:build darwin - -package syscall - -import ( - "unsafe" -) - -// struct DIR * buf fdopendir(int fd); -// -//export fdopendir -func libc_fdopendir(fd int32) unsafe.Pointer - -// int closedir(struct DIR * buf); -// -//export closedir -func libc_closedir(unsafe.Pointer) int32 - -// int readdir_r(struct DIR * buf, struct dirent *entry, struct dirent **result); -// -//export readdir_r -func libc_readdir_r(unsafe.Pointer, unsafe.Pointer, unsafe.Pointer) int32 - -// int stat(const char *path, struct stat * buf); -// -//export stat -func libc_stat(pathname *byte, ptr unsafe.Pointer) int32 - -// int fstat(int fd, struct stat * buf); -// -//export fstat -func libc_fstat(fd int32, ptr unsafe.Pointer) int32 - -// int lstat(const char *path, struct stat * buf); -// -//export lstat -func libc_lstat(pathname *byte, ptr unsafe.Pointer) int32 From e39358d0ee9d08fad1a838ecb4ea9322d4f9bbfa Mon Sep 17 00:00:00 2001 From: Roger Standridge <9526806+archie2x@users.noreply.github.com> Date: Wed, 21 Aug 2024 08:32:23 -0700 Subject: [PATCH 172/444] stub runtime_{Before,After}Exec for linkage --- src/runtime/proc.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/runtime/proc.go diff --git a/src/runtime/proc.go b/src/runtime/proc.go new file mode 100644 index 0000000000..e029ffe95f --- /dev/null +++ b/src/runtime/proc.go @@ -0,0 +1,19 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package runtime + +// Called from syscall package before Exec. +// +//go:linkname syscall_runtime_BeforeExec syscall.runtime_BeforeExec +func syscall_runtime_BeforeExec() { + // Used in BigGo to serialize exec / thread creation. Stubbing to + // satisfy link. +} + +// Called from syscall package after Exec. +// +//go:linkname syscall_runtime_AfterExec syscall.runtime_AfterExec +func syscall_runtime_AfterExec() { +} From 25abfff63204f72140ab1b29120ab9b674d5cf41 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 22 Aug 2024 15:53:22 +0200 Subject: [PATCH 173/444] mips: use MIPS32 (instead of MIPS32R2) as the instruction set This should widen compatibility a bit, so that older CPUs can also execute programs built by TinyGo. The performance may be lower, if that's an issue we can look into implementing the proposal here: https://github.com/golang/go/issues/60072 This still wouldn't make programs usable on MIPS II CPUs, I suppose we can lower compatiblity down to that CPU if needed. I tried setting the -cpu flag in the QEMU command line to be able to test this, but it looks like there are no QEMU CPU models that are mips32r1 and have a FPU. So it's difficult to test this. --- compileopts/target.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compileopts/target.go b/compileopts/target.go index c394fa8ca8..41a7babd91 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -336,7 +336,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) { spec.Features = "+fp-armv8,+neon,-fmv,-outline-atomics" } case "mips", "mipsle": - spec.CPU = "mips32r2" + spec.CPU = "mips32" spec.CFlags = append(spec.CFlags, "-fno-pic") if options.GOARCH == "mips" { llvmarch = "mips" // big endian @@ -345,10 +345,10 @@ func defaultTarget(options *Options) (*TargetSpec, error) { } switch options.GOMIPS { case "hardfloat": - spec.Features = "+fpxx,+mips32r2,+nooddspreg,-noabicalls" + spec.Features = "+fpxx,+mips32,+nooddspreg,-noabicalls" case "softfloat": spec.SoftFloat = true - spec.Features = "+mips32r2,+soft-float,-noabicalls" + spec.Features = "+mips32,+soft-float,-noabicalls" spec.CFlags = append(spec.CFlags, "-msoft-float") default: return nil, fmt.Errorf("invalid GOMIPS=%s: must be hardfloat or softfloat", options.GOMIPS) From 73f519b589bf45d8b9a121063bd264357f632c06 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 22 Aug 2024 16:42:03 +0200 Subject: [PATCH 174/444] interp: support big-endian targets The interp package was assuming that all targets were little-endian. But that's not true: we now have a big-endian target (GOARCH=mips). This fixes the interp package to use the appropriate byte order for a given target. --- compiler/llvmutil/llvm.go | 11 +++++ interp/interp.go | 4 ++ interp/interpreter.go | 98 +++++++++++++++++++-------------------- interp/memory.go | 76 ++++++++++++++++-------------- main_test.go | 6 ++- 5 files changed, 110 insertions(+), 85 deletions(-) diff --git a/compiler/llvmutil/llvm.go b/compiler/llvmutil/llvm.go index 607e91e8d8..061bee6c9a 100644 --- a/compiler/llvmutil/llvm.go +++ b/compiler/llvmutil/llvm.go @@ -8,6 +8,7 @@ package llvmutil import ( + "encoding/binary" "strconv" "strings" @@ -216,3 +217,13 @@ func Version() int { } return major } + +// Return the byte order for the given target triple. Most targets are little +// endian, but for example MIPS can be big-endian. +func ByteOrder(target string) binary.ByteOrder { + if strings.HasPrefix(target, "mips-") { + return binary.BigEndian + } else { + return binary.LittleEndian + } +} diff --git a/interp/interp.go b/interp/interp.go index 80afc39c74..30b0872485 100644 --- a/interp/interp.go +++ b/interp/interp.go @@ -3,11 +3,13 @@ package interp import ( + "encoding/binary" "fmt" "os" "strings" "time" + "github.com/tinygo-org/tinygo/compiler/llvmutil" "tinygo.org/x/go-llvm" ) @@ -24,6 +26,7 @@ type runner struct { dataPtrType llvm.Type // often used type so created in advance uintptrType llvm.Type // equivalent to uintptr in Go maxAlign int // maximum alignment of an object, alignment of runtime.alloc() result + byteOrder binary.ByteOrder // big-endian or little-endian debug bool // log debug messages pkgName string // package name of the currently executing package functionCache map[llvm.Value]*function // cache of compiled functions @@ -38,6 +41,7 @@ func newRunner(mod llvm.Module, timeout time.Duration, debug bool) *runner { r := runner{ mod: mod, targetData: llvm.NewTargetData(mod.DataLayout()), + byteOrder: llvmutil.ByteOrder(mod.Target()), debug: debug, functionCache: make(map[llvm.Value]*function), objects: []object{{}}, diff --git a/interp/interpreter.go b/interp/interpreter.go index b35129b814..512d93eb74 100644 --- a/interp/interpreter.go +++ b/interp/interpreter.go @@ -173,7 +173,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent case 3: // Conditional branch: [cond, thenBB, elseBB] lastBB = currentBB - switch operands[0].Uint() { + switch operands[0].Uint(r) { case 1: // true -> thenBB currentBB = int(operands[1].(literalValue).value.(uint32)) case 0: // false -> elseBB @@ -191,12 +191,12 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent } case llvm.Switch: // Switch statement: [value, defaultLabel, case0, label0, case1, label1, ...] - value := operands[0].Uint() - targetLabel := operands[1].Uint() // default label + value := operands[0].Uint(r) + targetLabel := operands[1].Uint(r) // default label // Do a lazy switch by iterating over all cases. for i := 2; i < len(operands); i += 2 { - if value == operands[i].Uint() { - targetLabel = operands[i+1].Uint() + if value == operands[i].Uint(r) { + targetLabel = operands[i+1].Uint(r) break } } @@ -211,7 +211,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // Select is much like a ternary operator: it picks a result from // the second and third operand based on the boolean first operand. var result value - switch operands[0].Uint() { + switch operands[0].Uint(r) { case 1: result = operands[1] case 0: @@ -282,7 +282,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // by creating a global variable. // Get the requested memory size to be allocated. - size := operands[1].Uint() + size := operands[1].Uint(r) // Get the object layout, if it is available. llvmLayoutType := r.getLLVMTypeFromLayout(operands[2]) @@ -318,9 +318,9 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // memmove(dst, src, n*elemSize) // return int(n) // } - dstLen := operands[3].Uint() - srcLen := operands[4].Uint() - elemSize := operands[5].Uint() + dstLen := operands[3].Uint(r) + srcLen := operands[4].Uint(r) + elemSize := operands[5].Uint(r) n := srcLen if n > dstLen { n = dstLen @@ -374,7 +374,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent if err != nil { return nil, mem, r.errorAt(inst, err) } - nBytes := uint32(operands[3].Uint()) + nBytes := uint32(operands[3].Uint(r)) dstObj := mem.getWritable(dst.index()) dstBuf := dstObj.buffer.asRawValue(r) if mem.get(src.index()).buffer == nil { @@ -661,8 +661,8 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // pointer into the underlying object. var offset int64 for i := 1; i < len(operands); i += 2 { - index := operands[i].Int() - elementSize := operands[i+1].Int() + index := operands[i].Int(r) + elementSize := operands[i+1].Int(r) if elementSize < 0 { // This is a struct field. offset += index @@ -677,7 +677,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent return nil, mem, r.errorAt(inst, err) } // GEP on fixed pointer value (for example, memory-mapped I/O). - ptrValue := operands[0].Uint() + uint64(offset) + ptrValue := operands[0].Uint(r) + uint64(offset) locals[inst.localIndex] = makeLiteralInt(ptrValue, int(operands[0].len(r)*8)) continue } @@ -739,11 +739,11 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent var lhs, rhs float64 switch operands[0].len(r) { case 8: - lhs = math.Float64frombits(operands[0].Uint()) - rhs = math.Float64frombits(operands[1].Uint()) + lhs = math.Float64frombits(operands[0].Uint(r)) + rhs = math.Float64frombits(operands[1].Uint(r)) case 4: - lhs = float64(math.Float32frombits(uint32(operands[0].Uint()))) - rhs = float64(math.Float32frombits(uint32(operands[1].Uint()))) + lhs = float64(math.Float32frombits(uint32(operands[0].Uint(r)))) + rhs = float64(math.Float32frombits(uint32(operands[1].Uint(r)))) default: panic("unknown float type") } @@ -782,23 +782,23 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent if inst.opcode == llvm.Add { // This likely means this is part of a // unsafe.Pointer(uintptr(ptr) + offset) pattern. - lhsPtr, err = lhsPtr.addOffset(int64(rhs.Uint())) + lhsPtr, err = lhsPtr.addOffset(int64(rhs.Uint(r))) if err != nil { return nil, mem, r.errorAt(inst, err) } locals[inst.localIndex] = lhsPtr - } else if inst.opcode == llvm.Xor && rhs.Uint() == 0 { + } else if inst.opcode == llvm.Xor && rhs.Uint(r) == 0 { // Special workaround for strings.noescape, see // src/strings/builder.go in the Go source tree. This is // the identity operator, so we can return the input. locals[inst.localIndex] = lhs - } else if inst.opcode == llvm.And && rhs.Uint() < 8 { + } else if inst.opcode == llvm.And && rhs.Uint(r) < 8 { // This is probably part of a pattern to get the lower bits // of a pointer for pointer tagging, like this: // uintptr(unsafe.Pointer(t)) & 0b11 // We can actually support this easily by ANDing with the // pointer offset. - result := uint64(lhsPtr.offset()) & rhs.Uint() + result := uint64(lhsPtr.offset()) & rhs.Uint(r) locals[inst.localIndex] = makeLiteralInt(result, int(lhs.len(r)*8)) } else { // Catch-all for weird operations that should just be done @@ -813,31 +813,31 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent var result uint64 switch inst.opcode { case llvm.Add: - result = lhs.Uint() + rhs.Uint() + result = lhs.Uint(r) + rhs.Uint(r) case llvm.Sub: - result = lhs.Uint() - rhs.Uint() + result = lhs.Uint(r) - rhs.Uint(r) case llvm.Mul: - result = lhs.Uint() * rhs.Uint() + result = lhs.Uint(r) * rhs.Uint(r) case llvm.UDiv: - result = lhs.Uint() / rhs.Uint() + result = lhs.Uint(r) / rhs.Uint(r) case llvm.SDiv: - result = uint64(lhs.Int() / rhs.Int()) + result = uint64(lhs.Int(r) / rhs.Int(r)) case llvm.URem: - result = lhs.Uint() % rhs.Uint() + result = lhs.Uint(r) % rhs.Uint(r) case llvm.SRem: - result = uint64(lhs.Int() % rhs.Int()) + result = uint64(lhs.Int(r) % rhs.Int(r)) case llvm.Shl: - result = lhs.Uint() << rhs.Uint() + result = lhs.Uint(r) << rhs.Uint(r) case llvm.LShr: - result = lhs.Uint() >> rhs.Uint() + result = lhs.Uint(r) >> rhs.Uint(r) case llvm.AShr: - result = uint64(lhs.Int() >> rhs.Uint()) + result = uint64(lhs.Int(r) >> rhs.Uint(r)) case llvm.And: - result = lhs.Uint() & rhs.Uint() + result = lhs.Uint(r) & rhs.Uint(r) case llvm.Or: - result = lhs.Uint() | rhs.Uint() + result = lhs.Uint(r) | rhs.Uint(r) case llvm.Xor: - result = lhs.Uint() ^ rhs.Uint() + result = lhs.Uint(r) ^ rhs.Uint(r) default: panic("unreachable") } @@ -855,11 +855,11 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // and then truncating it as necessary. var value uint64 if inst.opcode == llvm.SExt { - value = uint64(operands[0].Int()) + value = uint64(operands[0].Int(r)) } else { - value = operands[0].Uint() + value = operands[0].Uint(r) } - bitwidth := operands[1].Uint() + bitwidth := operands[1].Uint(r) if r.debug { fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", value, bitwidth) } @@ -868,11 +868,11 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent var value float64 switch inst.opcode { case llvm.SIToFP: - value = float64(operands[0].Int()) + value = float64(operands[0].Int(r)) case llvm.UIToFP: - value = float64(operands[0].Uint()) + value = float64(operands[0].Uint(r)) } - bitwidth := operands[1].Uint() + bitwidth := operands[1].Uint(r) if r.debug { fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", value, bitwidth) } @@ -918,21 +918,21 @@ func (r *runner) interpretICmp(lhs, rhs value, predicate llvm.IntPredicate) bool } return result case llvm.IntUGT: - return lhs.Uint() > rhs.Uint() + return lhs.Uint(r) > rhs.Uint(r) case llvm.IntUGE: - return lhs.Uint() >= rhs.Uint() + return lhs.Uint(r) >= rhs.Uint(r) case llvm.IntULT: - return lhs.Uint() < rhs.Uint() + return lhs.Uint(r) < rhs.Uint(r) case llvm.IntULE: - return lhs.Uint() <= rhs.Uint() + return lhs.Uint(r) <= rhs.Uint(r) case llvm.IntSGT: - return lhs.Int() > rhs.Int() + return lhs.Int(r) > rhs.Int(r) case llvm.IntSGE: - return lhs.Int() >= rhs.Int() + return lhs.Int(r) >= rhs.Int(r) case llvm.IntSLT: - return lhs.Int() < rhs.Int() + return lhs.Int(r) < rhs.Int(r) case llvm.IntSLE: - return lhs.Int() <= rhs.Int() + return lhs.Int(r) <= rhs.Int(r) default: // _should_ be unreachable, until LLVM adds new icmp operands (unlikely) panic("interp: unsupported icmp") diff --git a/interp/memory.go b/interp/memory.go index 759a1ffe48..176228fdc1 100644 --- a/interp/memory.go +++ b/interp/memory.go @@ -361,8 +361,8 @@ type value interface { clone() value asPointer(*runner) (pointerValue, error) asRawValue(*runner) rawValue - Uint() uint64 - Int() int64 + Uint(*runner) uint64 + Int(*runner) int64 toLLVMValue(llvm.Type, *memoryView) (llvm.Value, error) String() string } @@ -405,7 +405,8 @@ func (v literalValue) len(r *runner) uint32 { } func (v literalValue) String() string { - return strconv.FormatInt(v.Int(), 10) + // Note: passing a nil *runner to v.Int because we know it won't use it. + return strconv.FormatInt(v.Int(nil), 10) } func (v literalValue) clone() value { @@ -421,13 +422,13 @@ func (v literalValue) asRawValue(r *runner) rawValue { switch value := v.value.(type) { case uint64: buf = make([]byte, 8) - binary.LittleEndian.PutUint64(buf, value) + r.byteOrder.PutUint64(buf, value) case uint32: buf = make([]byte, 4) - binary.LittleEndian.PutUint32(buf, uint32(value)) + r.byteOrder.PutUint32(buf, uint32(value)) case uint16: buf = make([]byte, 2) - binary.LittleEndian.PutUint16(buf, uint16(value)) + r.byteOrder.PutUint16(buf, uint16(value)) case uint8: buf = []byte{uint8(value)} default: @@ -440,7 +441,7 @@ func (v literalValue) asRawValue(r *runner) rawValue { return raw } -func (v literalValue) Uint() uint64 { +func (v literalValue) Uint(r *runner) uint64 { switch value := v.value.(type) { case uint64: return value @@ -455,7 +456,7 @@ func (v literalValue) Uint() uint64 { } } -func (v literalValue) Int() int64 { +func (v literalValue) Int(r *runner) int64 { switch value := v.value.(type) { case uint64: return int64(value) @@ -553,11 +554,11 @@ func (v pointerValue) asRawValue(r *runner) rawValue { return rv } -func (v pointerValue) Uint() uint64 { +func (v pointerValue) Uint(r *runner) uint64 { panic("cannot convert pointer to integer") } -func (v pointerValue) Int() int64 { +func (v pointerValue) Int(r *runner) int64 { panic("cannot convert pointer to integer") } @@ -702,7 +703,12 @@ func (v rawValue) String() string { } // Format as number if none of the buf is a pointer. if !v.hasPointer() { - return strconv.FormatInt(v.Int(), 10) + // Construct a fake runner, which is little endian. + // We only use String() for debugging, so this is is good enough + // (the printed value will just be slightly wrong when debugging the + // interp package with GOOS=mips for example). + r := &runner{byteOrder: binary.LittleEndian} + return strconv.FormatInt(v.Int(r), 10) } } return "<[…" + strconv.Itoa(len(v.buf)) + "]>" @@ -738,33 +744,33 @@ func (v rawValue) bytes() []byte { return buf } -func (v rawValue) Uint() uint64 { +func (v rawValue) Uint(r *runner) uint64 { buf := v.bytes() switch len(v.buf) { case 1: return uint64(buf[0]) case 2: - return uint64(binary.LittleEndian.Uint16(buf)) + return uint64(r.byteOrder.Uint16(buf)) case 4: - return uint64(binary.LittleEndian.Uint32(buf)) + return uint64(r.byteOrder.Uint32(buf)) case 8: - return binary.LittleEndian.Uint64(buf) + return r.byteOrder.Uint64(buf) default: panic("unknown integer size") } } -func (v rawValue) Int() int64 { +func (v rawValue) Int(r *runner) int64 { switch len(v.buf) { case 1: - return int64(int8(v.Uint())) + return int64(int8(v.Uint(r))) case 2: - return int64(int16(v.Uint())) + return int64(int16(v.Uint(r))) case 4: - return int64(int32(v.Uint())) + return int64(int32(v.Uint(r))) case 8: - return int64(int64(v.Uint())) + return int64(int64(v.Uint(r))) default: panic("unknown integer size") } @@ -878,11 +884,11 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, var n uint64 switch llvmType.IntTypeWidth() { case 64: - n = rawValue{v.buf[:8]}.Uint() + n = rawValue{v.buf[:8]}.Uint(mem.r) case 32: - n = rawValue{v.buf[:4]}.Uint() + n = rawValue{v.buf[:4]}.Uint(mem.r) case 16: - n = rawValue{v.buf[:2]}.Uint() + n = rawValue{v.buf[:2]}.Uint(mem.r) case 8: n = uint64(v.buf[0]) case 1: @@ -951,7 +957,7 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, } // This is either a null pointer or a raw pointer for memory-mapped I/O // (such as 0xe000ed00). - ptr := rawValue{v.buf[:mem.r.pointerSize]}.Uint() + ptr := rawValue{v.buf[:mem.r.pointerSize]}.Uint(mem.r) if ptr == 0 { // Null pointer. return llvm.ConstNull(llvmType), nil @@ -969,11 +975,11 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, } return llvm.ConstIntToPtr(ptrValue, llvmType), nil case llvm.DoubleTypeKind: - b := rawValue{v.buf[:8]}.Uint() + b := rawValue{v.buf[:8]}.Uint(mem.r) f := math.Float64frombits(b) return llvm.ConstFloat(llvmType, f), nil case llvm.FloatTypeKind: - b := uint32(rawValue{v.buf[:4]}.Uint()) + b := uint32(rawValue{v.buf[:4]}.Uint(mem.r)) f := math.Float32frombits(b) return llvm.ConstFloat(llvmType, float64(f)), nil default: @@ -1065,19 +1071,19 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) { switch llvmValue.Type().IntTypeWidth() { case 64: var buf [8]byte - binary.LittleEndian.PutUint64(buf[:], n) + r.byteOrder.PutUint64(buf[:], n) for i, b := range buf { v.buf[i] = uint64(b) } case 32: var buf [4]byte - binary.LittleEndian.PutUint32(buf[:], uint32(n)) + r.byteOrder.PutUint32(buf[:], uint32(n)) for i, b := range buf { v.buf[i] = uint64(b) } case 16: var buf [2]byte - binary.LittleEndian.PutUint16(buf[:], uint16(n)) + r.byteOrder.PutUint16(buf[:], uint16(n)) for i, b := range buf { v.buf[i] = uint64(b) } @@ -1109,14 +1115,14 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) { case llvm.DoubleTypeKind: f, _ := llvmValue.DoubleValue() var buf [8]byte - binary.LittleEndian.PutUint64(buf[:], math.Float64bits(f)) + r.byteOrder.PutUint64(buf[:], math.Float64bits(f)) for i, b := range buf { v.buf[i] = uint64(b) } case llvm.FloatTypeKind: f, _ := llvmValue.DoubleValue() var buf [4]byte - binary.LittleEndian.PutUint32(buf[:], math.Float32bits(float32(f))) + r.byteOrder.PutUint32(buf[:], math.Float32bits(float32(f))) for i, b := range buf { v.buf[i] = uint64(b) } @@ -1166,11 +1172,11 @@ func (v localValue) asRawValue(r *runner) rawValue { panic("interp: localValue.asRawValue") } -func (v localValue) Uint() uint64 { +func (v localValue) Uint(r *runner) uint64 { panic("interp: localValue.Uint") } -func (v localValue) Int() int64 { +func (v localValue) Int(r *runner) int64 { panic("interp: localValue.Int") } @@ -1254,7 +1260,7 @@ func (r *runner) readObjectLayout(layoutValue value) (uint64, *big.Int) { ptr, err := layoutValue.asPointer(r) if err == errIntegerAsPointer { // It's an integer, which means it's a small object or unknown. - layout := layoutValue.Uint() + layout := layoutValue.Uint(r) if layout == 0 { // Nil pointer, which means the layout is unknown. return 0, nil @@ -1287,7 +1293,7 @@ func (r *runner) readObjectLayout(layoutValue value) (uint64, *big.Int) { // Read the object size in words and the bitmap from the global. buf := r.objects[ptr.index()].buffer.(rawValue) - objectSizeWords := rawValue{buf: buf.buf[:r.pointerSize]}.Uint() + objectSizeWords := rawValue{buf: buf.buf[:r.pointerSize]}.Uint(r) rawByteValues := buf.buf[r.pointerSize:] rawBytes := make([]byte, len(rawByteValues)) for i, v := range rawByteValues { diff --git a/main_test.go b/main_test.go index 40002ee871..80c8a46cd1 100644 --- a/main_test.go +++ b/main_test.go @@ -181,9 +181,13 @@ func TestBuild(t *testing.T) { // Run a single test for GOARCH=mips to see whether it works at all. // Big-endian MIPS isn't fully supported yet, but simple examples // should work. + // Once big-endian is fully supported, we can probably flip this + // around and do full testing of MIPS big-endian support and only do + // limited testing of MIPS little-endian (because the two are some + // similar). t.Parallel() options := optionsFromOSARCH("linux/mips/softfloat", sema) - runTest("alias.go", options, t, nil, nil) + runTest("map.go", options, t, nil, nil) }) t.Run("WebAssembly", func(t *testing.T) { t.Parallel() From 4f1b69827d34d350f0148869c49bf118cd8613ee Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 23 Aug 2024 16:08:43 +0200 Subject: [PATCH 175/444] reflect: support big-endian systems The reflect package needs to know the endianness of the system in a few places. Before this patch, it assumed little-endian systems. But with GOARCH=mips we now have a big-endian system which also needs to be supported. So this patch fixes the reflect package to work on big-endian systems. Also, I've updated the tests for MIPS: instead of running the little-endian tests, I've changed it to run the big-endian tests instead. The two are very similar except for endianness so this should be fine. To be sure we won't accidentally break little-endian support, I've kept a single MIPS little-endian test (the CGo test, which doesn't yet work on big-endian systems anyway). --- main_test.go | 26 +++++++++++++++----------- src/reflect/endian-big.go | 36 ++++++++++++++++++++++++++++++++++++ src/reflect/endian-little.go | 35 +++++++++++++++++++++++++++++++++++ src/reflect/value.go | 31 ++++--------------------------- 4 files changed, 90 insertions(+), 38 deletions(-) create mode 100644 src/reflect/endian-big.go create mode 100644 src/reflect/endian-little.go diff --git a/main_test.go b/main_test.go index 80c8a46cd1..62eb5c51b9 100644 --- a/main_test.go +++ b/main_test.go @@ -36,7 +36,7 @@ var supportedLinuxArches = map[string]string{ "X86Linux": "linux/386", "ARMLinux": "linux/arm/6", "ARM64Linux": "linux/arm64", - "MIPSLinux": "linux/mipsle/hardfloat", + "MIPSLinux": "linux/mips/hardfloat", "WASIp1": "wasip1/wasm", } @@ -177,17 +177,14 @@ func TestBuild(t *testing.T) { }) } } - t.Run("MIPS big-endian", func(t *testing.T) { - // Run a single test for GOARCH=mips to see whether it works at all. - // Big-endian MIPS isn't fully supported yet, but simple examples - // should work. - // Once big-endian is fully supported, we can probably flip this - // around and do full testing of MIPS big-endian support and only do - // limited testing of MIPS little-endian (because the two are some - // similar). + t.Run("MIPS little-endian", func(t *testing.T) { + // Run a single test for GOARCH=mipsle to see whether it works at + // all. It is already mostly tested because GOARCH=mips and + // GOARCH=mipsle are so similar, but it's good to have an extra test + // to be sure. t.Parallel() - options := optionsFromOSARCH("linux/mips/softfloat", sema) - runTest("map.go", options, t, nil, nil) + options := optionsFromOSARCH("linux/mipsle/softfloat", sema) + runTest("cgo/", options, t, nil, nil) }) t.Run("WebAssembly", func(t *testing.T) { t.Parallel() @@ -232,6 +229,13 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { continue } } + if options.GOOS == "linux" && options.GOARCH == "mips" { + if name == "cgo/" { + // CGo isn't supported yet on big-endian systems (needs updates + // to bitfield access methods). + continue + } + } if options.Target == "simavr" { // Not all tests are currently supported on AVR. // Skip the ones that aren't. diff --git a/src/reflect/endian-big.go b/src/reflect/endian-big.go new file mode 100644 index 0000000000..94951e200d --- /dev/null +++ b/src/reflect/endian-big.go @@ -0,0 +1,36 @@ +//go:build mips + +package reflect + +import "unsafe" + +// loadValue loads a value that may or may not be word-aligned. The number of +// bytes given in size are loaded. The biggest possible size it can load is that +// of an uintptr. +func loadValue(ptr unsafe.Pointer, size uintptr) uintptr { + loadedValue := uintptr(0) + for i := uintptr(0); i < size; i++ { + loadedValue <<= 8 + loadedValue |= uintptr(*(*byte)(ptr)) + ptr = unsafe.Add(ptr, 1) + } + return loadedValue +} + +// storeValue is the inverse of loadValue. It stores a value to a pointer that +// doesn't need to be aligned. +func storeValue(ptr unsafe.Pointer, size, value uintptr) { + // This could perhaps be optimized using bits.ReverseBytes32 if needed. + value <<= (unsafe.Sizeof(uintptr(0)) - size) * 8 + for i := uintptr(0); i < size; i++ { + *(*byte)(ptr) = byte(value >> ((unsafe.Sizeof(uintptr(0)) - 1) * 8)) + ptr = unsafe.Add(ptr, 1) + value <<= 8 + } +} + +// maskAndShift cuts out a part of a uintptr. Note that the offset may not be 0. +func maskAndShift(value, offset, size uintptr) uintptr { + mask := ^uintptr(0) >> ((unsafe.Sizeof(uintptr(0)) - size) * 8) + return (uintptr(value) >> ((unsafe.Sizeof(uintptr(0)) - offset - size) * 8)) & mask +} diff --git a/src/reflect/endian-little.go b/src/reflect/endian-little.go new file mode 100644 index 0000000000..7d7e30059d --- /dev/null +++ b/src/reflect/endian-little.go @@ -0,0 +1,35 @@ +//go:build !mips + +package reflect + +import "unsafe" + +// loadValue loads a value that may or may not be word-aligned. The number of +// bytes given in size are loaded. The biggest possible size it can load is that +// of an uintptr. +func loadValue(ptr unsafe.Pointer, size uintptr) uintptr { + loadedValue := uintptr(0) + shift := uintptr(0) + for i := uintptr(0); i < size; i++ { + loadedValue |= uintptr(*(*byte)(ptr)) << shift + shift += 8 + ptr = unsafe.Add(ptr, 1) + } + return loadedValue +} + +// storeValue is the inverse of loadValue. It stores a value to a pointer that +// doesn't need to be aligned. +func storeValue(ptr unsafe.Pointer, size, value uintptr) { + for i := uintptr(0); i < size; i++ { + *(*byte)(ptr) = byte(value) + ptr = unsafe.Add(ptr, 1) + value >>= 8 + } +} + +// maskAndShift cuts out a part of a uintptr. Note that the offset may not be 0. +func maskAndShift(value, offset, size uintptr) uintptr { + mask := ^uintptr(0) >> ((unsafe.Sizeof(uintptr(0)) - size) * 8) + return (uintptr(value) >> (offset * 8)) & mask +} diff --git a/src/reflect/value.go b/src/reflect/value.go index 9e602f69de..15a900f9e1 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -887,26 +887,6 @@ func (v Value) Index(i int) Value { } } -// loadValue loads a value that may or may not be word-aligned. The number of -// bytes given in size are loaded. The biggest possible size it can load is that -// of an uintptr. -func loadValue(ptr unsafe.Pointer, size uintptr) uintptr { - loadedValue := uintptr(0) - shift := uintptr(0) - for i := uintptr(0); i < size; i++ { - loadedValue |= uintptr(*(*byte)(ptr)) << shift - shift += 8 - ptr = unsafe.Add(ptr, 1) - } - return loadedValue -} - -// maskAndShift cuts out a part of a uintptr. Note that the offset may not be 0. -func maskAndShift(value, offset, size uintptr) uintptr { - mask := ^uintptr(0) >> ((unsafe.Sizeof(uintptr(0)) - size) * 8) - return (uintptr(value) >> (offset * 8)) & mask -} - func (v Value) NumMethod() int { return v.typecode.NumMethod() } @@ -1088,9 +1068,7 @@ func (v Value) Set(x Value) { if v.typecode.Kind() == Interface && x.typecode.Kind() != Interface { // move the value of x back into the interface, if possible if x.isIndirect() && x.typecode.Size() <= unsafe.Sizeof(uintptr(0)) { - var value uintptr - memcpy(unsafe.Pointer(&value), x.value, x.typecode.Size()) - x.value = unsafe.Pointer(value) + x.value = unsafe.Pointer(loadValue(x.value, x.typecode.Size())) } intf := composeInterface(unsafe.Pointer(x.typecode), x.value) @@ -1101,12 +1079,11 @@ func (v Value) Set(x Value) { } size := v.typecode.Size() - xptr := x.value if size <= unsafe.Sizeof(uintptr(0)) && !x.isIndirect() { - value := x.value - xptr = unsafe.Pointer(&value) + storeValue(v.value, size, uintptr(x.value)) + } else { + memcpy(v.value, x.value, size) } - memcpy(v.value, xptr, size) } func (v Value) SetZero() { From ee5bc65c97ddd8ffd54d89a3cda24211a738cbb3 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 31 Aug 2024 13:18:26 +0200 Subject: [PATCH 176/444] compiler: move some code around to make the next bugfix easier This just makes the next fix easier to read. --- compiler/goroutine.go | 64 ++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/compiler/goroutine.go b/compiler/goroutine.go index 95abc77ff9..60fdfaabf5 100644 --- a/compiler/goroutine.go +++ b/compiler/goroutine.go @@ -13,37 +13,7 @@ import ( // createGo emits code to start a new goroutine. func (b *builder) createGo(instr *ssa.Go) { - // Get all function parameters to pass to the goroutine. - var params []llvm.Value - for _, param := range instr.Call.Args { - params = append(params, b.getValue(param, getPos(instr))) - } - - var prefix string - var funcPtr llvm.Value - var funcType llvm.Type - hasContext := false - if callee := instr.Call.StaticCallee(); callee != nil { - // Static callee is known. This makes it easier to start a new - // goroutine. - var context llvm.Value - switch value := instr.Call.Value.(type) { - case *ssa.Function: - // Goroutine call is regular function call. No context is necessary. - case *ssa.MakeClosure: - // A goroutine call on a func value, but the callee is trivial to find. For - // example: immediately applied functions. - funcValue := b.getValue(value, getPos(instr)) - context = b.extractFuncContext(funcValue) - default: - panic("StaticCallee returned an unexpected value") - } - if !context.IsNil() { - params = append(params, context) // context parameter - hasContext = true - } - funcType, funcPtr = b.getFunction(callee) - } else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok { + if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok { // We cheat. None of the builtins do any long or blocking operation, so // we might as well run these builtins right away without the program // noticing the difference. @@ -74,6 +44,38 @@ func (b *builder) createGo(instr *ssa.Go) { } b.createBuiltin(argTypes, argValues, builtin.Name(), instr.Pos()) return + } + + // Get all function parameters to pass to the goroutine. + var params []llvm.Value + for _, param := range instr.Call.Args { + params = append(params, b.getValue(param, getPos(instr))) + } + + var prefix string + var funcPtr llvm.Value + var funcType llvm.Type + hasContext := false + if callee := instr.Call.StaticCallee(); callee != nil { + // Static callee is known. This makes it easier to start a new + // goroutine. + var context llvm.Value + switch value := instr.Call.Value.(type) { + case *ssa.Function: + // Goroutine call is regular function call. No context is necessary. + case *ssa.MakeClosure: + // A goroutine call on a func value, but the callee is trivial to find. For + // example: immediately applied functions. + funcValue := b.getValue(value, getPos(instr)) + context = b.extractFuncContext(funcValue) + default: + panic("StaticCallee returned an unexpected value") + } + if !context.IsNil() { + params = append(params, context) // context parameter + hasContext = true + } + funcType, funcPtr = b.getFunction(callee) } else if instr.Call.IsInvoke() { // This is a method call on an interface value. itf := b.getValue(instr.Call.Value, getPos(instr)) From d4cb92f27c48148bc269308ff6973338b6f1f7c9 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 31 Aug 2024 13:25:42 +0200 Subject: [PATCH 177/444] compiler: fix passing weirdly-padded structs to new goroutines The values were stored in the passed object as the values itself (not expanded like is common in the calling convention), and read back after assuming they were expanded. This often works for simple parameters (int, pointer, etc), but not for more complex parameters. Especially when there's padding. Found this while working on `//go:wasmexport`. --- compiler/goroutine.go | 2 +- .../testdata/goroutine-cortex-m-qemu-tasks.ll | 12 +++++------- compiler/testdata/goroutine-wasm-asyncify.ll | 12 +++++------- testdata/goroutines.go | 16 ++++++++++++++++ testdata/goroutines.txt | 1 + 5 files changed, 28 insertions(+), 15 deletions(-) diff --git a/compiler/goroutine.go b/compiler/goroutine.go index 60fdfaabf5..a235563450 100644 --- a/compiler/goroutine.go +++ b/compiler/goroutine.go @@ -49,7 +49,7 @@ func (b *builder) createGo(instr *ssa.Go) { // Get all function parameters to pass to the goroutine. var params []llvm.Value for _, param := range instr.Call.Args { - params = append(params, b.getValue(param, getPos(instr))) + params = append(params, b.expandFormalParam(b.getValue(param, getPos(instr)))...) } var prefix string diff --git a/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll b/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll index f0a692c0f2..f149f3a0cf 100644 --- a/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll +++ b/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll @@ -3,8 +3,6 @@ source_filename = "goroutine.go" target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64" target triple = "thumbv7m-unknown-unknown-eabi" -%runtime._string = type { ptr, i32 } - @"main$string" = internal unnamed_addr constant [4 x i8] c"test", align 1 ; Function Attrs: allockind("alloc,zeroed") allocsize(0) @@ -150,12 +148,12 @@ define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, entry: %0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #9 store ptr %itf.value, ptr %0, align 4 - %1 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 1 + %1 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 1 store ptr @"main$string", ptr %1, align 4 - %.repack1 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 1, i32 1 - store i32 4, ptr %.repack1, align 4 - %2 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 2 - store ptr %itf.typecode, ptr %2, align 4 + %2 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 2 + store i32 4, ptr %2, align 4 + %3 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 3 + store ptr %itf.typecode, ptr %3, align 4 %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr undef) #9 call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #9 ret void diff --git a/compiler/testdata/goroutine-wasm-asyncify.ll b/compiler/testdata/goroutine-wasm-asyncify.ll index 1281857d31..699b9f2057 100644 --- a/compiler/testdata/goroutine-wasm-asyncify.ll +++ b/compiler/testdata/goroutine-wasm-asyncify.ll @@ -3,8 +3,6 @@ source_filename = "goroutine.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 } - @"main$string" = internal unnamed_addr constant [4 x i8] c"test", align 1 ; Function Attrs: allockind("alloc,zeroed") allocsize(0) @@ -161,12 +159,12 @@ entry: %0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #9 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #9 store ptr %itf.value, ptr %0, align 4 - %1 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 1 + %1 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 1 store ptr @"main$string", ptr %1, align 4 - %.repack1 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 1, i32 1 - store i32 4, ptr %.repack1, align 4 - %2 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 2 - store ptr %itf.typecode, ptr %2, align 4 + %2 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 2 + store i32 4, ptr %2, align 4 + %3 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 3 + store ptr %itf.typecode, ptr %3, align 4 call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #9 ret void } diff --git a/testdata/goroutines.go b/testdata/goroutines.go index 66abc54fde..5ac3b73187 100644 --- a/testdata/goroutines.go +++ b/testdata/goroutines.go @@ -86,6 +86,10 @@ func main() { testCond() testIssue1790() + + done := make(chan int) + go testPaddedParameters(paddedStruct{x: 5, y: 7}, done) + <-done } func acquire(m *sync.Mutex) { @@ -243,3 +247,15 @@ func (f Foo) Wait() { time.Sleep(time.Microsecond) println(" ...waited") } + +type paddedStruct struct { + x uint8 + _ [0]int64 + y uint8 +} + +// Structs with interesting padding used to crash. +func testPaddedParameters(s paddedStruct, done chan int) { + println("paddedStruct:", s.x, s.y) + close(done) +} diff --git a/testdata/goroutines.txt b/testdata/goroutines.txt index 35c0cd44dd..1430ee0a20 100644 --- a/testdata/goroutines.txt +++ b/testdata/goroutines.txt @@ -26,3 +26,4 @@ called: Foo.Nowait called: Foo.Wait ...waited done with 'go on interface' +paddedStruct: 5 7 From 1f3e0004a9db9c238fd451284b8b5b0abf4d57bb Mon Sep 17 00:00:00 2001 From: Warren Guy <5602790+warrenguy@users.noreply.github.com> Date: Thu, 5 Sep 2024 10:49:36 +0100 Subject: [PATCH 178/444] add board: RAKwireless RAK4631 (#4454) targets: add rak4631 --- GNUmakefile | 2 + src/machine/board_rak4631.go | 86 ++++++++++++++++++++++++++++++++++++ targets/rak4631.json | 6 +++ 3 files changed, 94 insertions(+) create mode 100644 src/machine/board_rak4631.go create mode 100644 targets/rak4631.json diff --git a/GNUmakefile b/GNUmakefile index 94d9c357d2..8979e25bef 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -642,6 +642,8 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=xiao examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=rak4631 examples/blinky1 + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/dac @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pyportal examples/dac diff --git a/src/machine/board_rak4631.go b/src/machine/board_rak4631.go new file mode 100644 index 0000000000..c59f3717b7 --- /dev/null +++ b/src/machine/board_rak4631.go @@ -0,0 +1,86 @@ +//go:build rak4631 + +package machine + +const HasLowFrequencyCrystal = true + +// Digital Pins +const ( + D0 Pin = P0_28 + D1 Pin = P0_02 +) + +// Analog pins +const ( + A0 Pin = P0_17 + A1 Pin = P1_02 + A2 Pin = P0_21 +) + +// Onboard LEDs +const ( + LED = LED2 + LED1 = P1_03 + LED2 = P1_04 +) + +// UART pins +const ( + // Default to UART1 + UART_RX_PIN = UART0_RX_PIN + UART_TX_PIN = UART0_TX_PIN + + // UART1 + UART0_RX_PIN = P0_19 + UART0_TX_PIN = P0_20 + + // UART2 + UART1_RX_PIN = P0_15 + UART1_TX_PIN = P0_16 +) + +// I2C pins +const ( + SDA_PIN = SDA1_PIN + SCL_PIN = SCL1_PIN + + SDA1_PIN = P0_13 + SCL1_PIN = P0_14 + + SDA2_PIN = P0_24 + SCL2_PIN = P0_25 +) + +// SPI pins +const ( + SPI0_SCK_PIN = P0_03 + SPI0_SDO_PIN = P0_29 + SPI0_SDI_PIN = P0_30 +) + +// Peripherals +const ( + LORA_NSS = P1_10 + LORA_SCK = P1_11 + LORA_MOSI = P1_12 + LORA_MISO = P1_13 + LORA_BUSY = P1_14 + LORA_DIO1 = P1_15 + LORA_NRESET = P1_06 + LORA_POWER = P1_05 +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "WisCore RAK4631 Board" + usb_STRING_MANUFACTURER = "RAKwireless" +) + +var ( + usb_VID uint16 = 0x239a + usb_PID uint16 = 0x8029 +) + +var ( + DefaultUART = UART0 +) diff --git a/targets/rak4631.json b/targets/rak4631.json new file mode 100644 index 0000000000..cbd8221300 --- /dev/null +++ b/targets/rak4631.json @@ -0,0 +1,6 @@ +{ + "inherits": ["nrf52840", "nrf52840-s140v6-uf2"], + "build-tags": ["rak4631"], + "serial-port": ["239a:8029"], + "msd-volume-name": ["RAK4631"] +} From c931bc73948a794301ebbe33d429614dc9dcf534 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 27 Aug 2024 15:47:19 +0200 Subject: [PATCH 179/444] wasip2: do not export the _start function It seems to have been replaced with the Component Model `run` function. --- src/runtime/runtime_wasm_wasip2.go | 10 +--------- targets/wasip2.json | 3 ++- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/runtime/runtime_wasm_wasip2.go b/src/runtime/runtime_wasm_wasip2.go index ca189aad64..57e6623d33 100644 --- a/src/runtime/runtime_wasm_wasip2.go +++ b/src/runtime/runtime_wasm_wasip2.go @@ -13,19 +13,11 @@ type timeUnit int64 //export wasi:cli/run@0.2.0#run func __wasi_cli_run_run() uint32 { - _start() - return 0 -} - -//export _start -func _start() { // These need to be initialized early so that the heap can be initialized. heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) run() -} - -func init() { + return 0 } var args []string diff --git a/targets/wasip2.json b/targets/wasip2.json index 3d6f68c6ec..7735e12dab 100644 --- a/targets/wasip2.json +++ b/targets/wasip2.json @@ -17,7 +17,8 @@ ], "ldflags": [ "--stack-first", - "--no-demangle" + "--no-demangle", + "--no-entry" ], "extra-files": [ "src/runtime/asm_tinygowasm.S" From 78ddc51471984b714ff25db33fe33012ee9a6f19 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Thu, 5 Sep 2024 11:22:30 -0700 Subject: [PATCH 180/444] internal/wasm-tools: update wasm-tools-go to new repo and version --- internal/wasm-tools/go.mod | 4 ++-- internal/wasm-tools/go.sum | 16 ++++++++-------- internal/wasm-tools/tools.go | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/wasm-tools/go.mod b/internal/wasm-tools/go.mod index 8a0e49351b..cffb52fd11 100644 --- a/internal/wasm-tools/go.mod +++ b/internal/wasm-tools/go.mod @@ -2,11 +2,11 @@ module github.com/tinygo-org/tinygo/internal/tools go 1.22.4 -require github.com/ydnar/wasm-tools-go v0.1.4 +require github.com/ydnar/wasm-tools-go v0.1.5 require ( github.com/coreos/go-semver v0.3.1 // indirect github.com/urfave/cli/v3 v3.0.0-alpha9 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - golang.org/x/mod v0.19.0 // indirect + golang.org/x/mod v0.21.0 // indirect ) diff --git a/internal/wasm-tools/go.sum b/internal/wasm-tools/go.sum index b2d0b1e3ac..58528629f4 100644 --- a/internal/wasm-tools/go.sum +++ b/internal/wasm-tools/go.sum @@ -12,14 +12,14 @@ github.com/urfave/cli/v3 v3.0.0-alpha9 h1:P0RMy5fQm1AslQS+XCmy9UknDXctOmG/q/FZkU github.com/urfave/cli/v3 v3.0.0-alpha9/go.mod h1:0kK/RUFHyh+yIKSfWxwheGndfnrvYSmYFVeKCh03ZUc= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= -github.com/ydnar/wasm-tools-go v0.1.4 h1:+25WqBj0AhLx8OFvZvrs7bQO6L3WtQ7t6JzQEYsXQb8= -github.com/ydnar/wasm-tools-go v0.1.4/go.mod h1:lQfv2Tde3tRgZDSYriro0EmdSHzP1mrHPMmYNahSS/g= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +github.com/ydnar/wasm-tools-go v0.1.5 h1:ah2WT4gH0IrmN29ZSsjgNC9SPsdXZ+KN+o9uTZqNVSI= +github.com/ydnar/wasm-tools-go v0.1.5/go.mod h1:L3sDi5rbAMJNmMO5cpDRzYYhB0r9xIU0xgpKjwU0Adg= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/wasm-tools/tools.go b/internal/wasm-tools/tools.go index c8eb42fde1..0876ea7331 100644 --- a/internal/wasm-tools/tools.go +++ b/internal/wasm-tools/tools.go @@ -5,7 +5,7 @@ package tools import ( - _ "github.com/ydnar/wasm-tools-go/cmd/wit-bindgen-go" + _ "github.com/bytecodealliance/wasm-tools-go/cmd/wit-bindgen-go" ) -//go:generate go install github.com/ydnar/wasm-tools-go/cmd/wit-bindgen-go +//go:generate go install github.com/bytecodealliance/wasm-tools-go/cmd/wit-bindgen-go From 9dcb63ca985506d207fc1a2db65251a14abf32cb Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Thu, 5 Sep 2024 11:31:45 -0700 Subject: [PATCH 181/444] internal/wasi: regenerated with wit-bindgen-go@v0.20 --- src/internal/wasi/cli/v0.2.0/command/command.wit.go | 2 -- src/internal/wasi/cli/v0.2.0/environment/environment.wit.go | 2 -- src/internal/wasi/cli/v0.2.0/exit/exit.wit.go | 2 -- src/internal/wasi/cli/v0.2.0/run/run.exports.go | 2 -- src/internal/wasi/cli/v0.2.0/run/run.wit.go | 2 -- src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go | 2 -- src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go | 2 -- src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go | 2 -- .../wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go | 2 -- .../wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go | 2 -- .../wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go | 2 -- .../wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go | 2 -- .../wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go | 2 -- .../wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go | 2 -- src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go | 2 -- src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go | 2 -- src/internal/wasi/filesystem/v0.2.0/types/abi.go | 2 -- src/internal/wasi/filesystem/v0.2.0/types/types.wit.go | 2 -- src/internal/wasi/io/v0.2.0/error/error.wit.go | 2 -- src/internal/wasi/io/v0.2.0/poll/poll.wit.go | 2 -- src/internal/wasi/io/v0.2.0/streams/abi.go | 2 -- src/internal/wasi/io/v0.2.0/streams/streams.wit.go | 2 -- .../wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go | 2 -- src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go | 2 -- src/internal/wasi/random/v0.2.0/random/random.wit.go | 2 -- .../sockets/v0.2.0/instance-network/instance-network.wit.go | 2 -- src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go | 2 -- .../wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go | 2 -- src/internal/wasi/sockets/v0.2.0/network/abi.go | 2 -- src/internal/wasi/sockets/v0.2.0/network/network.wit.go | 2 -- .../sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go | 2 -- src/internal/wasi/sockets/v0.2.0/tcp/abi.go | 2 -- src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go | 2 -- .../sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go | 2 -- src/internal/wasi/sockets/v0.2.0/udp/abi.go | 2 -- src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go | 2 -- 36 files changed, 72 deletions(-) diff --git a/src/internal/wasi/cli/v0.2.0/command/command.wit.go b/src/internal/wasi/cli/v0.2.0/command/command.wit.go index be6dd30450..cdd985d607 100644 --- a/src/internal/wasi/cli/v0.2.0/command/command.wit.go +++ b/src/internal/wasi/cli/v0.2.0/command/command.wit.go @@ -1,6 +1,4 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package command represents the world "wasi:cli/command@0.2.0". package command diff --git a/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go b/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go index 81fec996ad..a3cd386c22 100644 --- a/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go +++ b/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package environment represents the imported interface "wasi:cli/environment@0.2.0". package environment diff --git a/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go b/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go index 24aa1b5610..b506fa1857 100644 --- a/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go +++ b/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package exit represents the imported interface "wasi:cli/exit@0.2.0". package exit diff --git a/src/internal/wasi/cli/v0.2.0/run/run.exports.go b/src/internal/wasi/cli/v0.2.0/run/run.exports.go index 8dfaedec22..ae848313f5 100644 --- a/src/internal/wasi/cli/v0.2.0/run/run.exports.go +++ b/src/internal/wasi/cli/v0.2.0/run/run.exports.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - package run import ( diff --git a/src/internal/wasi/cli/v0.2.0/run/run.wit.go b/src/internal/wasi/cli/v0.2.0/run/run.wit.go index 22e7fc4321..51eb5fb311 100644 --- a/src/internal/wasi/cli/v0.2.0/run/run.wit.go +++ b/src/internal/wasi/cli/v0.2.0/run/run.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package run represents the exported interface "wasi:cli/run@0.2.0". package run diff --git a/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go index b57323715b..4486147535 100644 --- a/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go +++ b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package stderr represents the imported interface "wasi:cli/stderr@0.2.0". package stderr diff --git a/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go index 664ca14bef..c777175698 100644 --- a/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go +++ b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package stdin represents the imported interface "wasi:cli/stdin@0.2.0". package stdin diff --git a/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go index 6b2b28aac5..4a7007f0e0 100644 --- a/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go +++ b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package stdout represents the imported interface "wasi:cli/stdout@0.2.0". package stdout diff --git a/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go index 318a91ac79..795be5d093 100644 --- a/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go +++ b/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package terminalinput represents the imported interface "wasi:cli/terminal-input@0.2.0". // // Terminal input. diff --git a/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go index 6e56faf408..eb97c9ee7e 100644 --- a/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go +++ b/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package terminaloutput represents the imported interface "wasi:cli/terminal-output@0.2.0". // // Terminal output. diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go index d9e32838c0..72215a8b7c 100644 --- a/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go +++ b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package terminalstderr represents the imported interface "wasi:cli/terminal-stderr@0.2.0". // // An interface providing an optional `terminal-output` for stderr as a diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go index 834864f8ea..1a6091d7c8 100644 --- a/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go +++ b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package terminalstdin represents the imported interface "wasi:cli/terminal-stdin@0.2.0". // // An interface providing an optional `terminal-input` for stdin as a diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go index 7dbb2cab1b..3951449458 100644 --- a/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go +++ b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package terminalstdout represents the imported interface "wasi:cli/terminal-stdout@0.2.0". // // An interface providing an optional `terminal-output` for stdout as a diff --git a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go index 1b1ae5358d..580bd2b552 100644 --- a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go +++ b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package monotonicclock represents the imported interface "wasi:clocks/monotonic-clock@0.2.0". // // WASI Monotonic Clock is a clock API intended to let users measure elapsed diff --git a/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go b/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go index a270f78d6d..a59a313d24 100644 --- a/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go +++ b/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package wallclock represents the imported interface "wasi:clocks/wall-clock@0.2.0". // // WASI Wall Clock is a clock API intended to let users query the current diff --git a/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go index 4638a9a39d..d57dea5179 100644 --- a/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go +++ b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package preopens represents the imported interface "wasi:filesystem/preopens@0.2.0". package preopens diff --git a/src/internal/wasi/filesystem/v0.2.0/types/abi.go b/src/internal/wasi/filesystem/v0.2.0/types/abi.go index 136fb06c2f..4cf4f32804 100644 --- a/src/internal/wasi/filesystem/v0.2.0/types/abi.go +++ b/src/internal/wasi/filesystem/v0.2.0/types/abi.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - package types import ( diff --git a/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go b/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go index f1f139ed13..ecceefe924 100644 --- a/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go +++ b/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package types represents the imported interface "wasi:filesystem/types@0.2.0". // // WASI filesystem is a filesystem API primarily intended to let users run WASI diff --git a/src/internal/wasi/io/v0.2.0/error/error.wit.go b/src/internal/wasi/io/v0.2.0/error/error.wit.go index aae635062a..e7f92a8d3f 100644 --- a/src/internal/wasi/io/v0.2.0/error/error.wit.go +++ b/src/internal/wasi/io/v0.2.0/error/error.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package ioerror represents the imported interface "wasi:io/error@0.2.0". package ioerror diff --git a/src/internal/wasi/io/v0.2.0/poll/poll.wit.go b/src/internal/wasi/io/v0.2.0/poll/poll.wit.go index 274a8e8a4b..626ac50e1e 100644 --- a/src/internal/wasi/io/v0.2.0/poll/poll.wit.go +++ b/src/internal/wasi/io/v0.2.0/poll/poll.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package poll represents the imported interface "wasi:io/poll@0.2.0". // // A poll API intended to let users wait for I/O events on multiple handles diff --git a/src/internal/wasi/io/v0.2.0/streams/abi.go b/src/internal/wasi/io/v0.2.0/streams/abi.go index 8d7592070c..5f1500288a 100644 --- a/src/internal/wasi/io/v0.2.0/streams/abi.go +++ b/src/internal/wasi/io/v0.2.0/streams/abi.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - package streams import ( diff --git a/src/internal/wasi/io/v0.2.0/streams/streams.wit.go b/src/internal/wasi/io/v0.2.0/streams/streams.wit.go index 317fdc8820..01fc0288f0 100644 --- a/src/internal/wasi/io/v0.2.0/streams/streams.wit.go +++ b/src/internal/wasi/io/v0.2.0/streams/streams.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package streams represents the imported interface "wasi:io/streams@0.2.0". // // WASI I/O is an I/O abstraction API which is currently focused on providing diff --git a/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go b/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go index e9ac780821..3b8f33c065 100644 --- a/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go +++ b/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package insecureseed represents the imported interface "wasi:random/insecure-seed@0.2.0". // // The insecure-seed interface for seeding hash-map DoS resistance. diff --git a/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go b/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go index 7b38f0118d..44cdbd7cfd 100644 --- a/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go +++ b/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package insecure represents the imported interface "wasi:random/insecure@0.2.0". // // The insecure interface for insecure pseudo-random numbers. diff --git a/src/internal/wasi/random/v0.2.0/random/random.wit.go b/src/internal/wasi/random/v0.2.0/random/random.wit.go index 05b7556236..cf0929bf5b 100644 --- a/src/internal/wasi/random/v0.2.0/random/random.wit.go +++ b/src/internal/wasi/random/v0.2.0/random/random.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package random represents the imported interface "wasi:random/random@0.2.0". // // WASI Random is a random data API. diff --git a/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go b/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go index e7d5f0ea7e..4740360171 100644 --- a/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package instancenetwork represents the imported interface "wasi:sockets/instance-network@0.2.0". // // This interface provides a value-export of the default network handle.. diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go index 41714d9ec8..fbc790e008 100644 --- a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go +++ b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - package ipnamelookup import ( diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go index dcd1fd9d95..f4c7632f7b 100644 --- a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package ipnamelookup represents the imported interface "wasi:sockets/ip-name-lookup@0.2.0". package ipnamelookup diff --git a/src/internal/wasi/sockets/v0.2.0/network/abi.go b/src/internal/wasi/sockets/v0.2.0/network/abi.go index ef9fc1e8dc..54820be8aa 100644 --- a/src/internal/wasi/sockets/v0.2.0/network/abi.go +++ b/src/internal/wasi/sockets/v0.2.0/network/abi.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - package network import ( diff --git a/src/internal/wasi/sockets/v0.2.0/network/network.wit.go b/src/internal/wasi/sockets/v0.2.0/network/network.wit.go index 5f2403c433..58ad4e9b6e 100644 --- a/src/internal/wasi/sockets/v0.2.0/network/network.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/network/network.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package network represents the imported interface "wasi:sockets/network@0.2.0". package network diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go index 20478aaaeb..44e24502b8 100644 --- a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package tcpcreatesocket represents the imported interface "wasi:sockets/tcp-create-socket@0.2.0". package tcpcreatesocket diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/abi.go b/src/internal/wasi/sockets/v0.2.0/tcp/abi.go index f986948e37..f6b37f118e 100644 --- a/src/internal/wasi/sockets/v0.2.0/tcp/abi.go +++ b/src/internal/wasi/sockets/v0.2.0/tcp/abi.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - package tcp import ( diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go index e278713101..30eefbd87e 100644 --- a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package tcp represents the imported interface "wasi:sockets/tcp@0.2.0". package tcp diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go index c06bc96c57..dc21be4d9c 100644 --- a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package udpcreatesocket represents the imported interface "wasi:sockets/udp-create-socket@0.2.0". package udpcreatesocket diff --git a/src/internal/wasi/sockets/v0.2.0/udp/abi.go b/src/internal/wasi/sockets/v0.2.0/udp/abi.go index 3783acdac3..23a96c983e 100644 --- a/src/internal/wasi/sockets/v0.2.0/udp/abi.go +++ b/src/internal/wasi/sockets/v0.2.0/udp/abi.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - package udp import ( diff --git a/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go b/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go index 41b8491c0f..268a8f6c89 100644 --- a/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go @@ -1,7 +1,5 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. -//go:build !wasip1 - // Package udp represents the imported interface "wasi:sockets/udp@0.2.0". package udp From e13d4ba3d0d8851b6a3b73e791d2303992b4d3d5 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Thu, 5 Sep 2024 11:40:45 -0700 Subject: [PATCH 182/444] GNUmakefile, internal/wasm-tools: s/ydnar/bytecodealliance/g --- GNUmakefile | 2 +- internal/wasm-tools/go.mod | 2 +- internal/wasm-tools/go.sum | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 8979e25bef..ed1855c833 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -267,7 +267,7 @@ lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a: cd lib/wasi-libc && $(MAKE) -j4 EXTRA_CFLAGS="-O2 -g -DNDEBUG -mnontrapping-fptoint -msign-ext" MALLOC_IMPL=none CC="$(CLANG)" AR=$(LLVM_AR) NM=$(LLVM_NM) # Generate WASI syscall bindings -WASM_TOOLS_MODULE=github.com/ydnar/wasm-tools-go +WASM_TOOLS_MODULE=github.com/bytecodealliance/wasm-tools-go .PHONY: wasi-syscall wasi-syscall: wasi-cm go run -modfile ./internal/wasm-tools/go.mod $(WASM_TOOLS_MODULE)/cmd/wit-bindgen-go generate --versioned -o ./src/internal -p internal --cm internal/cm ./lib/wasi-cli/wit diff --git a/internal/wasm-tools/go.mod b/internal/wasm-tools/go.mod index cffb52fd11..eadddcbbba 100644 --- a/internal/wasm-tools/go.mod +++ b/internal/wasm-tools/go.mod @@ -2,7 +2,7 @@ module github.com/tinygo-org/tinygo/internal/tools go 1.22.4 -require github.com/ydnar/wasm-tools-go v0.1.5 +require github.com/bytecodealliance/wasm-tools-go v0.2.0 require ( github.com/coreos/go-semver v0.3.1 // indirect diff --git a/internal/wasm-tools/go.sum b/internal/wasm-tools/go.sum index 58528629f4..d4fb71dfb0 100644 --- a/internal/wasm-tools/go.sum +++ b/internal/wasm-tools/go.sum @@ -1,3 +1,5 @@ +github.com/bytecodealliance/wasm-tools-go v0.2.0 h1:JdmiZew7ewHjf+ZGGRE4gZM85Ad/PGW/5I57hepEOjQ= +github.com/bytecodealliance/wasm-tools-go v0.2.0/go.mod h1:2GnJCUlcDrslZ/L6+yYqoUnewDlBvqRS2N/0NW9ro6w= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -12,8 +14,6 @@ github.com/urfave/cli/v3 v3.0.0-alpha9 h1:P0RMy5fQm1AslQS+XCmy9UknDXctOmG/q/FZkU github.com/urfave/cli/v3 v3.0.0-alpha9/go.mod h1:0kK/RUFHyh+yIKSfWxwheGndfnrvYSmYFVeKCh03ZUc= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= -github.com/ydnar/wasm-tools-go v0.1.5 h1:ah2WT4gH0IrmN29ZSsjgNC9SPsdXZ+KN+o9uTZqNVSI= -github.com/ydnar/wasm-tools-go v0.1.5/go.mod h1:L3sDi5rbAMJNmMO5cpDRzYYhB0r9xIU0xgpKjwU0Adg= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= From 2e47a9c5cdd38025bda8b6a21b356b564111bf3a Mon Sep 17 00:00:00 2001 From: deadprogram Date: Fri, 16 Aug 2024 16:11:31 +0200 Subject: [PATCH 183/444] gen-device: switch generator for Renesas code to use main cmsis-svd repo --- GNUmakefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index ed1855c833..7333a6ebc9 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -185,7 +185,7 @@ fmt-check: ## Warn if any source needs reformatting @unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1 -gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp gen-device-rp ## Generate microcontroller-specific sources +gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp gen-device-rp gen-device-renesas ## Generate microcontroller-specific sources ifneq ($(STM32), 0) gen-device: gen-device-stm32 endif @@ -234,7 +234,7 @@ gen-device-rp: build/gen-device-svd GO111MODULE=off $(GO) fmt ./src/device/rp gen-device-renesas: build/gen-device-svd - ./build/gen-device-svd -source=https://github.com/tinygo-org/renesas-svd lib/renesas-svd/ src/device/renesas/ + ./build/gen-device-svd -source=https://github.com/cmsis-svd/cmsis-svd-data/tree/master/data/Renesas lib/cmsis-svd/data/Renesas/ src/device/renesas/ GO111MODULE=off $(GO) fmt ./src/device/renesas $(LLVM_PROJECTDIR)/llvm: From c201faab92f70b05fecb406b69ec619d6f4dd9fc Mon Sep 17 00:00:00 2001 From: deadprogram Date: Fri, 16 Aug 2024 16:12:15 +0200 Subject: [PATCH 184/444] gitignore: ignore device files generated for Renesas Signed-off-by: deadprogram --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 7c2f12b312..a2bc8dc444 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,8 @@ src/device/stm32/*.go src/device/stm32/*.s src/device/kendryte/*.go src/device/kendryte/*.s +src/device/renesas/*.go +src/device/renesas/*.s src/device/rp/*.go src/device/rp/*.s ./vendor From b5626e70cbe6e0cb6cba5a462f1f53f503d376fd Mon Sep 17 00:00:00 2001 From: deadprogram Date: Fri, 16 Aug 2024 16:16:53 +0200 Subject: [PATCH 185/444] submodules: remove separate renesas-svd repo in favor of more recent changes. Signed-off-by: deadprogram --- .gitmodules | 3 --- lib/renesas-svd | 1 - 2 files changed, 4 deletions(-) delete mode 160000 lib/renesas-svd diff --git a/.gitmodules b/.gitmodules index 4a8820e3a6..91bd14a7d7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -32,9 +32,6 @@ [submodule "lib/macos-minimal-sdk"] path = lib/macos-minimal-sdk url = https://github.com/aykevl/macos-minimal-sdk.git -[submodule "lib/renesas-svd"] - path = lib/renesas-svd - url = https://github.com/tinygo-org/renesas-svd.git [submodule "src/net"] path = src/net url = https://github.com/tinygo-org/net.git diff --git a/lib/renesas-svd b/lib/renesas-svd deleted file mode 160000 index 03d7688085..0000000000 --- a/lib/renesas-svd +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 03d76880854b9042f5d043f4355cdf8eef522fa5 From 5abf1e998d2622f6aa0ed2f49e21c46bc23012e8 Mon Sep 17 00:00:00 2001 From: archie2x <9526806+archie2x@users.noreply.github.com> Date: Thu, 12 Sep 2024 22:05:44 -0700 Subject: [PATCH 186/444] Fix #4421: Add `-C DIR` flag (#4422) feature: Fix #4421: Add `-C DIR` flag Signed-off-by: Roger Standridge <9526806+archie2x@users.noreply.github.com> --- GNUmakefile | 11 +++++++- main.go | 54 ++++++++++++++++++++++++++++++++++++ tests/testing/chdir/chdir.go | 27 ++++++++++++++++++ 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 tests/testing/chdir/chdir.go diff --git a/GNUmakefile b/GNUmakefile index 7333a6ebc9..70a7c5f589 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -483,8 +483,17 @@ tinygo-baremetal: # regression test for #2666: e.g. encoding/hex must pass on baremetal $(TINYGO) test -target cortex-m-qemu encoding/hex +.PHONY: testchdir +testchdir: + # test 'build' command with{,out} -C argument + $(TINYGO) build -C tests/testing/chdir chdir.go && rm tests/testing/chdir/chdir + $(TINYGO) build ./tests/testing/chdir/chdir.go && rm chdir + # test 'run' command with{,out} -C argument + EXPECT_DIR=$(PWD)/tests/testing/chdir $(TINYGO) run -C tests/testing/chdir chdir.go + EXPECT_DIR=$(PWD) $(TINYGO) run ./tests/testing/chdir/chdir.go + .PHONY: smoketest -smoketest: +smoketest: testchdir $(TINYGO) version $(TINYGO) targets > /dev/null # regression test for #2892 diff --git a/main.go b/main.go index 51e62aaaee..227c869c94 100644 --- a/main.go +++ b/main.go @@ -1461,6 +1461,7 @@ func main() { // Early command processing, before commands are interpreted by the Go flag // library. + handleChdirFlag() switch command { case "clang", "ld.lld", "wasm-ld": err := builder.RunTool(command, os.Args[2:]...) @@ -1946,3 +1947,56 @@ type outputEntry struct { stderr bool data []byte } + +// handleChdirFlag handles the -C flag before doing anything else. +// The -C flag must be the first flag on the command line, to make it easy to find +// even with commands that have custom flag parsing. +// handleChdirFlag handles the flag by chdir'ing to the directory +// and then removing that flag from the command line entirely. +// +// We have to handle the -C flag this way for two reasons: +// +// 1. Toolchain selection needs to be in the right directory to look for go.mod and go.work. +// +// 2. A toolchain switch later on reinvokes the new go command with the same arguments. +// The parent toolchain has already done the chdir; the child must not try to do it again. + +func handleChdirFlag() { + used := 2 // b.c. command at os.Args[1] + if used >= len(os.Args) { + return + } + + var dir string + switch a := os.Args[used]; { + default: + return + + case a == "-C", a == "--C": + if used+1 >= len(os.Args) { + return + } + dir = os.Args[used+1] + os.Args = slicesDelete(os.Args, used, used+2) + + case strings.HasPrefix(a, "-C="), strings.HasPrefix(a, "--C="): + _, dir, _ = strings.Cut(a, "=") + os.Args = slicesDelete(os.Args, used, used+1) + } + + if err := os.Chdir(dir); err != nil { + fmt.Fprintln(os.Stderr, "cannot chdir:", err) + os.Exit(1) + } +} + +// go1.19 compatibility: lacks slices package +func slicesDelete[S ~[]E, E any](s S, i, j int) S { + _ = s[i:j:len(s)] // bounds check + + if i == j { + return s + } + + return append(s[:i], s[j:]...) +} diff --git a/tests/testing/chdir/chdir.go b/tests/testing/chdir/chdir.go new file mode 100644 index 0000000000..75281c21f0 --- /dev/null +++ b/tests/testing/chdir/chdir.go @@ -0,0 +1,27 @@ +package main + +import ( + "log" + "os" + "path/filepath" + "runtime" +) + +/* +Test that this program is 'run' in expected directory. 'run' with expected +working-directory in 'EXPECT_DIR' environment variable' with{,out} a -C +argument. +*/ +func main() { + expectDir := os.Getenv("EXPECT_DIR") + cwd, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + if runtime.GOOS == "windows" { + cwd = filepath.ToSlash(cwd) + } + if cwd != expectDir { + log.Fatalf("expected:\"%v\" != os.Getwd():\"%v\"", expectDir, cwd) + } +} From d948941d82261f26adf37b5a699ee2359f1eb42c Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Fri, 13 Sep 2024 11:24:10 +0200 Subject: [PATCH 187/444] governance: add initial documentation for project governance (#4457) governance: add initial attempt to document project governance Signed-off-by: deadprogram --- GOVERNANCE.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 GOVERNANCE.md diff --git a/GOVERNANCE.md b/GOVERNANCE.md new file mode 100644 index 0000000000..7650b404b4 --- /dev/null +++ b/GOVERNANCE.md @@ -0,0 +1,32 @@ +TinyGo Team Members +=================== + +The team of humans who maintain TinyGo. + +* **Purpose**: To maintain the community, code, documentation, and tools for the TinyGo compiler. +* **Board**: The group of people who share responsibility for key decisions for the TinyGo organization. + * **Majority Voting**: The board makes decisions by majority vote. + * **Membership**: The board elects its own members. +* **Do-ocracy**: Those who step forward to do a given task propose how it should be done. Then other interested people can make comments. +* **Proof of Work**: Power in decision-making is slightly weighted based on a participant's labor for the community. +* **Initiation**: We need to establish a procedure for how people join the team of maintainers. +* **Transparency**: Important information should be made publicly available, ideally in a way that allows for public comment. +* **Code of Conduct**: Participants agree to abide by the current project Code of Conduct. + +## Members + +* Ayke van Laethem (@aykevl) +* Daniel Esteban (@conejoninja) +* Ron Evans (@deadprogram) +* Damian Gryski (@dgryski) +* Masaaki Takasago (@sago35) +* Patricio Whittingslow (@soypat) +* Yurii Soldak (@ysoldak) + +## Experimental + +* **Monthly Meeting**: A monthly meeting for the team and any other interested participants. + Duration: 1 hour + Facilitation: @deadprogram + Schedule: See https://github.com/tinygo-org/tinygo/wiki/Meetings for more information + From 5a014dd6a3b80bba7f8f8a2d5e82b979eed26827 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Tue, 17 Sep 2024 09:24:23 +0200 Subject: [PATCH 188/444] tinygo: add relative and absolute --dir options to wasmtime args (#4431) main: add relative and absolute --dir options to wasmtime args --- GNUmakefile | 2 +- main.go | 90 +++++++++++++++++++---------------------------------- 2 files changed, 33 insertions(+), 59 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 70a7c5f589..34be4045be 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -293,7 +293,7 @@ tinygo: ## Build the TinyGo compiler @if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " $(MAKE) llvm-source"; echo " $(MAKE) $(LLVM_BUILDDIR)"; exit 1; fi CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" -ldflags="-X github.com/tinygo-org/tinygo/goenv.GitSha1=`git rev-parse --short HEAD`" . test: wasi-libc check-nodejs-version - CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=20m -buildmode exe -tags "byollvm osusergo" $(GOTESTPKGS) + CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -v -timeout=1h -buildmode exe -tags "byollvm osusergo" $(GOTESTPKGS) # Standard library packages that pass tests on darwin, linux, wasi, and windows, but take over a minute in wasi TEST_PACKAGES_SLOW = \ diff --git a/main.go b/main.go index 227c869c94..6109c00bfa 100644 --- a/main.go +++ b/main.go @@ -283,46 +283,6 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options // Tests are always run in the package directory. cmd.Dir = result.MainDir - // wasmtime is the default emulator used for `-target=wasip1`. wasmtime - // is a WebAssembly runtime CLI with WASI enabled by default. However, - // only stdio are allowed by default. For example, while STDOUT routes - // to the host, other files don't. It also does not inherit environment - // variables from the host. Some tests read testdata files, often from - // outside the package directory. Other tests require temporary - // writeable directories. We allow this by adding wasmtime flags below. - if config.EmulatorName() == "wasmtime" { - // At this point, The current working directory is at the package - // directory. Ex. $GOROOT/src/compress/flate for compress/flate. - // buildAndRun has already added arguments for wasmtime, that allow - // read-access to files such as "testdata/huffman-zero.in". - // - // Ex. main(.wasm) --dir=. -- -test.v - - // Below adds additional wasmtime flags in case a test reads files - // outside its directory, like "../testdata/e.txt". This allows any - // relative directory up to the module root, even if the test never - // reads any files. - // - // Ex. run --dir=.. --dir=../.. --dir=../../.. - var dirs []string - switch config.Target.GOOS { - case "wasip1": - dirs = dirsToModuleRootRel(result.MainDir, result.ModuleRoot) - default: - dirs = dirsToModuleRootAbs(result.MainDir, result.ModuleRoot) - } - - args := []string{"run"} - for _, d := range dirs { - args = append(args, "--dir="+d) - } - - args = append(args, "--env=PWD="+cmd.Dir) - - args = append(args, cmd.Args[1:]...) - cmd.Args = args - } - // Run the test. start := time.Now() err = cmd.Run() @@ -848,12 +808,11 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c for _, v := range environmentVars { emuArgs = append(emuArgs, "--env", v) } - if len(cmdArgs) != 0 { - // Use of '--' argument no longer necessary as of Wasmtime v14: - // https://github.com/bytecodealliance/wasmtime/pull/6946 - // args = append(args, "--") - args = append(args, cmdArgs...) - } + + // Use of '--' argument no longer necessary as of Wasmtime v14: + // https://github.com/bytecodealliance/wasmtime/pull/6946 + // args = append(args, "--") + args = append(args, cmdArgs...) // Set this for nicer backtraces during tests, but don't override the user. if _, ok := os.LookupEnv("WASMTIME_BACKTRACE_DETAILS"); !ok { @@ -903,21 +862,36 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c name = emulator[0] + // wasmtime is a WebAssembly runtime CLI with WASI enabled by default. + // By default, only stdio is allowed. For example, while STDOUT routes + // to the host, other files don't. It also does not inherit environment + // variables from the host. Some tests read testdata files, often from + // outside the package directory. Other tests require temporary + // writeable directories. We allow this by adding wasmtime flags below. if name == "wasmtime" { - // Wasmtime needs some special flags to pass environment variables - // and allow reading from the current directory. - switch config.Options.Target { - case "wasip1": - emuArgs = append(emuArgs, "--dir=.") - case "wasip2": - dir := result.MainDir - if isSingleFile { - cwd, _ := os.Getwd() - dir = cwd + // Below adds additional wasmtime flags in case a test reads files + // outside its directory, like "../testdata/e.txt". This allows any + // relative directory up to the module root, even if the test never + // reads any files. + if config.TestConfig.CompileTestBinary { + // Add relative dirs (../, ../..) up to module root (for wasip1) + dirs := dirsToModuleRootRel(result.MainDir, result.ModuleRoot) + + // Add absolute dirs up to module root (for wasip2) + dirs = append(dirs, dirsToModuleRootAbs(result.MainDir, result.ModuleRoot)...) + + for _, d := range dirs { + emuArgs = append(emuArgs, "--dir="+d) } - emuArgs = append(emuArgs, "--dir="+dir) - emuArgs = append(emuArgs, "--env=PWD="+dir) } + + dir := result.MainDir + if isSingleFile { + dir, _ = os.Getwd() + } + emuArgs = append(emuArgs, "--dir=.") + emuArgs = append(emuArgs, "--dir="+dir) + emuArgs = append(emuArgs, "--env=PWD="+dir) } emuArgs = append(emuArgs, emulator[1:]...) From d4729f92bde874dac83c07d96538dba90fd716d3 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sun, 15 Sep 2024 10:18:02 -0700 Subject: [PATCH 189/444] targets/wasip2: add wasmtime -S args to support network interfaces --- targets/wasip2.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/wasip2.json b/targets/wasip2.json index 7735e12dab..b32a68197e 100644 --- a/targets/wasip2.json +++ b/targets/wasip2.json @@ -23,7 +23,7 @@ "extra-files": [ "src/runtime/asm_tinygowasm.S" ], - "emulator": "wasmtime --wasm component-model --dir={tmpDir}::/tmp {}", + "emulator": "wasmtime --wasm component-model -Sinherit-network -Sallow-ip-name-lookup --dir={tmpDir}::/tmp {}", "wit-package": "{root}/lib/wasi-cli/wit/", "wit-world": "wasi:cli/command" } From a9bf981d9219ad894c39d5dddce517708f7d0be5 Mon Sep 17 00:00:00 2001 From: leongross Date: Tue, 17 Sep 2024 07:12:57 -0700 Subject: [PATCH 190/444] Cgo add cbytes implementation (rebased version of #3318) (#4470) cgo: added CBytes implementation --- cgo/cgo.go | 9 ++++++++- cgo/testdata/basic.out.go | 7 +++++++ cgo/testdata/const.out.go | 7 +++++++ cgo/testdata/errors.out.go | 7 +++++++ cgo/testdata/flags.out.go | 7 +++++++ cgo/testdata/symbols.out.go | 7 +++++++ cgo/testdata/types.out.go | 7 +++++++ src/runtime/string.go | 7 +++++++ testdata/cgo/main.go | 2 ++ testdata/cgo/out.txt | 2 ++ 10 files changed, 61 insertions(+), 1 deletion(-) diff --git a/cgo/cgo.go b/cgo/cgo.go index d5f1221200..4a5d7efedf 100644 --- a/cgo/cgo.go +++ b/cgo/cgo.go @@ -162,6 +162,13 @@ func __GoBytes(unsafe.Pointer, uintptr) []byte func GoBytes(ptr unsafe.Pointer, length C.int) []byte { return C.__GoBytes(ptr, uintptr(length)) } + +//go:linkname C.__CBytes runtime.cgo_CBytes +func __CBytes([]byte) unsafe.Pointer + +func CBytes(b []byte) unsafe.Pointer { + return C.__CBytes(b) +} ` // Process extracts `import "C"` statements from the AST, parses the comment @@ -218,7 +225,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl switch decl := decl.(type) { case *ast.FuncDecl: switch decl.Name.Name { - case "CString", "GoString", "GoStringN", "__GoStringN", "GoBytes", "__GoBytes": + case "CString", "GoString", "GoStringN", "__GoStringN", "GoBytes", "__GoBytes", "CBytes", "__CBytes": // Adjust the name to have a "C." prefix so it is correctly // resolved. decl.Name.Name = "C." + decl.Name.Name diff --git a/cgo/testdata/basic.out.go b/cgo/testdata/basic.out.go index f2daa78dac..6c2623980e 100644 --- a/cgo/testdata/basic.out.go +++ b/cgo/testdata/basic.out.go @@ -24,6 +24,13 @@ func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte { return C.__GoBytes(ptr, uintptr(length)) } +//go:linkname C.__CBytes runtime.cgo_CBytes +func C.__CBytes([]byte) unsafe.Pointer + +func C.CBytes(b []byte) unsafe.Pointer { + return C.__CBytes(b) +} + type ( C.char uint8 C.schar int8 diff --git a/cgo/testdata/const.out.go b/cgo/testdata/const.out.go index 4a5f5fd5b0..2b48163b4b 100644 --- a/cgo/testdata/const.out.go +++ b/cgo/testdata/const.out.go @@ -24,6 +24,13 @@ func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte { return C.__GoBytes(ptr, uintptr(length)) } +//go:linkname C.__CBytes runtime.cgo_CBytes +func C.__CBytes([]byte) unsafe.Pointer + +func C.CBytes(b []byte) unsafe.Pointer { + return C.__CBytes(b) +} + type ( C.char uint8 C.schar int8 diff --git a/cgo/testdata/errors.out.go b/cgo/testdata/errors.out.go index d0e04320ae..43a6a65c97 100644 --- a/cgo/testdata/errors.out.go +++ b/cgo/testdata/errors.out.go @@ -44,6 +44,13 @@ func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte { return C.__GoBytes(ptr, uintptr(length)) } +//go:linkname C.__CBytes runtime.cgo_CBytes +func C.__CBytes([]byte) unsafe.Pointer + +func C.CBytes(b []byte) unsafe.Pointer { + return C.__CBytes(b) +} + type ( C.char uint8 C.schar int8 diff --git a/cgo/testdata/flags.out.go b/cgo/testdata/flags.out.go index 72520ab6d9..83ca604200 100644 --- a/cgo/testdata/flags.out.go +++ b/cgo/testdata/flags.out.go @@ -29,6 +29,13 @@ func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte { return C.__GoBytes(ptr, uintptr(length)) } +//go:linkname C.__CBytes runtime.cgo_CBytes +func C.__CBytes([]byte) unsafe.Pointer + +func C.CBytes(b []byte) unsafe.Pointer { + return C.__CBytes(b) +} + type ( C.char uint8 C.schar int8 diff --git a/cgo/testdata/symbols.out.go b/cgo/testdata/symbols.out.go index f2826ae2e9..569cb65f6a 100644 --- a/cgo/testdata/symbols.out.go +++ b/cgo/testdata/symbols.out.go @@ -24,6 +24,13 @@ func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte { return C.__GoBytes(ptr, uintptr(length)) } +//go:linkname C.__CBytes runtime.cgo_CBytes +func C.__CBytes([]byte) unsafe.Pointer + +func C.CBytes(b []byte) unsafe.Pointer { + return C.__CBytes(b) +} + type ( C.char uint8 C.schar int8 diff --git a/cgo/testdata/types.out.go b/cgo/testdata/types.out.go index a35d3733c4..e5382ec803 100644 --- a/cgo/testdata/types.out.go +++ b/cgo/testdata/types.out.go @@ -24,6 +24,13 @@ func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte { return C.__GoBytes(ptr, uintptr(length)) } +//go:linkname C.__CBytes runtime.cgo_CBytes +func C.__CBytes([]byte) unsafe.Pointer + +func C.CBytes(b []byte) unsafe.Pointer { + return C.__CBytes(b) +} + type ( C.char uint8 C.schar int8 diff --git a/src/runtime/string.go b/src/runtime/string.go index 13bfcd0ed2..aeefe1d4fa 100644 --- a/src/runtime/string.go +++ b/src/runtime/string.go @@ -283,3 +283,10 @@ func cgo_GoBytes(ptr unsafe.Pointer, length uintptr) []byte { } return buf } + +func cgo_CBytes(b []byte) unsafe.Pointer { + p := malloc(uintptr(len(b))) + s := unsafe.Slice((*byte)(p), len(b)) + copy(s, b) + return p +} diff --git a/testdata/cgo/main.go b/testdata/cgo/main.go index ddd1992e22..00e0ba01da 100644 --- a/testdata/cgo/main.go +++ b/testdata/cgo/main.go @@ -165,6 +165,8 @@ func main() { println("C.GoString(nil):", C.GoString(nil)) println("len(C.GoStringN(nil, 0)):", len(C.GoStringN(nil, 0))) println("len(C.GoBytes(nil, 0)):", len(C.GoBytes(nil, 0))) + println("len(C.GoBytes(C.CBytes(nil),0)):", len(C.GoBytes(C.CBytes(nil), 0))) + println(`rountrip CBytes:`, C.GoString((*C.char)(C.CBytes([]byte("hello\000"))))) // libc: test whether C functions work at all. buf1 := []byte("foobar\x00") diff --git a/testdata/cgo/out.txt b/testdata/cgo/out.txt index 4ea45d864c..3088d4cb4d 100644 --- a/testdata/cgo/out.txt +++ b/testdata/cgo/out.txt @@ -73,6 +73,8 @@ C.CStringN: 4 2 0 4 8 C.GoString(nil): len(C.GoStringN(nil, 0)): 0 len(C.GoBytes(nil, 0)): 0 +len(C.GoBytes(C.CBytes(nil),0)): 0 +rountrip CBytes: hello copied string: foobar CGo sqrt(3): +1.732051e+000 C sqrt(3): +1.732051e+000 From 84048f299f172af5e5cd77227b1464695ed1c738 Mon Sep 17 00:00:00 2001 From: leongross Date: Tue, 17 Sep 2024 08:26:22 -0700 Subject: [PATCH 191/444] os/File: add stubs for os.File Deadlines (#4465) os/File: add stubs for os.File Deadlines * add os.SetDeadline, os.SetReadDeadline, os.SetWriteDeadline stubs for posix files. * deadline: add tests Signed-off-by: leongross --- src/os/deadline_test.go | 50 +++++++++++++++++++++++++++++++++++++++++ src/os/file.go | 17 ++++++++++++++ src/os/file_posix.go | 25 ++++++++++++++++++++- 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 src/os/deadline_test.go diff --git a/src/os/deadline_test.go b/src/os/deadline_test.go new file mode 100644 index 0000000000..03097e46ef --- /dev/null +++ b/src/os/deadline_test.go @@ -0,0 +1,50 @@ +//go:build posix && !baremetal && !js + +package os_test + +import ( + "errors" + . "os" + "testing" +) + +func TestDeadlines(t *testing.T) { + // Create a handle to a known-good, existing file + f, err := Open("/dev/null") + if err != nil { + t.Fatal(err) + } + + if err := f.SetDeadline(0); err == nil { + if err != nil { + t.Errorf("wanted nil, got %v", err) + } + } + + if err := f.SetDeadline(1); err == nil { + if !errors.Is(err, ErrNotImplemented) { + t.Errorf("wanted ErrNotImplemented, got %v", err) + } + } + + if err := f.SetReadDeadline(1); err == nil { + if !errors.Is(err, ErrNotImplemented) { + t.Errorf("wanted ErrNotImplemented, got %v", err) + } + } + + if err := f.SetWriteDeadline(1); err == nil { + if !errors.Is(err, ErrNotImplemented) { + t.Errorf("wanted ErrNotImplemented, got %v", err) + } + } + + // Closed files must return an error + f.Close() + + if err := f.SetDeadline(0); err == nil { + if err != ErrClosed { + t.Errorf("wanted ErrClosed, got %v", err) + } + } +} diff --git a/src/os/file.go b/src/os/file.go index 7c3c0db125..b47bf748a7 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -257,12 +257,29 @@ func (f *File) SyscallConn() (conn syscall.RawConn, err error) { return } +// SetDeadline sets the read and write deadlines for a File. +// Calls to SetDeadline for files that do not support deadlines will return ErrNoDeadline +// This stub always returns ErrNoDeadline. +// A zero value for t means I/O operations will not time out. +func (f *File) SetDeadline(t time.Time) error { + if f.handle == nil { + return ErrClosed + } + return f.setDeadline(t) +} + // SetReadDeadline sets the deadline for future Read calls and any // currently-blocked Read call. func (f *File) SetReadDeadline(t time.Time) error { return f.setReadDeadline(t) } +// SetWriteDeadline sets the deadline for any future Write calls and any +// currently-blocked Write call. +func (f *File) SetWriteDeadline(t time.Time) error { + return f.setWriteDeadline(t) +} + // fd is an internal interface that is used to try a type assertion in order to // call the Fd() method of the underlying file handle if it is implemented. type fd interface { diff --git a/src/os/file_posix.go b/src/os/file_posix.go index e36a6b1cd7..a10ff49196 100644 --- a/src/os/file_posix.go +++ b/src/os/file_posix.go @@ -4,12 +4,35 @@ import ( "time" ) +//TODO: re-implement the ErrNoDeadline error in the correct code path + // Chtimes is a stub, not yet implemented func Chtimes(name string, atime time.Time, mtime time.Time) error { return ErrNotImplemented } +// setDeadline sets the read and write deadline. +func (f *File) setDeadline(t time.Time) error { + if t.IsZero() { + return nil + } + return ErrNotImplemented +} + // setReadDeadline sets the read deadline, not yet implemented -func (f *File) setReadDeadline(_ time.Time) error { +// A zero value for t means Read will not time out. +func (f *File) setReadDeadline(t time.Time) error { + if t.IsZero() { + return nil + } + return ErrNotImplemented +} + +// setWriteDeadline sets the write deadline, not yet implemented +// A zero value for t means Read will not time out. +func (f *File) setWriteDeadline(t time.Time) error { + if t.IsZero() { + return nil + } return ErrNotImplemented } From 892efaec973aa7fe5442ca0f16f05f4951ac73a3 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Tue, 17 Sep 2024 12:08:24 -0700 Subject: [PATCH 192/444] support -extldflags Fixes #4320 --- compileopts/config.go | 10 ++++++++++ compileopts/options.go | 1 + main.go | 2 ++ 3 files changed, 13 insertions(+) diff --git a/compileopts/config.go b/compileopts/config.go index 18d3c9e4d8..cc1f4d61ce 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -395,6 +395,16 @@ func (c *Config) LDFlags() []string { if c.Target.LinkerScript != "" { ldflags = append(ldflags, "-T", c.Target.LinkerScript) } + + if c.Options.ExtLDFlags != "" { + ext, err := shlex.Split(c.Options.ExtLDFlags) + if err != nil { + // if shlex can't split it, pass it as-is and let the external linker complain + ext = []string{c.Options.ExtLDFlags} + } + ldflags = append(ldflags, ext...) + } + return ldflags } diff --git a/compileopts/options.go b/compileopts/options.go index 9601ae3221..980097200d 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -56,6 +56,7 @@ type Options struct { Timeout time.Duration WITPackage string // pass through to wasm-tools component embed invocation WITWorld string // pass through to wasm-tools component embed -w option + ExtLDFlags string } // Verify performs a validation on the given options, raising an error if options are not valid. diff --git a/main.go b/main.go index 6109c00bfa..64d022fb95 100644 --- a/main.go +++ b/main.go @@ -1388,6 +1388,7 @@ func main() { cpuprofile := flag.String("cpuprofile", "", "cpuprofile output") monitor := flag.Bool("monitor", false, "enable serial monitor") baudrate := flag.Int("baudrate", 115200, "baudrate of serial monitor") + extLDFlags := flag.String("extldflags", "", "additional flags to pass to external linker") // Internal flags, that are only intended for TinyGo development. printIR := flag.Bool("internal-printir", false, "print LLVM IR") @@ -1503,6 +1504,7 @@ func main() { Timeout: *timeout, WITPackage: witPackage, WITWorld: witWorld, + ExtLDFlags: *extLDFlags, } if *printCommands { options.PrintCommands = printCommand From dcca47f1f6088ea2565468e4a93a96e4308af480 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 18 Sep 2024 11:46:16 +0200 Subject: [PATCH 193/444] main: add -ldflags='-extldflags=...' support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This matches upstream Go. Example: $ go test -ldflags='-extldflags=-foobar' os # os.test /usr/local/go1.23.1/pkg/tool/linux_arm64/link: running gcc failed: exit status 1 /usr/bin/gcc -s -o $WORK/b001/os.test -rdynamic /tmp/go-link-914594215/go.o /tmp/go-link-914594215/000000.o /tmp/go-link-914594215/000001.o /tmp/go-link-914594215/000002.o /tmp/go-link-914594215/000003.o /tmp/go-link-914594215/000004.o /tmp/go-link-914594215/000005.o /tmp/go-link-914594215/000006.o /tmp/go-link-914594215/000007.o /tmp/go-link-914594215/000008.o /tmp/go-link-914594215/000009.o /tmp/go-link-914594215/000010.o /tmp/go-link-914594215/000011.o /tmp/go-link-914594215/000012.o /tmp/go-link-914594215/000013.o /tmp/go-link-914594215/000014.o /tmp/go-link-914594215/000015.o /tmp/go-link-914594215/000016.o /tmp/go-link-914594215/000017.o /tmp/go-link-914594215/000018.o /tmp/go-link-914594215/000019.o /tmp/go-link-914594215/000020.o /tmp/go-link-914594215/000021.o -O2 -g -O2 -g -lresolv -O2 -g -lpthread -foobar gcc: error: unrecognized command-line option ‘-foobar’ FAIL os [build failed] FAIL And TinyGo, with this patch: $ tinygo test -ldflags='-extldflags=-foobar' os FAIL os 0.000s ld.lld: error: unknown argument '-foobar' Also note that Go doesn't support the `-extldflags` directly (which was previously the case with TinyGo): $ go test -extldflags='-foobar' os flag provided but not defined: -extldflags [...] --- main.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/main.go b/main.go index 64d022fb95..202d98a368 100644 --- a/main.go +++ b/main.go @@ -1304,19 +1304,20 @@ func (m globalValuesFlag) Set(value string) error { // parseGoLinkFlag parses the -ldflags parameter. Its primary purpose right now // is the -X flag, for setting the value of global string variables. -func parseGoLinkFlag(flagsString string) (map[string]map[string]string, error) { +func parseGoLinkFlag(flagsString string) (map[string]map[string]string, string, error) { set := flag.NewFlagSet("link", flag.ExitOnError) globalVarValues := make(globalValuesFlag) set.Var(globalVarValues, "X", "Set the value of the string variable to the given value.") + extLDFlags := set.String("extldflags", "", "additional flags to pass to external linker") flags, err := shlex.Split(flagsString) if err != nil { - return nil, err + return nil, "", err } err = set.Parse(flags) if err != nil { - return nil, err + return nil, "", err } - return map[string]map[string]string(globalVarValues), nil + return map[string]map[string]string(globalVarValues), *extLDFlags, nil } // getListOfPackages returns a standard list of packages for a given list that might @@ -1388,7 +1389,6 @@ func main() { cpuprofile := flag.String("cpuprofile", "", "cpuprofile output") monitor := flag.Bool("monitor", false, "enable serial monitor") baudrate := flag.Int("baudrate", 115200, "baudrate of serial monitor") - extLDFlags := flag.String("extldflags", "", "additional flags to pass to external linker") // Internal flags, that are only intended for TinyGo development. printIR := flag.Bool("internal-printir", false, "print LLVM IR") @@ -1449,7 +1449,7 @@ func main() { } flag.CommandLine.Parse(os.Args[2:]) - globalVarValues, err := parseGoLinkFlag(*ldflags) + globalVarValues, extLDFlags, err := parseGoLinkFlag(*ldflags) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) @@ -1504,7 +1504,7 @@ func main() { Timeout: *timeout, WITPackage: witPackage, WITWorld: witWorld, - ExtLDFlags: *extLDFlags, + ExtLDFlags: extLDFlags, } if *printCommands { options.PrintCommands = printCommand From 37460ad60af2601a0412426c229663443c8d8eaa Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 24 Mar 2023 00:23:03 +0100 Subject: [PATCH 194/444] compiler: support pragmas on generic functions --- compiler/symbol.go | 8 ++++++-- compiler/testdata/pragma.go | 16 ++++++++++++++++ compiler/testdata/pragma.ll | 13 +++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/compiler/symbol.go b/compiler/symbol.go index 29f0095208..32eb55107c 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -250,10 +250,14 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo { // parsePragmas is used by getFunctionInfo to parse function pragmas such as // //export or //go:noinline. func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { - if f.Syntax() == nil { + syntax := f.Syntax() + if f.Origin() != nil { + syntax = f.Origin().Syntax() + } + if syntax == nil { return } - if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil { + if decl, ok := syntax.(*ast.FuncDecl); ok && decl.Doc != nil { for _, comment := range decl.Doc.List { text := comment.Text if strings.HasPrefix(text, "//export ") { diff --git a/compiler/testdata/pragma.go b/compiler/testdata/pragma.go index fa1d4b0e96..1f6badf7fb 100644 --- a/compiler/testdata/pragma.go +++ b/compiler/testdata/pragma.go @@ -48,6 +48,22 @@ func inlineFunc() { func noinlineFunc() { } +type Int interface { + int8 | int16 +} + +// Same for generic functions (but the compiler may miss the pragma due to it +// being generic). +// +//go:noinline +func noinlineGenericFunc[T Int]() { +} + +func useGeneric() { + // Make sure the generic function above is instantiated. + noinlineGenericFunc[int8]() +} + // This function should have the specified section. // //go:section .special_function_section diff --git a/compiler/testdata/pragma.ll b/compiler/testdata/pragma.ll index 3ee4078f1a..28e678359d 100644 --- a/compiler/testdata/pragma.ll +++ b/compiler/testdata/pragma.ll @@ -48,6 +48,19 @@ entry: ret void } +; Function Attrs: nounwind +define hidden void @main.useGeneric(ptr %context) unnamed_addr #2 { +entry: + call void @"main.noinlineGenericFunc[int8]"(ptr undef) + ret void +} + +; Function Attrs: noinline nounwind +define linkonce_odr hidden void @"main.noinlineGenericFunc[int8]"(ptr %context) unnamed_addr #5 { +entry: + ret void +} + ; Function Attrs: noinline nounwind define hidden void @main.functionInSection(ptr %context) unnamed_addr #5 section ".special_function_section" { entry: From bcd4c6b65872333c9a3582d3db73121c074cb381 Mon Sep 17 00:00:00 2001 From: leongross Date: Thu, 12 Sep 2024 14:50:42 +0200 Subject: [PATCH 195/444] builder: nits remove unused job state enum, nits/reformatting Signed-off-by: leongross --- builder/jobs.go | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/builder/jobs.go b/builder/jobs.go index a23d07534d..116887461e 100644 --- a/builder/jobs.go +++ b/builder/jobs.go @@ -17,14 +17,6 @@ import ( // concurrency or performance issues. const jobRunnerDebug = false -type jobState uint8 - -const ( - jobStateQueued jobState = iota // not yet running - jobStateRunning // running - jobStateFinished // finished running -) - // compileJob is a single compiler job, comparable to a single Makefile target. // It is used to orchestrate various compiler tasks that can be run in parallel // but that have dependencies and thus have limitations in how they can be run. @@ -55,12 +47,11 @@ func dummyCompileJob(result string) *compileJob { // ordered as such in the job dependencies. func runJobs(job *compileJob, sema chan struct{}) error { if sema == nil { - // Have a default, if the semaphore isn't set. This is useful for - // tests. + // Have a default, if the semaphore isn't set. This is useful for tests. sema = make(chan struct{}, runtime.NumCPU()) } if cap(sema) == 0 { - return errors.New("cannot 0 jobs at a time") + return errors.New("cannot run 0 jobs at a time") } // Create a slice of jobs to run, where all dependencies are run in order. @@ -81,10 +72,10 @@ func runJobs(job *compileJob, sema chan struct{}) error { waiting := make(map[*compileJob]map[*compileJob]struct{}, len(jobs)) dependents := make(map[*compileJob][]*compileJob, len(jobs)) - jidx := make(map[*compileJob]int) + compileJobs := make(map[*compileJob]int) var ready intHeap for i, job := range jobs { - jidx[job] = i + compileJobs[job] = i if len(job.dependencies) == 0 { // This job is ready to run. ready.Push(i) @@ -105,8 +96,7 @@ func runJobs(job *compileJob, sema chan struct{}) error { // Create a channel to accept notifications of completion. doneChan := make(chan *compileJob) - // Send each job in the jobs slice to a worker, taking care of job - // dependencies. + // Send each job in the jobs slice to a worker, taking care of job dependencies. numRunningJobs := 0 var totalTime time.Duration start := time.Now() @@ -156,7 +146,7 @@ func runJobs(job *compileJob, sema chan struct{}) error { delete(wait, completed) if len(wait) == 0 { // This job is now ready to run. - ready.Push(jidx[j]) + ready.Push(compileJobs[j]) delete(waiting, j) } } From 52788e5826035dee45e1050ebcb3a1229612ec5b Mon Sep 17 00:00:00 2001 From: leongross Date: Sat, 28 Sep 2024 12:33:18 -0700 Subject: [PATCH 196/444] main: rework usage (#4467) main: rework usage * remove unused switch case statement and improve/add command specific usage. * limit help to 80 col width * remove inconsistent ':' * remove trailing spaces * rmove spaces woth tabs * reworkd build command Signed-off-by: leongross --- main.go | 177 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 155 insertions(+), 22 deletions(-) diff --git a/main.go b/main.go index 202d98a368..6f257d1b21 100644 --- a/main.go +++ b/main.go @@ -1230,36 +1230,169 @@ func getBMPPorts() (gdbPort, uartPort string, err error) { } } -func usage(command string) { - switch command { - default: - fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.") - fmt.Fprintln(os.Stderr, "version:", goenv.Version()) - fmt.Fprintf(os.Stderr, "usage: %s [arguments]\n", os.Args[0]) - fmt.Fprintln(os.Stderr, "\ncommands:") - fmt.Fprintln(os.Stderr, " build: compile packages and dependencies") - fmt.Fprintln(os.Stderr, " run: compile and run immediately") - fmt.Fprintln(os.Stderr, " test: test packages") - fmt.Fprintln(os.Stderr, " flash: compile and flash to the device") - fmt.Fprintln(os.Stderr, " gdb: run/flash and immediately enter GDB") - fmt.Fprintln(os.Stderr, " lldb: run/flash and immediately enter LLDB") - fmt.Fprintln(os.Stderr, " monitor: open communication port") - fmt.Fprintln(os.Stderr, " ports: list available serial ports") - fmt.Fprintln(os.Stderr, " env: list environment variables used during build") - fmt.Fprintln(os.Stderr, " list: run go list using the TinyGo root") - fmt.Fprintln(os.Stderr, " clean: empty cache directory ("+goenv.Get("GOCACHE")+")") - fmt.Fprintln(os.Stderr, " targets: list targets") - fmt.Fprintln(os.Stderr, " info: show info for specified target") - fmt.Fprintln(os.Stderr, " version: show version") - fmt.Fprintln(os.Stderr, " help: print this help text") +const ( + usageBuild = `Build compiles the packages named by the import paths, along with their +dependencies, but it does not install the results. The output binary is +specified using the -o parameter. The generated file type depends on the +extension: + + .o: + Create a relocatable object file. You can use this option if you + don't want to use the TinyGo build system or want to do other custom + things. + + .ll: + Create textual LLVM IR, after optimization. This is mainly useful + for debugging. + + .bc: + Create LLVM bitcode, after optimization. This may be useful for + debugging or for linking into other programs using LTO. + + .hex: + Create an Intel HEX file to flash it to a microcontroller. + + .bin: + Similar, but create a binary file. + + .wasm: + Compile and link a WebAssembly file. + +(all other) Compile and link the program into a regular executable. For +microcontrollers, it is common to use the .elf file extension to indicate a +linked ELF file is generated. For Linux, it is common to build binaries with no +extension at all.` + + usageRun = `Run the program, either directly on the host or in an emulated environment +(depending on -target).` + + usageFlash = `Flash the program to a microcontroller. Some common flags are described below. + + -target={name}: + Specifies the type of microcontroller that is used. The name of the + microcontroller is given on the individual pages for each board type + listed under Microcontrollers + (https://tinygo.org/docs/reference/microcontrollers/). + Examples: "arduino-nano", "d1mini", "xiao". + + -monitor: + Start the serial monitor (see below) immediately after + flashing. However, some microcontrollers need a split second + or two to configure the serial port after flashing, and + using the "-monitor" flag can fail because the serial + monitor starts too quickly. In that case, use the "tinygo + monitor" command explicitly.` + + usageMonitor = `Start the serial monitor on the serial port that is connected to the +microcontroller. If there is only a single board attached to the host computer, +the default values for various options should be sufficient. In other +situations, particularly if you have multiple microcontrollers attached, some +parameters may need to be overridden using the following flags: + + -port={port}: + If there are multiple microcontroller attached, an error + message will display a list of potential serial ports. The + appropriate port can be specified by this flag. On Linux, + the port will be something like /dev/ttyUSB0 or /dev/ttyACM1. + On MacOS, the port will look like /dev/cu.usbserial-1420. On + Windows, the port will be something like COM1 or COM31. + + -baudrate={rate}: + The default baud rate is 115200. Boards using the AVR + processor (e.g. Arduino Nano, Arduino Mega 2560) use 9600 + instead. + + -target={name}: + If you have more than one microcontrollers attached, you can + sometimes just specify the target name and let tinygo + monitor figure out the port. Sometimes, this does not work + and you have to explicitly use the -port flag. + +The serial monitor intercepts several control characters for its own use instead of sending them +to the microcontroller: + + Control-C: terminates the tinygo monitor + Control-Z: suspends the tinygo monitor and drops back into shell + Control-\: terminates the tinygo monitor with a stack trace + Control-S: flow control, suspends output to the console + Control-Q: flow control, resumes output to the console + Control-@: thrown away by tinygo monitor + +Note: If you are using os.Stdin on the microcontroller, you may find that a CR +character on the host computer (also known as Enter, ^M, or \r) is transmitted +to the microcontroller without conversion, so os.Stdin returns a \r character +instead of the expected \n (also known as ^J, NL, or LF) to indicate +end-of-line. You may be able to get around this problem by hitting Control-J in +tinygo monitor to transmit the \n end-of-line character.` + + usageGdb = `Build the program, optionally flash it to a microcontroller if it is a remote +target, and drop into a GDB shell. From there you can set breakpoints, start the +program with "run" or "continue" ("run" for a local program, continue for +on-chip debugging), single-step, show a backtrace, break and resume the program +with Ctrl-C/"continue", etc. You may need to install extra tools (like openocd +and arm-none-eabi-gdb) to be able to do this. Also, you may need a dedicated +debugger to be able to debug certain boards if no debugger is integrated. Some +boards (like the BBC micro:bit and most professional evaluation boards) have an +integrated debugger.` + + usageClean = `Clean the cache directory, normally stored in $HOME/.cache/tinygo. This is not +normally needed.` + + usageHelp = `Print a short summary of the available commands, plus a list of command flags.` + usageVersion = `Print the version of the command and the version of the used $GOROOT.` + usageEnv = `Print a list of environment variables that affect TinyGo (as a shell script). +If one or more variable names are given as arguments, env prints the value of +each on a new line.` + + usageDefault = `TinyGo is a Go compiler for small places. +version: %s +usage: %s [arguments] +commands: + build: compile packages and dependencies + run: compile and run immediately + test: test packages + flash: compile and flash to the device + gdb: run/flash and immediately enter GDB + lldb: run/flash and immediately enter LLDB + monitor: open communication port + ports: list available serial ports + env: list environment variables used during build + list: run go list using the TinyGo root + clean: empty cache directory (%s) + targets: list targets + info: show info for specified target + version: show version + help: print this help text` +) + +var ( + commandHelp = map[string]string{ + "build": usageBuild, + "run": usageRun, + "flash": usageFlash, + "monitor": usageMonitor, + "gdb": usageGdb, + "clean": usageClean, + "help": usageHelp, + "version": usageVersion, + "env": usageEnv, + } +) +func usage(command string) { + val, ok := commandHelp[command] + if !ok { + fmt.Fprintf(os.Stderr, usageDefault, goenv.Version(), os.Args[0], goenv.Get("GOCACHE")) if flag.Parsed() { fmt.Fprintln(os.Stderr, "\nflags:") flag.PrintDefaults() } fmt.Fprintln(os.Stderr, "\nfor more details, see https://tinygo.org/docs/reference/usage/") + } else { + fmt.Fprintln(os.Stderr, val) } + } func handleCompilerError(err error) { From b3c040e9f76a393cb2807d0247d7a515985162f4 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Wed, 18 Sep 2024 10:54:07 -0700 Subject: [PATCH 197/444] runtime: make map iteration less defined Fixes #3726 --- src/runtime/hashmap.go | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index a148415f7a..93bbb2f410 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -45,8 +45,11 @@ type hashmapIterator struct { buckets unsafe.Pointer // pointer to array of hashapBuckets numBuckets uintptr // length of buckets array bucketNumber uintptr // current index into buckets array + startBucket uintptr // starting location for iterator bucket *hashmapBucket // current bucket in chain bucketIndex uint8 // current index into bucket + startIndex uint8 // starting bucket index for iterator + wrapped bool // true if the iterator has wrapped } func hashmapNewIterator() unsafe.Pointer { @@ -390,28 +393,44 @@ func hashmapNext(m *hashmap, it *hashmapIterator, key, value unsafe.Pointer) boo // initialize iterator it.buckets = m.buckets it.numBuckets = uintptr(1) << m.bucketBits + it.startBucket = uintptr(fastrand()) & (it.numBuckets - 1) + it.startIndex = uint8(fastrand() & 7) + + it.bucketNumber = it.startBucket + it.bucket = hashmapBucketAddr(m, it.buckets, it.bucketNumber) + it.bucketIndex = it.startIndex } for { + // If we've wrapped and we're back at our starting location, terminate the iteration. + if it.wrapped && it.bucketNumber == it.startBucket && it.bucketIndex == it.startIndex { + return false + } + if it.bucketIndex >= 8 { // end of bucket, move to the next in the chain it.bucketIndex = 0 it.bucket = it.bucket.next } + if it.bucket == nil { + it.bucketNumber++ // next bucket if it.bucketNumber >= it.numBuckets { - // went through all buckets - return false + // went through all buckets -- wrap around + it.bucketNumber = 0 + it.wrapped = true } it.bucket = hashmapBucketAddr(m, it.buckets, it.bucketNumber) - it.bucketNumber++ // next bucket + continue } + if it.bucket.tophash[it.bucketIndex] == 0 { // slot is empty - move on it.bucketIndex++ continue } + // Found a key. slotKey := hashmapSlotKey(m, it.bucket, it.bucketIndex) memcpy(key, slotKey, m.keySize) From fa12450552884e0c37a03d943d97193488c9f527 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Wed, 18 Sep 2024 11:17:12 -0700 Subject: [PATCH 198/444] runtime: fix building with -tags=runtime_memhash_tsip --- src/internal/binary/binary.go | 5 +++++ src/runtime/memhash_tsip.go | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/internal/binary/binary.go b/src/internal/binary/binary.go index f3dbf6e9b5..25f8d39632 100644 --- a/src/internal/binary/binary.go +++ b/src/internal/binary/binary.go @@ -30,3 +30,8 @@ func (littleEndian) AppendUint16(b []byte, v uint16) []byte { func (littleEndian) Uint32(b []byte) uint32 { return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 } + +func (littleEndian) Uint64(b []byte) uint64 { + return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | + uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 +} diff --git a/src/runtime/memhash_tsip.go b/src/runtime/memhash_tsip.go index e05bf15dc2..a8829e0527 100644 --- a/src/runtime/memhash_tsip.go +++ b/src/runtime/memhash_tsip.go @@ -10,7 +10,7 @@ package runtime import ( - "encoding/binary" + "internal/binary" "math/bits" "unsafe" ) From b3e1974c304bb5b676bfdb0e193f40658495d61f Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Wed, 18 Sep 2024 12:07:33 -0700 Subject: [PATCH 199/444] runtime: add maps.clone Fixes #4382 --- src/runtime/hashmap.go | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index 93bbb2f410..7b46b42bef 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -281,13 +281,26 @@ func hashmapInsertIntoNewBucket(m *hashmap, key, value unsafe.Pointer, tophash u } func hashmapGrow(m *hashmap) { + // allocate our new buckets twice as big + n := hashmapCopy(m, m.bucketBits+1) + *m = n +} + +//go:linkname hashmapClone maps.clone +func hashmapClone(intf _interface) _interface { + typ, val := decomposeInterface(intf) + m := (*hashmap)(val) + n := hashmapCopy(m, m.bucketBits) + return composeInterface(typ, unsafe.Pointer(&n)) +} + +func hashmapCopy(m *hashmap, sizeBits uint8) hashmap { // clone map as empty n := *m n.count = 0 n.seed = uintptr(fastrand()) - // allocate our new buckets twice as big - n.bucketBits = m.bucketBits + 1 + n.bucketBits = sizeBits numBuckets := uintptr(1) << n.bucketBits bucketBufSize := hashmapBucketSize(m) n.buckets = alloc(bucketBufSize*numBuckets, nil) @@ -303,7 +316,7 @@ func hashmapGrow(m *hashmap) { hashmapSet(&n, key, value, h) } - *m = n + return n } // Get the value of a specified key, or zero the value if not found. From f3dfe1d49ecbcbadc256bea8ba452e1c3b05053a Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Wed, 18 Sep 2024 12:28:55 -0700 Subject: [PATCH 200/444] runtime: seed fastrand() with hardware randomness --- builder/sizes_test.go | 4 ++-- src/runtime/algorithm.go | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/builder/sizes_test.go b/builder/sizes_test.go index 9755abe819..64cd4e77bf 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -42,8 +42,8 @@ func TestBinarySize(t *testing.T) { tests := []sizeTest{ // microcontrollers {"hifive1b", "examples/echo", 4484, 280, 0, 2252}, - {"microbit", "examples/serial", 2732, 388, 8, 2256}, - {"wioterminal", "examples/pininterrupt", 6016, 1484, 116, 6816}, + {"microbit", "examples/serial", 2808, 388, 8, 2256}, + {"wioterminal", "examples/pininterrupt", 6064, 1484, 116, 6816}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the diff --git a/src/runtime/algorithm.go b/src/runtime/algorithm.go index 15ca2b7f5d..24571498b2 100644 --- a/src/runtime/algorithm.go +++ b/src/runtime/algorithm.go @@ -23,7 +23,13 @@ func fastrand() uint32 { return xorshift32State } -var xorshift32State uint32 = 1 +func init() { + r, _ := hardwareRand() + xorshift64State = uint64(r | 1) // protect against 0 + xorshift32State = uint32(xorshift64State) +} + +var xorshift32State uint32 func xorshift32(x uint32) uint32 { // Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs". @@ -43,7 +49,7 @@ func fastrand64() uint64 { return xorshift64State } -var xorshift64State uint64 = 1 +var xorshift64State uint64 // 64-bit xorshift multiply rng from http://vigna.di.unimi.it/ftp/papers/xorshift.pdf func xorshiftMult64(x uint64) uint64 { From 9d144895474b9f74090408de215a9e149b4ef704 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Wed, 18 Sep 2024 15:52:35 -0700 Subject: [PATCH 201/444] TestWebAssembly: use wasm-unknown for panic=trap test --- main_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/main_test.go b/main_test.go index 62eb5c51b9..c131070c8c 100644 --- a/main_test.go +++ b/main_test.go @@ -470,20 +470,21 @@ func TestWebAssembly(t *testing.T) { t.Parallel() type testCase struct { name string + target string panicStrategy string imports []string } for _, tc := range []testCase{ // Test whether there really are no imports when using -panic=trap. This // tests the bugfix for https://github.com/tinygo-org/tinygo/issues/4161. - {name: "panic-default", imports: []string{"wasi_snapshot_preview1.fd_write"}}, - {name: "panic-trap", panicStrategy: "trap", imports: []string{}}, + {name: "panic-default", target: "wasip1", imports: []string{"wasi_snapshot_preview1.fd_write", "wasi_snapshot_preview1.random_get"}}, + {name: "panic-trap", target: "wasm-unknown", panicStrategy: "trap", imports: []string{}}, } { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() tmpdir := t.TempDir() - options := optionsFromTarget("wasi", sema) + options := optionsFromTarget(tc.target, sema) options.PanicStrategy = tc.panicStrategy config, err := builder.NewConfig(&options) if err != nil { From 666d2bd501eedaf26c7b5c90027e95afdbd579e9 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 3 Oct 2024 16:32:01 +0200 Subject: [PATCH 202/444] builder: keep wasm temporary files Don't rename them when -work is set, instead update result.Binary each time and leave result.Executable be the linker output (as intended: result.Executable should be the unmodified linker output). --- builder/build.go | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/builder/build.go b/builder/build.go index 780dc8df49..3f12b99911 100644 --- a/builder/build.go +++ b/builder/build.go @@ -823,19 +823,13 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe args = append(args, "--asyncify") } - exeunopt := result.Executable - - if config.Options.Work { - // Keep the work direction around => don't overwrite the .wasm binary with the optimized version - exeunopt += ".pre-wasm-opt" - os.Rename(result.Executable, exeunopt) - } - + inputFile := result.Binary + result.Binary = result.Executable + ".wasmopt" args = append(args, opt, "-g", - exeunopt, - "--output", result.Executable, + inputFile, + "--output", result.Binary, ) wasmopt := goenv.Get("WASMOPT") @@ -865,13 +859,15 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe // wasm-tools component embed -w wasi:cli/command // $$(tinygo env TINYGOROOT)/lib/wasi-cli/wit/ main.wasm -o embedded.wasm + componentEmbedInputFile := result.Binary + result.Binary = result.Executable + ".wasm-component-embed" args := []string{ "component", "embed", "-w", witWorld, witPackage, - result.Executable, - "-o", result.Executable, + componentEmbedInputFile, + "-o", result.Binary, } wasmtools := goenv.Get("WASMTOOLS") @@ -888,11 +884,13 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } // wasm-tools component new embedded.wasm -o component.wasm + componentNewInputFile := result.Binary + result.Binary = result.Executable + ".wasm-component-new" args = []string{ "component", "new", - result.Executable, - "-o", result.Executable, + componentNewInputFile, + "-o", result.Binary, } if config.Options.PrintCommands != nil { From e17daf165d85beb52f59a9e09f61ea1e6a70f5cd Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Thu, 3 Oct 2024 16:03:08 -0700 Subject: [PATCH 203/444] GNUmakefile: add some more passing stdlib tests (#4492) --- GNUmakefile | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/GNUmakefile b/GNUmakefile index 34be4045be..eff22d8ab1 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -309,6 +309,7 @@ TEST_PACKAGES_FAST = \ container/list \ container/ring \ crypto/des \ + crypto/elliptic \ crypto/md5 \ crypto/rc4 \ crypto/sha1 \ @@ -322,6 +323,8 @@ TEST_PACKAGES_FAST = \ encoding/base64 \ encoding/csv \ encoding/hex \ + go/ast \ + go/format \ go/scanner \ hash \ hash/adler32 \ @@ -357,11 +360,17 @@ endif # archive/zip requires os.ReadAt, which is not yet supported on windows # bytes requires mmap # compress/flate appears to hang on wasi +# crypto/aes fails on wasi, needs panic()/recover() # crypto/hmac fails on wasi, it exits with a "slice out of range" panic # debug/plan9obj requires os.ReadAt, which is not yet supported on windows # image requires recover(), which is not yet supported on wasi # io/ioutil requires os.ReadDir, which is not yet supported on windows or wasi +# mime: fail on wasi; neds panic()/recover() +# mime/multipart: needs wasip1 syscall.FDFLAG_NONBLOCK # mime/quotedprintable requires syscall.Faccessat +# net/mail: needs wasip1 syscall.FDFLAG_NONBLOCK +# net/ntextproto: needs wasip1 syscall.FDFLAG_NONBLOCK +# regexp/syntax: fails on wasip1; needs panic()/recover() # strconv requires recover() which is not yet supported on wasi # text/tabwriter requires recover(), which is not yet supported on wasi # text/template/parse requires recover(), which is not yet supported on wasi @@ -371,14 +380,20 @@ endif TEST_PACKAGES_LINUX := \ archive/zip \ compress/flate \ + crypto/aes \ crypto/hmac \ debug/dwarf \ debug/plan9obj \ image \ io/ioutil \ + mime \ + mime/multipart \ mime/quotedprintable \ net \ + net/mail \ + net/textproto \ os/user \ + regexp/syntax \ strconv \ text/tabwriter \ text/template/parse From 407889864f1749fee46312fa191bf53ff59137ce Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Thu, 3 Oct 2024 18:10:59 -0700 Subject: [PATCH 204/444] GNUmakefile: more stdlib packages --- GNUmakefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/GNUmakefile b/GNUmakefile index eff22d8ab1..ac7f66f12e 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -303,6 +303,7 @@ TEST_PACKAGES_SLOW = \ # Standard library packages that pass tests quickly on darwin, linux, wasi, and windows TEST_PACKAGES_FAST = \ + cmp \ compress/lzw \ compress/zlib \ container/heap \ @@ -315,10 +316,12 @@ TEST_PACKAGES_FAST = \ crypto/sha1 \ crypto/sha256 \ crypto/sha512 \ + database/sql/driver \ debug/macho \ embed/internal/embedtest \ encoding \ encoding/ascii85 \ + encoding/asn1 \ encoding/base32 \ encoding/base64 \ encoding/csv \ @@ -326,6 +329,7 @@ TEST_PACKAGES_FAST = \ go/ast \ go/format \ go/scanner \ + go/version \ hash \ hash/adler32 \ hash/crc64 \ From 9da8b5c786880e47f6f96b82be7c410af6f9011b Mon Sep 17 00:00:00 2001 From: Ayke Date: Sat, 5 Oct 2024 00:33:47 +0200 Subject: [PATCH 205/444] wasm: add `//go:wasmexport` support (#4451) This adds support for the `//go:wasmexport` pragma as proposed here: https://github.com/golang/go/issues/65199 It is currently implemented only for wasip1 and wasm-unknown, but it is certainly possible to extend it to other targets like GOOS=js and wasip2. --- builder/build.go | 12 +- compileopts/config.go | 11 + compileopts/options.go | 10 + compileopts/target.go | 1 + compiler/compiler.go | 6 + compiler/goroutine.go | 273 +++++++++++++++++++++-- compiler/symbol.go | 76 +++++-- go.mod | 1 + go.sum | 7 +- main.go | 2 + main_test.go | 187 ++++++++++++++++ src/runtime/runtime_tinygowasm.go | 3 + src/runtime/runtime_wasip1.go | 13 +- src/runtime/runtime_wasm_js_scheduler.go | 4 +- src/runtime/runtime_wasm_unknown.go | 14 +- src/runtime/runtime_wasmentry.go | 100 +++++++++ src/runtime/scheduler.go | 11 +- src/runtime/scheduler_any.go | 2 +- targets/wasm-unknown.json | 1 + testdata/wasmexport-noscheduler.go | 35 +++ testdata/wasmexport.go | 52 +++++ testdata/wasmexport.txt | 11 + 22 files changed, 765 insertions(+), 67 deletions(-) create mode 100644 src/runtime/runtime_wasmentry.go create mode 100644 testdata/wasmexport-noscheduler.go create mode 100644 testdata/wasmexport.go create mode 100644 testdata/wasmexport.txt diff --git a/builder/build.go b/builder/build.go index 3f12b99911..4c49fe13cd 100644 --- a/builder/build.go +++ b/builder/build.go @@ -197,6 +197,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe ABI: config.ABI(), GOOS: config.GOOS(), GOARCH: config.GOARCH(), + BuildMode: config.BuildMode(), CodeModel: config.CodeModel(), RelocationModel: config.RelocationModel(), SizeLevel: sizeLevel, @@ -649,6 +650,13 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe result.Binary = result.Executable // final file ldflags := append(config.LDFlags(), "-o", result.Executable) + if config.Options.BuildMode == "c-shared" { + if !strings.HasPrefix(config.Triple(), "wasm32-") { + return result, fmt.Errorf("buildmode c-shared is only supported on wasm at the moment") + } + ldflags = append(ldflags, "--no-entry") + } + // Add compiler-rt dependency if needed. Usually this is a simple load from // a cache. if config.Target.RTLib == "compiler-rt" { @@ -880,7 +888,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe err := cmd.Run() if err != nil { - return fmt.Errorf("wasm-tools failed: %w", err) + return fmt.Errorf("`wasm-tools component embed` failed: %w", err) } // wasm-tools component new embedded.wasm -o component.wasm @@ -902,7 +910,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe err = cmd.Run() if err != nil { - return fmt.Errorf("wasm-tools failed: %w", err) + return fmt.Errorf("`wasm-tools component new` failed: %w", err) } } diff --git a/compileopts/config.go b/compileopts/config.go index cc1f4d61ce..44d3b005cc 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -33,6 +33,17 @@ func (c *Config) CPU() string { return c.Target.CPU } +// The current build mode (like the `-buildmode` command line flag). +func (c *Config) BuildMode() string { + if c.Options.BuildMode != "" { + return c.Options.BuildMode + } + if c.Target.BuildMode != "" { + return c.Target.BuildMode + } + return "default" +} + // Features returns a list of features this CPU supports. For example, for a // RISC-V processor, that could be "+a,+c,+m". For many targets, an empty list // will be returned. diff --git a/compileopts/options.go b/compileopts/options.go index 980097200d..b83f6f63ba 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -8,6 +8,7 @@ import ( ) var ( + validBuildModeOptions = []string{"default", "c-shared"} validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise"} validSchedulerOptions = []string{"none", "tasks", "asyncify"} validSerialOptions = []string{"none", "uart", "usb", "rtt"} @@ -26,6 +27,7 @@ type Options struct { GOMIPS string // environment variable (only used with GOARCH=mips and GOARCH=mipsle) Directory string // working dir, leave it unset to use the current working dir Target string + BuildMode string // -buildmode flag Opt string GC string PanicStrategy string @@ -61,6 +63,14 @@ type Options struct { // Verify performs a validation on the given options, raising an error if options are not valid. func (o *Options) Verify() error { + if o.BuildMode != "" { + valid := isInArray(validBuildModeOptions, o.BuildMode) + if !valid { + return fmt.Errorf(`invalid buildmode option '%s': valid values are %s`, + o.BuildMode, + strings.Join(validBuildModeOptions, ", ")) + } + } if o.GC != "" { valid := isInArray(validGCOptions, o.GC) if !valid { diff --git a/compileopts/target.go b/compileopts/target.go index 41a7babd91..ab9f871f34 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -32,6 +32,7 @@ type TargetSpec struct { GOARCH string `json:"goarch,omitempty"` SoftFloat bool // used for non-baremetal systems (GOMIPS=softfloat etc) BuildTags []string `json:"build-tags,omitempty"` + BuildMode string `json:"buildmode,omitempty"` // default build mode (if nothing specified) GC string `json:"gc,omitempty"` Scheduler string `json:"scheduler,omitempty"` Serial string `json:"serial,omitempty"` // which serial output to use (uart, usb, none) diff --git a/compiler/compiler.go b/compiler/compiler.go index 6756fe9693..752e4a5c62 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -44,6 +44,7 @@ type Config struct { ABI string GOOS string GOARCH string + BuildMode string CodeModel string RelocationModel string SizeLevel int @@ -1384,6 +1385,11 @@ func (b *builder) createFunction() { b.llvmFn.SetLinkage(llvm.InternalLinkage) b.createFunction() } + + // Create wrapper function that can be called externally. + if b.info.wasmExport != "" { + b.createWasmExport() + } } // posser is an interface that's implemented by both ssa.Value and diff --git a/compiler/goroutine.go b/compiler/goroutine.go index a235563450..701797152c 100644 --- a/compiler/goroutine.go +++ b/compiler/goroutine.go @@ -7,6 +7,7 @@ import ( "go/token" "go/types" + "github.com/tinygo-org/tinygo/compiler/llvmutil" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) @@ -101,7 +102,7 @@ func (b *builder) createGo(instr *ssa.Go) { paramBundle := b.emitPointerPack(params) var stackSize llvm.Value - callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, instr.Pos()) + callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, false, instr.Pos()) if b.AutomaticStackSize { // The stack size is not known until after linking. Call a dummy // function that will be replaced with a load from a special ELF @@ -121,6 +122,147 @@ func (b *builder) createGo(instr *ssa.Go) { b.createCall(fnType, start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.dataPtrType)}, "") } +// Create an exported wrapper function for functions with the //go:wasmexport +// pragma. This wrapper function is quite complex when the scheduler is enabled: +// it needs to start a new goroutine each time the exported function is called. +func (b *builder) createWasmExport() { + pos := b.info.wasmExportPos + if b.info.exported { + // //export really shouldn't be used anymore when //go:wasmexport is + // available, because //go:wasmexport is much better defined. + b.addError(pos, "cannot use //export and //go:wasmexport at the same time") + return + } + + const suffix = "#wasmexport" + + // Declare the exported function. + paramTypes := b.llvmFnType.ParamTypes() + exportedFnType := llvm.FunctionType(b.llvmFnType.ReturnType(), paramTypes[:len(paramTypes)-1], false) + exportedFn := llvm.AddFunction(b.mod, b.fn.RelString(nil)+suffix, exportedFnType) + b.addStandardAttributes(exportedFn) + llvmutil.AppendToGlobal(b.mod, "llvm.used", exportedFn) + exportedFn.AddFunctionAttr(b.ctx.CreateStringAttribute("wasm-export-name", b.info.wasmExport)) + + // Create a builder for this wrapper function. + builder := newBuilder(b.compilerContext, b.ctx.NewBuilder(), b.fn) + defer builder.Dispose() + + // Define this function as a separate function in DWARF + if b.Debug { + if b.fn.Syntax() != nil { + // Create debug info file if needed. + pos := b.program.Fset.Position(pos) + builder.difunc = builder.attachDebugInfoRaw(b.fn, exportedFn, suffix, pos.Filename, pos.Line) + } + builder.setDebugLocation(pos) + } + + // Create a single basic block inside of it. + bb := llvm.AddBasicBlock(exportedFn, "entry") + builder.SetInsertPointAtEnd(bb) + + // Insert an assertion to make sure this //go:wasmexport function is not + // called at a time when it is not allowed (for example, before the runtime + // is initialized). + builder.createRuntimeCall("wasmExportCheckRun", nil, "") + + if b.Scheduler == "none" { + // When the scheduler has been disabled, this is really trivial: just + // call the function. + params := exportedFn.Params() + params = append(params, llvm.ConstNull(b.dataPtrType)) // context parameter + retval := builder.CreateCall(b.llvmFnType, b.llvmFn, params, "") + if b.fn.Signature.Results() == nil { + builder.CreateRetVoid() + } else { + builder.CreateRet(retval) + } + + } else { + // The scheduler is enabled, so we need to start a new goroutine, wait + // for it to complete, and read the result value. + + // Build a function that looks like this: + // + // func foo#wasmexport(param0, param1, ..., paramN) { + // var state *stateStruct + // + // // 'done' must be explicitly initialized ('state' is not zeroed) + // state.done = false + // + // // store the parameters in the state object + // state.param0 = param0 + // state.param1 = param1 + // ... + // state.paramN = paramN + // + // // create a goroutine and push it to the runqueue + // task.start(uintptr(gowrapper), &state) + // + // // run the scheduler + // runtime.wasmExportRun(&state.done) + // + // // if there is a return value, load it and return + // return state.result + // } + + hasReturn := b.fn.Signature.Results() != nil + + // Build the state struct type. + // It stores the function parameters, the 'done' flag, and reserves + // space for a return value if needed. + stateFields := exportedFnType.ParamTypes() + numParams := len(stateFields) + stateFields = append(stateFields, b.ctx.Int1Type()) // 'done' field + if hasReturn { + stateFields = append(stateFields, b.llvmFnType.ReturnType()) + } + stateStruct := b.ctx.StructType(stateFields, false) + + // Allocate the state struct on the stack. + statePtr := builder.CreateAlloca(stateStruct, "status") + + // Initialize the 'done' field. + doneGEP := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{ + llvm.ConstInt(b.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), uint64(numParams), false), + }, "done.gep") + builder.CreateStore(llvm.ConstNull(b.ctx.Int1Type()), doneGEP) + + // Store all parameters in the state object. + for i, param := range exportedFn.Params() { + gep := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{ + llvm.ConstInt(b.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false), + }, "") + builder.CreateStore(param, gep) + } + + // Create a new goroutine and add it to the runqueue. + wrapper := b.createGoroutineStartWrapper(b.llvmFnType, b.llvmFn, "", false, true, pos) + stackSize := llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false) + taskStartFnType, taskStartFn := builder.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function)) + builder.createCall(taskStartFnType, taskStartFn, []llvm.Value{wrapper, statePtr, stackSize, llvm.Undef(b.dataPtrType)}, "") + + // Run the scheduler. + builder.createRuntimeCall("wasmExportRun", []llvm.Value{doneGEP}, "") + + // Read the return value (if any) and return to the caller of the + // //go:wasmexport function. + if hasReturn { + gep := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{ + llvm.ConstInt(b.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), uint64(numParams)+1, false), + }, "") + retval := builder.CreateLoad(b.llvmFnType.ReturnType(), gep, "retval") + builder.CreateRet(retval) + } else { + builder.CreateRetVoid() + } + } +} + // createGoroutineStartWrapper creates a wrapper for the task-based // implementation of goroutines. For example, to call a function like this: // @@ -144,7 +286,7 @@ func (b *builder) createGo(instr *ssa.Go) { // to last parameter of the function) is used for this wrapper. If hasContext is // false, the parameter bundle is assumed to have no context parameter and undef // is passed instead. -func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.Value, prefix string, hasContext bool, pos token.Pos) llvm.Value { +func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.Value, prefix string, hasContext, isWasmExport bool, pos token.Pos) llvm.Value { var wrapper llvm.Value b := &builder{ @@ -162,14 +304,18 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm. if !fn.IsAFunction().IsNil() { // See whether this wrapper has already been created. If so, return it. name := fn.Name() - wrapper = c.mod.NamedFunction(name + "$gowrapper") + wrapperName := name + "$gowrapper" + if isWasmExport { + wrapperName += "-wasmexport" + } + wrapper = c.mod.NamedFunction(wrapperName) if !wrapper.IsNil() { return llvm.ConstPtrToInt(wrapper, c.uintptrType) } // Create the wrapper. wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType}, false) - wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType) + wrapper = llvm.AddFunction(c.mod, wrapperName, wrapperType) c.addStandardAttributes(wrapper) wrapper.SetLinkage(llvm.LinkOnceODRLinkage) wrapper.SetUnnamedAddr(true) @@ -199,23 +345,110 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm. b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) } - // Create the list of params for the call. - paramTypes := fnType.ParamTypes() - if !hasContext { - paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter - } - params := b.emitPointerUnpack(wrapper.Param(0), paramTypes) - if !hasContext { - params = append(params, llvm.Undef(c.dataPtrType)) // add dummy context parameter - } + if !isWasmExport { + // Regular 'go' instruction. - // Create the call. - b.CreateCall(fnType, fn, params, "") + // Create the list of params for the call. + paramTypes := fnType.ParamTypes() + if !hasContext { + paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter + } - if c.Scheduler == "asyncify" { - b.CreateCall(deadlockType, deadlock, []llvm.Value{ - llvm.Undef(c.dataPtrType), - }, "") + params := b.emitPointerUnpack(wrapper.Param(0), paramTypes) + if !hasContext { + params = append(params, llvm.Undef(c.dataPtrType)) // add dummy context parameter + } + + // Create the call. + b.CreateCall(fnType, fn, params, "") + + if c.Scheduler == "asyncify" { + b.CreateCall(deadlockType, deadlock, []llvm.Value{ + llvm.Undef(c.dataPtrType), + }, "") + } + } else { + // Goroutine started from a //go:wasmexport pragma. + // The function looks like this: + // + // func foo$gowrapper-wasmexport(state *stateStruct) { + // // load values + // param0 := state.params[0] + // param1 := state.params[1] + // + // // call wrapped functions + // result := foo(param0, param1, ...) + // + // // store result value (if there is any) + // state.result = result + // + // // finish exported function + // state.done = true + // runtime.wasmExportExit() + // } + // + // The state object here looks like: + // + // struct state { + // param0 + // param1 + // param* // etc + // done bool + // result returnType + // } + + returnType := fnType.ReturnType() + hasReturn := returnType != b.ctx.VoidType() + statePtr := wrapper.Param(0) + + // Create the state struct (it must match the type in createWasmExport). + stateFields := fnType.ParamTypes() + numParams := len(stateFields) - 1 + stateFields = stateFields[:numParams:numParams] // strip 'context' parameter + stateFields = append(stateFields, c.ctx.Int1Type()) // 'done' bool + if hasReturn { + stateFields = append(stateFields, returnType) + } + stateStruct := b.ctx.StructType(stateFields, false) + + // Extract parameters from the state object, and call the function + // that's being wrapped. + var callParams []llvm.Value + for i := 0; i < numParams; i++ { + gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{ + llvm.ConstInt(b.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false), + }, "") + param := b.CreateLoad(stateFields[i], gep, "") + callParams = append(callParams, param) + } + callParams = append(callParams, llvm.ConstNull(c.dataPtrType)) // add 'context' parameter + result := b.CreateCall(fnType, fn, callParams, "") + + // Store the return value back into the shared state. + // Unlike regular goroutines, these special //go:wasmexport + // goroutines can return a value. + if hasReturn { + gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{ + llvm.ConstInt(c.ctx.Int32Type(), 0, false), + llvm.ConstInt(c.ctx.Int32Type(), uint64(numParams)+1, false), + }, "result.ptr") + b.CreateStore(result, gep) + } + + // Mark this function as having finished executing. + // This is important so the runtime knows the exported function + // didn't block. + doneGEP := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{ + llvm.ConstInt(c.ctx.Int32Type(), 0, false), + llvm.ConstInt(c.ctx.Int32Type(), uint64(numParams), false), + }, "done.gep") + b.CreateStore(llvm.ConstInt(b.ctx.Int1Type(), 1, false), doneGEP) + + // Call back into the runtime. This will exit the goroutine, switch + // back to the scheduler, which will in turn return from the + // //go:wasmexport function. + b.createRuntimeCall("wasmExportExit", nil, "") } } else { @@ -297,5 +530,5 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm. } // Return a ptrtoint of the wrapper, not the function itself. - return b.CreatePtrToInt(wrapper, c.uintptrType, "") + return llvm.ConstPtrToInt(wrapper, c.uintptrType) } diff --git a/compiler/symbol.go b/compiler/symbol.go index 32eb55107c..4a080cbbce 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -23,15 +23,17 @@ import ( // The linkName value contains a valid link name, even if //go:linkname is not // present. type functionInfo struct { - wasmModule string // go:wasm-module - wasmName string // wasm-export-name or wasm-import-name in the IR - linkName string // go:linkname, go:export - the IR function name - section string // go:section - object file section name - exported bool // go:export, CGo - interrupt bool // go:interrupt - nobounds bool // go:nobounds - variadic bool // go:variadic (CGo only) - inline inlineType // go:inline + wasmModule string // go:wasm-module + wasmName string // wasm-export-name or wasm-import-name in the IR + wasmExport string // go:wasmexport is defined (export is unset, this adds an exported wrapper) + wasmExportPos token.Pos // position of //go:wasmexport comment + linkName string // go:linkname, go:export - the IR function name + section string // go:section - object file section name + exported bool // go:export, CGo + interrupt bool // go:interrupt + nobounds bool // go:nobounds + variadic bool // go:variadic (CGo only) + inline inlineType // go:inline } type inlineType int @@ -241,8 +243,22 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo { // Pick the default linkName. linkName: f.RelString(nil), } + + // Check for a few runtime functions that are treated specially. + if info.linkName == "runtime.wasmEntryReactor" && c.BuildMode == "c-shared" { + info.linkName = "_initialize" + info.wasmName = "_initialize" + info.exported = true + } + if info.linkName == "runtime.wasmEntryCommand" && c.BuildMode == "default" { + info.linkName = "_start" + info.wasmName = "_start" + info.exported = true + } + // Check for //go: pragmas, which may change the link name (among others). c.parsePragmas(&info, f) + c.functionInfos[f] = info return info } @@ -296,10 +312,39 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { if len(parts) != 3 { continue } - c.checkWasmImport(f, comment.Text) + if f.Blocks != nil { + // Defined functions cannot be exported. + c.addError(f.Pos(), "can only use //go:wasmimport on declarations") + continue + } + c.checkWasmImportExport(f, comment.Text) info.exported = true info.wasmModule = parts[1] info.wasmName = parts[2] + case "//go:wasmexport": + if f.Blocks == nil { + c.addError(f.Pos(), "can only use //go:wasmexport on definitions") + continue + } + if len(parts) != 2 { + c.addError(f.Pos(), fmt.Sprintf("expected one parameter to //go:wasmimport, not %d", len(parts)-1)) + continue + } + name := parts[1] + if name == "_start" || name == "_initialize" { + c.addError(f.Pos(), fmt.Sprintf("//go:wasmexport does not allow %#v", name)) + continue + } + if c.BuildMode != "c-shared" && f.RelString(nil) == "main.main" { + c.addError(f.Pos(), fmt.Sprintf("//go:wasmexport does not allow main.main to be exported with -buildmode=%s", c.BuildMode)) + continue + } + if c.archFamily() != "wasm32" { + c.addError(f.Pos(), "//go:wasmexport is only supported on wasm") + } + c.checkWasmImportExport(f, comment.Text) + info.wasmExport = name + info.wasmExportPos = comment.Slash case "//go:inline": info.inline = inlineHint case "//go:noinline": @@ -346,22 +391,17 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { } } -// Check whether this function cannot be used in //go:wasmimport. It will add an -// error if this is the case. +// Check whether this function can be used in //go:wasmimport or +// //go:wasmexport. It will add an error if this is not the case. // // 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) { +func (c *compilerContext) checkWasmImportExport(f *ssa.Function, pragma string) { if c.pkg.Path() == "runtime" || c.pkg.Path() == "syscall/js" || c.pkg.Path() == "syscall" { // The runtime is a special case. Allow all kinds of parameters // (importantly, including pointers). return } - if f.Blocks != nil { - // Defined functions cannot be exported. - c.addError(f.Pos(), "can only use //go:wasmimport on declarations") - return - } if f.Signature.Results().Len() > 1 { c.addError(f.Signature.Results().At(1).Pos(), fmt.Sprintf("%s: too many return values", pragma)) } else if f.Signature.Results().Len() == 1 { diff --git a/go.mod b/go.mod index bf85ef3ad1..a4de141365 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-tty v0.0.4 github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 + github.com/tetratelabs/wazero v1.6.0 go.bug.st/serial v1.6.0 golang.org/x/net v0.26.0 golang.org/x/sys v0.21.0 diff --git a/go.sum b/go.sum index 7a6d1f4a97..d3c1bd310c 100644 --- a/go.sum +++ b/go.sum @@ -12,7 +12,6 @@ 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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -45,19 +44,18 @@ github.com/mattn/go-tty v0.0.4/go.mod h1:u5GGXBtZU6RQoKV8gY5W6UhMudbR5vXnUe7j3px github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5 h1:1SoBaSPudixRecmlHXb/GxmaD3fLMtHIDN13QujwQuc= github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tetratelabs/wazero v1.6.0 h1:z0H1iikCdP8t+q341xqepY4EWvHEw8Es7tlqiVzlP3g= +github.com/tetratelabs/wazero v1.6.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A= go.bug.st/serial v1.6.0 h1:mAbRGN4cKE2J5gMwsMHC2KQisdLRQssO9WSM+rbZJ8A= go.bug.st/serial v1.6.0/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -76,6 +74,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= tinygo.org/x/go-llvm v0.0.0-20240627184919-3b50c76783a8 h1:bLsZXRUBavt++CJlMN7sppNziqu3LyamESLhFJcpqFQ= tinygo.org/x/go-llvm v0.0.0-20240627184919-3b50c76783a8/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0= diff --git a/main.go b/main.go index 6f257d1b21..fe8a3fb15a 100644 --- a/main.go +++ b/main.go @@ -1500,6 +1500,7 @@ func main() { var tags buildutil.TagsFlag flag.Var(&tags, "tags", "a space-separated list of extra build tags") target := flag.String("target", "", "chip/board name or JSON target specification file") + buildMode := flag.String("buildmode", "", "build mode to use (default, c-shared)") var stackSize uint64 flag.Func("stack-size", "goroutine stack size (if unknown at compile time)", func(s string) error { size, err := bytesize.Parse(s) @@ -1608,6 +1609,7 @@ func main() { GOARM: goenv.Get("GOARM"), GOMIPS: goenv.Get("GOMIPS"), Target: *target, + BuildMode: *buildMode, StackSize: stackSize, Opt: *opt, GC: *gc, diff --git a/main_test.go b/main_test.go index c131070c8c..3bbb31da3d 100644 --- a/main_test.go +++ b/main_test.go @@ -6,6 +6,7 @@ package main import ( "bufio" "bytes" + "context" "errors" "flag" "io" @@ -21,6 +22,9 @@ import ( "time" "github.com/aykevl/go-wasm" + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" "github.com/tinygo-org/tinygo/builder" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/diagnostics" @@ -524,6 +528,189 @@ func TestWebAssembly(t *testing.T) { } } +func TestWasmExport(t *testing.T) { + t.Parallel() + + type testCase struct { + name string + target string + buildMode string + scheduler string + file string + noOutput bool + command bool // call _start (command mode) instead of _initialize + } + + tests := []testCase{ + // "command mode" WASI + { + name: "WASIp1-command", + target: "wasip1", + command: true, + }, + // "reactor mode" WASI (with -buildmode=c-shared) + { + name: "WASIp1-reactor", + target: "wasip1", + buildMode: "c-shared", + }, + // Make sure reactor mode also works without a scheduler. + { + name: "WASIp1-reactor-noscheduler", + target: "wasip1", + buildMode: "c-shared", + scheduler: "none", + file: "wasmexport-noscheduler.go", + }, + // Test -target=wasm-unknown with the default build mode (which is + // c-shared). + { + name: "wasm-unknown-reactor", + target: "wasm-unknown", + file: "wasmexport-noscheduler.go", + noOutput: true, // wasm-unknown cannot produce output + }, + // Test -target=wasm-unknown with -buildmode=default, which makes it run + // in command mode. + { + name: "wasm-unknown-command", + target: "wasm-unknown", + buildMode: "default", + file: "wasmexport-noscheduler.go", + noOutput: true, // wasm-unknown cannot produce output + command: true, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Build the wasm binary. + tmpdir := t.TempDir() + options := optionsFromTarget(tc.target, sema) + options.BuildMode = tc.buildMode + options.Scheduler = tc.scheduler + buildConfig, err := builder.NewConfig(&options) + if err != nil { + t.Fatal(err) + } + filename := "wasmexport.go" + if tc.file != "" { + filename = tc.file + } + result, err := builder.Build("testdata/"+filename, ".wasm", tmpdir, buildConfig) + if err != nil { + t.Fatal("failed to build binary:", err) + } + + // Read the wasm binary back into memory. + data, err := os.ReadFile(result.Binary) + if err != nil { + t.Fatal("could not read wasm binary: ", err) + } + + // Set up the wazero runtime. + output := &bytes.Buffer{} + ctx := context.Background() + r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter()) + defer r.Close(ctx) + config := wazero.NewModuleConfig(). + WithStdout(output).WithStderr(output). + WithStartFunctions() + + // Prepare for testing. + var mod api.Module + mustCall := func(results []uint64, err error) []uint64 { + if err != nil { + t.Error("failed to run function:", err) + } + return results + } + checkResult := func(name string, results []uint64, expected []uint64) { + if len(results) != len(expected) { + t.Errorf("%s: expected %v but got %v", name, expected, results) + } + for i, result := range results { + if result != expected[i] { + t.Errorf("%s: expected %v but got %v", name, expected, results) + break + } + } + } + runTests := func() { + // Test an exported function without params or return value. + checkResult("hello()", mustCall(mod.ExportedFunction("hello").Call(ctx)), nil) + + // Test that we can call an exported function more than once. + checkResult("add(3, 5)", mustCall(mod.ExportedFunction("add").Call(ctx, 3, 5)), []uint64{8}) + checkResult("add(7, 9)", mustCall(mod.ExportedFunction("add").Call(ctx, 7, 9)), []uint64{16}) + checkResult("add(6, 1)", mustCall(mod.ExportedFunction("add").Call(ctx, 6, 1)), []uint64{7}) + + // Test that imported functions can call exported functions + // again. + checkResult("reentrantCall(2, 3)", mustCall(mod.ExportedFunction("reentrantCall").Call(ctx, 2, 3)), []uint64{5}) + checkResult("reentrantCall(1, 8)", mustCall(mod.ExportedFunction("reentrantCall").Call(ctx, 1, 8)), []uint64{9}) + } + + // Add wasip1 module. + wasi_snapshot_preview1.MustInstantiate(ctx, r) + + // Add custom "tester" module. + callOutside := func(a, b int32) int32 { + results, err := mod.ExportedFunction("add").Call(ctx, uint64(a), uint64(b)) + if err != nil { + t.Error("could not call exported add function:", err) + } + return int32(results[0]) + } + callTestMain := func() { + runTests() + } + builder := r.NewHostModuleBuilder("tester") + builder.NewFunctionBuilder().WithFunc(callOutside).Export("callOutside") + builder.NewFunctionBuilder().WithFunc(callTestMain).Export("callTestMain") + _, err = builder.Instantiate(ctx) + if err != nil { + t.Fatal(err) + } + + // Parse and instantiate the wasm. + mod, err = r.InstantiateWithConfig(ctx, data, config) + if err != nil { + t.Fatal("could not instantiate wasm module:", err) + } + + // Initialize the module and run the tests. + if tc.command { + // Call _start (the entry point), which calls + // tester.callTestMain, which then runs all the tests. + mustCall(mod.ExportedFunction("_start").Call(ctx)) + } else { + // Run the _initialize call, because this is reactor mode wasm. + mustCall(mod.ExportedFunction("_initialize").Call(ctx)) + runTests() + } + + // Check that the output matches the expected output. + // (Skip this for wasm-unknown because it can't produce output). + if !tc.noOutput { + expectedOutput, err := os.ReadFile("testdata/wasmexport.txt") + if err != nil { + t.Fatal("could not read output file:", err) + } + actual := output.Bytes() + expectedOutput = bytes.ReplaceAll(expectedOutput, []byte("\r\n"), []byte("\n")) + actual = bytes.ReplaceAll(actual, []byte("\r\n"), []byte("\n")) + if !bytes.Equal(actual, expectedOutput) { + t.Error(string(Diff("expected", expectedOutput, "actual", actual))) + } + } + }) + } +} + func TestTest(t *testing.T) { t.Parallel() diff --git a/src/runtime/runtime_tinygowasm.go b/src/runtime/runtime_tinygowasm.go index 744e138afb..f791ffacdf 100644 --- a/src/runtime/runtime_tinygowasm.go +++ b/src/runtime/runtime_tinygowasm.go @@ -1,5 +1,8 @@ //go:build tinygo.wasm && !wasm_unknown && !wasip2 +// This file is for wasm/wasip1 and for wasm/js, which both use much of the +// WASIp1 API. + package runtime import ( diff --git a/src/runtime/runtime_wasip1.go b/src/runtime/runtime_wasip1.go index 4a1afb2abd..ad66b0d860 100644 --- a/src/runtime/runtime_wasip1.go +++ b/src/runtime/runtime_wasip1.go @@ -13,15 +13,6 @@ type timeUnit int64 //export __wasm_call_ctors func __wasm_call_ctors() -//export _start -func _start() { - // These need to be initialized early so that the heap can be initialized. - heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) - heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) - run() - __stdio_exit() -} - // Read the command line arguments from WASI. // For example, they can be passed to a program with wasmtime like this: // @@ -100,6 +91,10 @@ func ticks() timeUnit { return timeUnit(nano) } +func beforeExit() { + __stdio_exit() +} + // Implementations of WASI APIs //go:wasmimport wasi_snapshot_preview1 args_get diff --git a/src/runtime/runtime_wasm_js_scheduler.go b/src/runtime/runtime_wasm_js_scheduler.go index 0edde29eb5..94018336e4 100644 --- a/src/runtime/runtime_wasm_js_scheduler.go +++ b/src/runtime/runtime_wasm_js_scheduler.go @@ -14,7 +14,7 @@ func resume() { } wasmNested = true - scheduler() + scheduler(false) wasmNested = false } @@ -26,6 +26,6 @@ func go_scheduler() { } wasmNested = true - scheduler() + scheduler(false) wasmNested = false } diff --git a/src/runtime/runtime_wasm_unknown.go b/src/runtime/runtime_wasm_unknown.go index d307a4f87e..846b95d2a8 100644 --- a/src/runtime/runtime_wasm_unknown.go +++ b/src/runtime/runtime_wasm_unknown.go @@ -2,7 +2,8 @@ package runtime -import "unsafe" +// TODO: this is essentially reactor mode wasm. So we might want to support +// -buildmode=c-shared (and default to it). type timeUnit int64 @@ -11,14 +12,6 @@ type timeUnit int64 //export __wasm_call_ctors func __wasm_call_ctors() -//export _initialize -func _initialize() { - // These need to be initialized early so that the heap can be initialized. - heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) - heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) - initAll() -} - func init() { __wasm_call_ctors() } @@ -40,3 +33,6 @@ func sleepTicks(d timeUnit) { func ticks() timeUnit { return timeUnit(0) } + +func beforeExit() { +} diff --git a/src/runtime/runtime_wasmentry.go b/src/runtime/runtime_wasmentry.go new file mode 100644 index 0000000000..5a135f428d --- /dev/null +++ b/src/runtime/runtime_wasmentry.go @@ -0,0 +1,100 @@ +//go:build tinygo.wasm && !wasip2 && !js + +package runtime + +// Entry points for WebAssembly modules, and runtime support for +// //go:wasmexport: runtime.wasmExport* function calls are inserted by the +// compiler for //go:wasmexport support. + +import ( + "internal/task" + "unsafe" +) + +// This is the _start entry point, when using -buildmode=default. +func wasmEntryCommand() { + // These need to be initialized early so that the heap can be initialized. + heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) + heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) + wasmExportState = wasmExportStateInMain + run() + wasmExportState = wasmExportStateExited + beforeExit() +} + +// This is the _initialize entry point, when using -buildmode=c-shared. +func wasmEntryReactor() { + // This function is called before any //go:wasmexport functions are called + // to initialize everything. It must not block. + + // Initialize the heap. + heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) + heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) + initHeap() + + if hasScheduler { + // A package initializer might do funky stuff like start a goroutine and + // wait until it completes, so we have to run package initializers in a + // goroutine. + go func() { + initAll() + wasmExportState = wasmExportStateReactor + }() + scheduler(true) + if wasmExportState != wasmExportStateReactor { + // Unlikely, but if package initializers do something blocking (like + // time.Sleep()), that's a bug. + runtimePanic("package initializer blocks") + } + } else { + // There are no goroutines (except for the main one, if you can call it + // that), so we can just run all the package initializers. + initAll() + wasmExportState = wasmExportStateReactor + } +} + +// Track which state we're in: before (or during) init, running inside +// main.main, after main.main returned, or reactor mode (after init). +var wasmExportState uint8 + +const ( + wasmExportStateInit = iota + wasmExportStateInMain + wasmExportStateExited + wasmExportStateReactor +) + +func wasmExportCheckRun() { + switch wasmExportState { + case wasmExportStateInit: + runtimePanic("//go:wasmexport function called before runtime initialization") + case wasmExportStateExited: + runtimePanic("//go:wasmexport function called after main.main returned") + } +} + +// Called from within a //go:wasmexport wrapper (the one that's exported from +// the wasm module) after the goroutine has been queued. Just run the scheduler, +// and check that the goroutine finished when the scheduler is idle (as required +// by the //go:wasmexport proposal). +// +// This function is not called when the scheduler is disabled. +func wasmExportRun(done *bool) { + scheduler(true) + if !*done { + runtimePanic("//go:wasmexport function did not finish") + } +} + +// Called from the goroutine wrapper for the //go:wasmexport function. It just +// signals to the runtime that the //go:wasmexport call has finished, and can +// switch back to the wasmExportRun function. +// +// This function is not called when the scheduler is disabled. +func wasmExportExit() { + task.Pause() + + // TODO: we could cache the allocated stack so we don't have to keep + // allocating a new stack on every //go:wasmexport call. +} diff --git a/src/runtime/scheduler.go b/src/runtime/scheduler.go index 30b2da8a62..2f22876527 100644 --- a/src/runtime/scheduler.go +++ b/src/runtime/scheduler.go @@ -157,7 +157,13 @@ func removeTimer(tim *timer) bool { } // Run the scheduler until all tasks have finished. -func scheduler() { +// There are a few special cases: +// - When returnAtDeadlock is true, it also returns when there are no more +// runnable goroutines. +// - When using the asyncify scheduler, it returns when it has to wait +// (JavaScript uses setTimeout so the scheduler must return to the JS +// environment). +func scheduler(returnAtDeadlock bool) { // Main scheduler loop. var now timeUnit for !schedulerDone { @@ -193,6 +199,9 @@ func scheduler() { t := runqueue.Pop() if t == nil { if sleepQueue == nil && timerQueue == nil { + if returnAtDeadlock { + return + } if asyncScheduler { // JavaScript is treated specially, see below. return diff --git a/src/runtime/scheduler_any.go b/src/runtime/scheduler_any.go index 0911a2dc73..5e969f84ff 100644 --- a/src/runtime/scheduler_any.go +++ b/src/runtime/scheduler_any.go @@ -25,7 +25,7 @@ func run() { callMain() schedulerDone = true }() - scheduler() + scheduler(false) } const hasScheduler = true diff --git a/targets/wasm-unknown.json b/targets/wasm-unknown.json index 93a4d391e0..92c166ee52 100644 --- a/targets/wasm-unknown.json +++ b/targets/wasm-unknown.json @@ -3,6 +3,7 @@ "cpu": "generic", "features": "+mutable-globals,+nontrapping-fptoint,+sign-ext,-bulk-memory", "build-tags": ["tinygo.wasm", "wasm_unknown"], + "buildmode": "c-shared", "goos": "linux", "goarch": "arm", "linker": "wasm-ld", diff --git a/testdata/wasmexport-noscheduler.go b/testdata/wasmexport-noscheduler.go new file mode 100644 index 0000000000..844c8d1588 --- /dev/null +++ b/testdata/wasmexport-noscheduler.go @@ -0,0 +1,35 @@ +package main + +func init() { + println("called init") +} + +//go:wasmimport tester callTestMain +func callTestMain() + +func main() { + // main.main is not used when using -buildmode=c-shared. + callTestMain() +} + +//go:wasmexport hello +func hello() { + println("hello!") +} + +//go:wasmexport add +func add(a, b int) int { + println("called add:", a, b) + return a + b +} + +//go:wasmimport tester callOutside +func callOutside(a, b int) int + +//go:wasmexport reentrantCall +func reentrantCall(a, b int) int { + println("reentrantCall:", a, b) + result := callOutside(a, b) + println("reentrantCall result:", result) + return result +} diff --git a/testdata/wasmexport.go b/testdata/wasmexport.go new file mode 100644 index 0000000000..9065d6e926 --- /dev/null +++ b/testdata/wasmexport.go @@ -0,0 +1,52 @@ +package main + +import "time" + +func init() { + println("called init") + go adder() +} + +//go:wasmimport tester callTestMain +func callTestMain() + +func main() { + // main.main is not used when using -buildmode=c-shared. + callTestMain() +} + +//go:wasmexport hello +func hello() { + println("hello!") +} + +//go:wasmexport add +func add(a, b int) int { + println("called add:", a, b) + addInputs <- a + addInputs <- b + return <-addOutput +} + +var addInputs = make(chan int) +var addOutput = make(chan int) + +func adder() { + for { + a := <-addInputs + b := <-addInputs + time.Sleep(time.Millisecond) + addOutput <- a + b + } +} + +//go:wasmimport tester callOutside +func callOutside(a, b int) int + +//go:wasmexport reentrantCall +func reentrantCall(a, b int) int { + println("reentrantCall:", a, b) + result := callOutside(a, b) + println("reentrantCall result:", result) + return result +} diff --git a/testdata/wasmexport.txt b/testdata/wasmexport.txt new file mode 100644 index 0000000000..484a0ce8d8 --- /dev/null +++ b/testdata/wasmexport.txt @@ -0,0 +1,11 @@ +called init +hello! +called add: 3 5 +called add: 7 9 +called add: 6 1 +reentrantCall: 2 3 +called add: 2 3 +reentrantCall result: 5 +reentrantCall: 1 8 +called add: 1 8 +reentrantCall result: 9 From 453a1d35c315cb4b2519c86e4d53a7699bbcdea2 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 5 Oct 2024 02:36:47 +0200 Subject: [PATCH 206/444] compiler, runtime: enable go:wasmexport for wasip2 (#4499) * compiler: prefer go:wasmexport over go:export * runtime, targets/wasip2: enable -buildmode=c-shared for wasip2 * runtime: rename import from wasi_run to wasiclirun (PR feedback) --- compiler/symbol.go | 7 +++++++ ...untime_wasm_wasip2.go => runtime_wasip2.go} | 18 +++++++++++------- src/runtime/runtime_wasmentry.go | 2 +- targets/wasip2.json | 1 + 4 files changed, 20 insertions(+), 8 deletions(-) rename src/runtime/{runtime_wasm_wasip2.go => runtime_wasip2.go} (75%) diff --git a/compiler/symbol.go b/compiler/symbol.go index 4a080cbbce..216aa6eecb 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -389,6 +389,13 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { } } } + + // If both //go:wasmexport and //go:export or //export are declared, + // only honor go:wasmexport. + if info.wasmExport != "" { + // TODO: log warning? + info.exported = false + } } // Check whether this function can be used in //go:wasmimport or diff --git a/src/runtime/runtime_wasm_wasip2.go b/src/runtime/runtime_wasip2.go similarity index 75% rename from src/runtime/runtime_wasm_wasip2.go rename to src/runtime/runtime_wasip2.go index 57e6623d33..ba8f52100b 100644 --- a/src/runtime/runtime_wasm_wasip2.go +++ b/src/runtime/runtime_wasip2.go @@ -6,18 +6,19 @@ import ( "unsafe" "internal/wasi/cli/v0.2.0/environment" + wasiclirun "internal/wasi/cli/v0.2.0/run" monotonicclock "internal/wasi/clocks/v0.2.0/monotonic-clock" + + "internal/cm" ) type timeUnit int64 -//export wasi:cli/run@0.2.0#run -func __wasi_cli_run_run() uint32 { - // These need to be initialized early so that the heap can be initialized. - heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) - heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) - run() - return 0 +func init() { + wasiclirun.Exports.Run = func() cm.BoolResult { + callMain() + return false + } } var args []string @@ -51,3 +52,6 @@ func sleepTicks(d timeUnit) { func ticks() timeUnit { return timeUnit(monotonicclock.Now()) } + +func beforeExit() { +} diff --git a/src/runtime/runtime_wasmentry.go b/src/runtime/runtime_wasmentry.go index 5a135f428d..7bb1e1b44e 100644 --- a/src/runtime/runtime_wasmentry.go +++ b/src/runtime/runtime_wasmentry.go @@ -1,4 +1,4 @@ -//go:build tinygo.wasm && !wasip2 && !js +//go:build tinygo.wasm && !js package runtime diff --git a/targets/wasip2.json b/targets/wasip2.json index b32a68197e..4b0e675914 100644 --- a/targets/wasip2.json +++ b/targets/wasip2.json @@ -3,6 +3,7 @@ "cpu": "generic", "features": "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext", "build-tags": ["tinygo.wasm", "wasip2"], + "buildmode": "c-shared", "goos": "linux", "goarch": "arm", "linker": "wasm-ld", From d1b7238a36738f2b8359cd907ef9c2b9fce28c28 Mon Sep 17 00:00:00 2001 From: Ayke Date: Sat, 5 Oct 2024 18:53:38 +0200 Subject: [PATCH 207/444] Truly ignore `//export` when `//go:wasmexport` is used (#4500) --- compiler/symbol.go | 227 +++++++++++++++++++++++---------------------- 1 file changed, 115 insertions(+), 112 deletions(-) diff --git a/compiler/symbol.go b/compiler/symbol.go index 216aa6eecb..c2007cfd41 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -273,129 +273,132 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { if syntax == nil { return } + + // Read all pragmas of this function. + var pragmas []*ast.Comment + hasWasmExport := false if decl, ok := syntax.(*ast.FuncDecl); ok && decl.Doc != nil { for _, comment := range decl.Doc.List { text := comment.Text - if strings.HasPrefix(text, "//export ") { - // Rewrite '//export' to '//go:export' for compatibility with - // gc. - text = "//go:" + text[2:] + if strings.HasPrefix(text, "//go:") || strings.HasPrefix(text, "//export ") { + pragmas = append(pragmas, comment) + if strings.HasPrefix(comment.Text, "//go:wasmexport ") { + hasWasmExport = true + } } - if !strings.HasPrefix(text, "//go:") { + } + } + + // Parse each pragma. + for _, comment := range pragmas { + parts := strings.Fields(comment.Text) + switch parts[0] { + case "//export", "//go:export": + if len(parts) != 2 { + continue + } + if hasWasmExport { + // //go:wasmexport overrides //export. continue } - parts := strings.Fields(text) - switch parts[0] { - case "//go:export": - if len(parts) != 2 { - continue - } - info.linkName = parts[1] - info.wasmName = info.linkName - info.exported = true - case "//go:interrupt": - if hasUnsafeImport(f.Pkg.Pkg) { - info.interrupt = true - } - case "//go:wasm-module": - // Alternative comment for setting the import module. - // This is deprecated, use //go:wasmimport instead. - if len(parts) != 2 { - continue - } - info.wasmModule = parts[1] - case "//go:wasmimport": - // Import a WebAssembly function, for example a WASI function. - // Original proposal: https://github.com/golang/go/issues/38248 - // Allow globally: https://github.com/golang/go/issues/59149 - if len(parts) != 3 { - continue - } - if f.Blocks != nil { - // Defined functions cannot be exported. - c.addError(f.Pos(), "can only use //go:wasmimport on declarations") - continue - } - c.checkWasmImportExport(f, comment.Text) - info.exported = true - info.wasmModule = parts[1] - info.wasmName = parts[2] - case "//go:wasmexport": - if f.Blocks == nil { - c.addError(f.Pos(), "can only use //go:wasmexport on definitions") - continue - } - if len(parts) != 2 { - c.addError(f.Pos(), fmt.Sprintf("expected one parameter to //go:wasmimport, not %d", len(parts)-1)) - continue - } - name := parts[1] - if name == "_start" || name == "_initialize" { - c.addError(f.Pos(), fmt.Sprintf("//go:wasmexport does not allow %#v", name)) - continue - } - if c.BuildMode != "c-shared" && f.RelString(nil) == "main.main" { - c.addError(f.Pos(), fmt.Sprintf("//go:wasmexport does not allow main.main to be exported with -buildmode=%s", c.BuildMode)) - continue - } - if c.archFamily() != "wasm32" { - c.addError(f.Pos(), "//go:wasmexport is only supported on wasm") - } - c.checkWasmImportExport(f, comment.Text) - info.wasmExport = name - info.wasmExportPos = comment.Slash - case "//go:inline": - info.inline = inlineHint - case "//go:noinline": + info.linkName = parts[1] + info.wasmName = info.linkName + info.exported = true + case "//go:interrupt": + if hasUnsafeImport(f.Pkg.Pkg) { + info.interrupt = true + } + case "//go:wasm-module": + // Alternative comment for setting the import module. + // This is deprecated, use //go:wasmimport instead. + if len(parts) != 2 { + continue + } + info.wasmModule = parts[1] + case "//go:wasmimport": + // Import a WebAssembly function, for example a WASI function. + // Original proposal: https://github.com/golang/go/issues/38248 + // Allow globally: https://github.com/golang/go/issues/59149 + if len(parts) != 3 { + continue + } + if f.Blocks != nil { + // Defined functions cannot be exported. + c.addError(f.Pos(), "can only use //go:wasmimport on declarations") + continue + } + c.checkWasmImportExport(f, comment.Text) + info.exported = true + info.wasmModule = parts[1] + info.wasmName = parts[2] + case "//go:wasmexport": + if f.Blocks == nil { + c.addError(f.Pos(), "can only use //go:wasmexport on definitions") + continue + } + if len(parts) != 2 { + c.addError(f.Pos(), fmt.Sprintf("expected one parameter to //go:wasmimport, not %d", len(parts)-1)) + continue + } + name := parts[1] + if name == "_start" || name == "_initialize" { + c.addError(f.Pos(), fmt.Sprintf("//go:wasmexport does not allow %#v", name)) + continue + } + if c.BuildMode != "c-shared" && f.RelString(nil) == "main.main" { + c.addError(f.Pos(), fmt.Sprintf("//go:wasmexport does not allow main.main to be exported with -buildmode=%s", c.BuildMode)) + continue + } + if c.archFamily() != "wasm32" { + c.addError(f.Pos(), "//go:wasmexport is only supported on wasm") + } + c.checkWasmImportExport(f, comment.Text) + info.wasmExport = name + info.wasmExportPos = comment.Slash + case "//go:inline": + info.inline = inlineHint + case "//go:noinline": + info.inline = inlineNone + case "//go:linkname": + if len(parts) != 3 || parts[1] != f.Name() { + continue + } + // Only enable go:linkname when the package imports "unsafe". + // This is a slightly looser requirement than what gc uses: gc + // requires the file to import "unsafe", not the package as a + // whole. + if hasUnsafeImport(f.Pkg.Pkg) { + info.linkName = parts[2] + } + case "//go:section": + // Only enable go:section when the package imports "unsafe". + // go:section also implies go:noinline since inlining could + // move the code to a different section than that requested. + if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) { + info.section = parts[1] info.inline = inlineNone - case "//go:linkname": - if len(parts) != 3 || parts[1] != f.Name() { - continue - } - // Only enable go:linkname when the package imports "unsafe". - // This is a slightly looser requirement than what gc uses: gc - // requires the file to import "unsafe", not the package as a - // whole. - if hasUnsafeImport(f.Pkg.Pkg) { - info.linkName = parts[2] - } - case "//go:section": - // Only enable go:section when the package imports "unsafe". - // go:section also implies go:noinline since inlining could - // move the code to a different section than that requested. - if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) { - info.section = parts[1] - info.inline = inlineNone - } - case "//go:nobounds": - // Skip bounds checking in this function. Useful for some - // runtime functions. - // This is somewhat dangerous and thus only imported in packages - // that import unsafe. - if hasUnsafeImport(f.Pkg.Pkg) { - info.nobounds = true - } - case "//go:variadic": - // The //go:variadic pragma is emitted by the CGo preprocessing - // pass for C variadic functions. This includes both explicit - // (with ...) and implicit (no parameters in signature) - // functions. - if strings.HasPrefix(f.Name(), "C.") { - // This prefix cannot naturally be created, it must have - // been created as a result of CGo preprocessing. - info.variadic = true - } + } + case "//go:nobounds": + // Skip bounds checking in this function. Useful for some + // runtime functions. + // This is somewhat dangerous and thus only imported in packages + // that import unsafe. + if hasUnsafeImport(f.Pkg.Pkg) { + info.nobounds = true + } + case "//go:variadic": + // The //go:variadic pragma is emitted by the CGo preprocessing + // pass for C variadic functions. This includes both explicit + // (with ...) and implicit (no parameters in signature) + // functions. + if strings.HasPrefix(f.Name(), "C.") { + // This prefix cannot naturally be created, it must have + // been created as a result of CGo preprocessing. + info.variadic = true } } } - - // If both //go:wasmexport and //go:export or //export are declared, - // only honor go:wasmexport. - if info.wasmExport != "" { - // TODO: log warning? - info.exported = false - } } // Check whether this function can be used in //go:wasmimport or From c77ed8e50ee3583ba4e45dfa044abfad075d47f5 Mon Sep 17 00:00:00 2001 From: BCG Date: Wed, 1 Feb 2023 07:49:26 -0500 Subject: [PATCH 208/444] Added mstats fields --- src/runtime/mstats.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/runtime/mstats.go b/src/runtime/mstats.go index 2699e08b3f..7a6f8e637f 100644 --- a/src/runtime/mstats.go +++ b/src/runtime/mstats.go @@ -9,6 +9,11 @@ package runtime type MemStats struct { // General statistics. + // Alloc is bytes of allocated heap objects. + // + // This is the same as HeapAlloc (see below). + Alloc uint64 + // Sys is the total bytes of memory obtained from the OS. // // Sys is the sum of the XSys fields below. Sys measures the @@ -18,6 +23,19 @@ type MemStats struct { // Heap memory statistics. + // HeapAlloc is bytes of allocated heap objects. + // + // "Allocated" heap objects include all reachable objects, as + // well as unreachable objects that the garbage collector has + // not yet freed. Specifically, HeapAlloc increases as heap + // objects are allocated and decreases as the heap is swept + // and unreachable objects are freed. Sweeping occurs + // incrementally between GC cycles, so these two processes + // occur simultaneously, and as a result HeapAlloc tends to + // change smoothly (in contrast with the sawtooth that is + // typical of stop-the-world garbage collectors). + HeapAlloc uint64 + // HeapSys is bytes of heap memory, total. // // In TinyGo unlike upstream Go, we make no distinction between From fbb125131db3d86b3918c51ac77204debed01fda Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Mon, 7 Oct 2024 13:48:14 -0700 Subject: [PATCH 209/444] runtime: track Memstats.HeapAlloc for gc_blocks --- src/runtime/gc_blocks.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/runtime/gc_blocks.go b/src/runtime/gc_blocks.go index 7db6b7a1cf..1cc2384948 100644 --- a/src/runtime/gc_blocks.go +++ b/src/runtime/gc_blocks.go @@ -53,8 +53,10 @@ var ( nextAlloc gcBlock // the next block that should be tried by the allocator endBlock gcBlock // the block just past the end of the available space gcTotalAlloc uint64 // total number of bytes allocated + gcTotalBlocks uint64 // total number of allocated blocks gcMallocs uint64 // total number of allocations gcFrees uint64 // total number of objects freed + gcFreedBlocks uint64 // total number of freed blocks ) // zeroSizedAlloc is just a sentinel that gets returned when allocating 0 bytes. @@ -285,6 +287,7 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { gcMallocs++ neededBlocks := (size + (bytesPerBlock - 1)) / bytesPerBlock + gcTotalBlocks += uint64(neededBlocks) // Continue looping until a run of free blocks has been found that fits the // requested size. @@ -619,6 +622,7 @@ func markRoot(addr, root uintptr) { // It returns how many bytes are free in the heap after the sweep. func sweep() (freeBytes uintptr) { freeCurrentObject := false + var freed uint64 for block := gcBlock(0); block < endBlock; block++ { switch block.state() { case blockStateHead: @@ -626,13 +630,13 @@ func sweep() (freeBytes uintptr) { block.markFree() freeCurrentObject = true gcFrees++ - freeBytes += bytesPerBlock + freed++ case blockStateTail: if freeCurrentObject { // This is a tail object following an unmarked head. // Free it now. block.markFree() - freeBytes += bytesPerBlock + freed++ } case blockStateMark: // This is a marked object. The next tail blocks must not be freed, @@ -644,6 +648,8 @@ func sweep() (freeBytes uintptr) { freeBytes += bytesPerBlock } } + gcFreedBlocks += freed + freeBytes += uintptr(freed) * bytesPerBlock return } @@ -690,6 +696,8 @@ func ReadMemStats(m *MemStats) { m.Mallocs = gcMallocs m.Frees = gcFrees m.Sys = uint64(heapEnd - heapStart) + m.HeapAlloc = (gcTotalBlocks - gcFreedBlocks) * uint64(bytesPerBlock) + m.Alloc = m.HeapAlloc } func SetFinalizer(obj interface{}, finalizer interface{}) { From 158be02ef7bacaa88c1961b21e41f6f965ffbf7c Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Mon, 7 Oct 2024 17:26:09 -0700 Subject: [PATCH 210/444] builder: fix sizes --- builder/sizes_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/builder/sizes_test.go b/builder/sizes_test.go index 64cd4e77bf..628ddee896 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -41,9 +41,9 @@ func TestBinarySize(t *testing.T) { // This is a small number of very diverse targets that we want to test. tests := []sizeTest{ // microcontrollers - {"hifive1b", "examples/echo", 4484, 280, 0, 2252}, - {"microbit", "examples/serial", 2808, 388, 8, 2256}, - {"wioterminal", "examples/pininterrupt", 6064, 1484, 116, 6816}, + {"hifive1b", "examples/echo", 4560, 280, 0, 2268}, + {"microbit", "examples/serial", 2868, 388, 8, 2272}, + {"wioterminal", "examples/pininterrupt", 6104, 1484, 116, 6832}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the From e62fc43b055d5a68418f0c852acc150a2cd688df Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sun, 6 Oct 2024 17:06:35 +0200 Subject: [PATCH 211/444] crypto/x509/internal/macos: add package stub to build crypto/x509 on macOS --- GNUmakefile | 1 + loader/goroot.go | 51 ++++--- src/crypto/x509/internal/macos/macos.go | 185 ++++++++++++++++++++++++ 3 files changed, 213 insertions(+), 24 deletions(-) create mode 100644 src/crypto/x509/internal/macos/macos.go diff --git a/GNUmakefile b/GNUmakefile index ac7f66f12e..110ab5463f 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -310,6 +310,7 @@ TEST_PACKAGES_FAST = \ container/list \ container/ring \ crypto/des \ + crypto/ecdsa \ crypto/elliptic \ crypto/md5 \ crypto/rc4 \ diff --git a/loader/goroot.go b/loader/goroot.go index c7ac029d3d..05eeeda190 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -229,30 +229,33 @@ func needsSyscallPackage(buildTags []string) bool { // means use the TinyGo version. func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { paths := map[string]bool{ - "": true, - "crypto/": true, - "crypto/rand/": false, - "crypto/tls/": false, - "device/": false, - "examples/": false, - "internal/": true, - "internal/abi/": false, - "internal/binary/": false, - "internal/bytealg/": false, - "internal/cm/": false, - "internal/fuzz/": false, - "internal/reflectlite/": false, - "internal/task/": false, - "internal/wasi/": false, - "machine/": false, - "net/": true, - "net/http/": false, - "os/": true, - "reflect/": false, - "runtime/": false, - "sync/": true, - "testing/": true, - "unique/": false, + "": true, + "crypto/": true, + "crypto/rand/": false, + "crypto/tls/": false, + "crypto/x509/": true, + "crypto/x509/internal/": true, + "crypto/x509/internal/macos/": false, + "device/": false, + "examples/": false, + "internal/": true, + "internal/abi/": false, + "internal/binary/": false, + "internal/bytealg/": false, + "internal/cm/": false, + "internal/fuzz/": false, + "internal/reflectlite/": false, + "internal/task/": false, + "internal/wasi/": false, + "machine/": false, + "net/": true, + "net/http/": false, + "os/": true, + "reflect/": false, + "runtime/": false, + "sync/": true, + "testing/": true, + "unique/": false, } if goMinor >= 19 { diff --git a/src/crypto/x509/internal/macos/macos.go b/src/crypto/x509/internal/macos/macos.go new file mode 100644 index 0000000000..e9ec2ef843 --- /dev/null +++ b/src/crypto/x509/internal/macos/macos.go @@ -0,0 +1,185 @@ +package macos + +import ( + "errors" + "time" +) + +// Exported symbols copied from Big Go, but stripped of functionality. +// Allows building of crypto/x509 on macOS. + +const ( + ErrSecCertificateExpired = -67818 + ErrSecHostNameMismatch = -67602 + ErrSecNotTrusted = -67843 +) + +var ErrNoTrustSettings = errors.New("no trust settings found") +var SecPolicyAppleSSL = StringToCFString("1.2.840.113635.100.1.3") // defined by POLICYMACRO +var SecPolicyOid = StringToCFString("SecPolicyOid") +var SecTrustSettingsPolicy = StringToCFString("kSecTrustSettingsPolicy") +var SecTrustSettingsPolicyString = StringToCFString("kSecTrustSettingsPolicyString") +var SecTrustSettingsResultKey = StringToCFString("kSecTrustSettingsResult") + +func CFArrayAppendValue(array CFRef, val CFRef) {} + +func CFArrayGetCount(array CFRef) int { + return 0 +} + +func CFDataGetBytePtr(data CFRef) uintptr { + return 0 +} + +func CFDataGetLength(data CFRef) int { + return 0 +} + +func CFDataToSlice(data CFRef) []byte { + return nil +} + +func CFEqual(a, b CFRef) bool { + return false +} + +func CFErrorGetCode(errRef CFRef) int { + return 0 +} + +func CFNumberGetValue(num CFRef) (int32, error) { + return 0, errors.New("not implemented") +} + +func CFRelease(ref CFRef) {} + +func CFStringToString(ref CFRef) string { + return "" +} + +func ReleaseCFArray(array CFRef) {} + +func SecCertificateCopyData(cert CFRef) ([]byte, error) { + return nil, errors.New("not implemented") +} + +func SecTrustEvaluateWithError(trustObj CFRef) (int, error) { + return 0, errors.New("not implemented") +} + +func SecTrustGetCertificateCount(trustObj CFRef) int { + return 0 +} + +func SecTrustGetResult(trustObj CFRef, result CFRef) (CFRef, CFRef, error) { + return 0, 0, errors.New("not implemented") +} + +func SecTrustSetVerifyDate(trustObj CFRef, dateRef CFRef) error { + return errors.New("not implemented") +} + +type CFRef uintptr + +func BytesToCFData(b []byte) CFRef { + return 0 +} + +func CFArrayCreateMutable() CFRef { + return 0 +} + +func CFArrayGetValueAtIndex(array CFRef, index int) CFRef { + return 0 +} + +func CFDateCreate(seconds float64) CFRef { + return 0 +} + +func CFDictionaryGetValueIfPresent(dict CFRef, key CFString) (value CFRef, ok bool) { + return 0, false +} + +func CFErrorCopyDescription(errRef CFRef) CFRef { + return 0 +} + +func CFStringCreateExternalRepresentation(strRef CFRef) (CFRef, error) { + return 0, errors.New("not implemented") +} + +func SecCertificateCreateWithData(b []byte) (CFRef, error) { + return 0, errors.New("not implemented") +} + +func SecPolicyCreateSSL(name string) (CFRef, error) { + return 0, errors.New("not implemented") +} + +func SecTrustCreateWithCertificates(certs CFRef, policies CFRef) (CFRef, error) { + return 0, errors.New("not implemented") +} + +func SecTrustEvaluate(trustObj CFRef) (CFRef, error) { + return 0, errors.New("not implemented") +} + +func SecTrustGetCertificateAtIndex(trustObj CFRef, i int) (CFRef, error) { + return 0, errors.New("not implemented") +} + +func SecTrustSettingsCopyCertificates(domain SecTrustSettingsDomain) (certArray CFRef, err error) { + return 0, errors.New("not implemented") +} + +func SecTrustSettingsCopyTrustSettings(cert CFRef, domain SecTrustSettingsDomain) (trustSettings CFRef, err error) { + return 0, errors.New("not implemented") +} + +func TimeToCFDateRef(t time.Time) CFRef { + return 0 +} + +type CFString CFRef + +func StringToCFString(s string) CFString { + return 0 +} + +type OSStatus struct { + // Has unexported fields. +} + +func (s OSStatus) Error() string + +type SecTrustResultType int32 + +const ( + SecTrustResultInvalid SecTrustResultType = iota + SecTrustResultProceed + SecTrustResultConfirm // deprecated + SecTrustResultDeny + SecTrustResultUnspecified + SecTrustResultRecoverableTrustFailure + SecTrustResultFatalTrustFailure + SecTrustResultOtherError +) + +type SecTrustSettingsDomain int32 + +const ( + SecTrustSettingsDomainUser SecTrustSettingsDomain = iota + SecTrustSettingsDomainAdmin + SecTrustSettingsDomainSystem +) + +type SecTrustSettingsResult int32 + +const ( + SecTrustSettingsResultInvalid SecTrustSettingsResult = iota + SecTrustSettingsResultTrustRoot + SecTrustSettingsResultTrustAsRoot + SecTrustSettingsResultDeny + SecTrustSettingsResultUnspecified +) From 62c1555aa867508ebf443a7d6c3b61756c7fe368 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Tue, 8 Oct 2024 17:09:42 +0200 Subject: [PATCH 212/444] targets: add bulk memory flags to wasm-unknown target since basically every runtime has it now Signed-off-by: deadprogram --- targets/wasm-unknown.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/targets/wasm-unknown.json b/targets/wasm-unknown.json index 92c166ee52..59cd94db48 100644 --- a/targets/wasm-unknown.json +++ b/targets/wasm-unknown.json @@ -1,7 +1,7 @@ { "llvm-target": "wasm32-unknown-unknown", "cpu": "generic", - "features": "+mutable-globals,+nontrapping-fptoint,+sign-ext,-bulk-memory", + "features": "+mutable-globals,+nontrapping-fptoint,+sign-ext", "build-tags": ["tinygo.wasm", "wasm_unknown"], "buildmode": "c-shared", "goos": "linux", @@ -13,7 +13,6 @@ "gc": "leaking", "default-stack-size": 4096, "cflags": [ - "-mno-bulk-memory", "-mnontrapping-fptoint", "-msign-ext" ], From 87c6e19921ae916a7af53e1695e37995aa112607 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Tue, 8 Oct 2024 09:12:01 -0700 Subject: [PATCH 213/444] runtime: add HeapAlloc to gc_leaking --- src/runtime/gc_leaking.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/runtime/gc_leaking.go b/src/runtime/gc_leaking.go index 5c9594277c..71c4258b67 100644 --- a/src/runtime/gc_leaking.go +++ b/src/runtime/gc_leaking.go @@ -79,6 +79,9 @@ func ReadMemStats(m *MemStats) { m.Mallocs = gcMallocs m.Frees = gcFrees m.Sys = uint64(heapEnd - heapStart) + // no free -- current in use heap is the total allocated + m.HeapAlloc = gcTotalAlloc + m.Alloc = m.HeapAlloc } func GC() { From 505e68057d86e154f124f1ebd1921c33f9f4b2e3 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 11 Oct 2024 14:57:38 +0200 Subject: [PATCH 214/444] nix: use LLVM 18 instead of LLVM 17 This should fix some CI issues we're currently having. Thanks to @sylv-io for discovering that we need to remove LLVM to avoid an Xtensa backend linker error. --- .github/workflows/nix.yml | 5 +++++ flake.lock | 8 ++++---- flake.nix | 12 ++++++------ 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 62da55672a..7ad4911d6c 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -15,6 +15,11 @@ jobs: nix-test: runs-on: ubuntu-latest steps: + - name: Uninstall system LLVM + # Hack to work around issue where we still include system headers for + # some reason. + # See: https://github.com/tinygo-org/tinygo/pull/4516#issuecomment-2416363668 + run: sudo apt-get remove llvm-18 - name: Checkout uses: actions/checkout@v4 - name: Pull musl diff --git a/flake.lock b/flake.lock index 6c453ad9bc..d541b769ca 100644 --- a/flake.lock +++ b/flake.lock @@ -20,16 +20,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1703068421, - "narHash": "sha256-WSw5Faqlw75McIflnl5v7qVD/B3S2sLh+968bpOGrWA=", + "lastModified": 1728500571, + "narHash": "sha256-dOymOQ3AfNI4Z337yEwHGohrVQb4yPODCW9MDUyAc4w=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d65bceaee0fb1e64363f7871bc43dc1c6ecad99f", + "rev": "d51c28603def282a24fa034bcb007e2bcb5b5dd0", "type": "github" }, "original": { "id": "nixpkgs", - "ref": "nixos-23.11", + "ref": "nixos-24.05", "type": "indirect" } }, diff --git a/flake.nix b/flake.nix index 9ba3278401..25ffc70205 100644 --- a/flake.nix +++ b/flake.nix @@ -35,7 +35,7 @@ inputs = { # Use a recent stable release, but fix the version to make it reproducible. # This version should be updated from time to time. - nixpkgs.url = "nixpkgs/nixos-23.11"; + nixpkgs.url = "nixpkgs/nixos-24.05"; flake-utils.url = "github:numtide/flake-utils"; }; outputs = { self, nixpkgs, flake-utils }: @@ -49,11 +49,11 @@ buildInputs = [ # These dependencies are required for building tinygo (go install). go - llvmPackages_17.llvm - llvmPackages_17.libclang + llvmPackages_18.llvm + llvmPackages_18.libclang # Additional dependencies needed at runtime, for building and/or # flashing. - llvmPackages_17.lld + llvmPackages_18.lld avrdude binaryen # Additional dependencies needed for on-chip debugging. @@ -68,7 +68,7 @@ # Without setting these explicitly, Homebrew versions might be used # or the default `ar` and `nm` tools might be used (which don't # support wasi). - export CLANG="clang-17 -resource-dir ${llvmPackages_17.clang.cc.lib}/lib/clang/17" + export CLANG="clang-18 -resource-dir ${llvmPackages_18.clang.cc.lib}/lib/clang/18" export LLVM_AR=llvm-ar export LLVM_NM=llvm-nm @@ -77,7 +77,7 @@ export MD5SUM=md5sum # Ugly hack to make the Clang resources directory available. - export GOFLAGS="\"-ldflags=-X github.com/tinygo-org/tinygo/goenv.clangResourceDir=${llvmPackages_17.clang.cc.lib}/lib/clang/17\" -tags=llvm17" + export GOFLAGS="\"-ldflags=-X github.com/tinygo-org/tinygo/goenv.clangResourceDir=${llvmPackages_18.clang.cc.lib}/lib/clang/18\" -tags=llvm18" ''; }; } From a0d4ecb607478c9981673cd86716a535e4f6df15 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Thu, 17 Oct 2024 08:07:30 -0700 Subject: [PATCH 215/444] internal/wasm-tools: update wasm-tools-go to v0.3.0 --- internal/wasm-tools/go.mod | 9 ++++++++- internal/wasm-tools/go.sum | 29 +++++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/internal/wasm-tools/go.mod b/internal/wasm-tools/go.mod index eadddcbbba..fbc3b20f07 100644 --- a/internal/wasm-tools/go.mod +++ b/internal/wasm-tools/go.mod @@ -2,11 +2,18 @@ module github.com/tinygo-org/tinygo/internal/tools go 1.22.4 -require github.com/bytecodealliance/wasm-tools-go v0.2.0 +require github.com/bytecodealliance/wasm-tools-go v0.3.0 require ( github.com/coreos/go-semver v0.3.1 // indirect + github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/regclient/regclient v0.7.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/ulikunitz/xz v0.5.12 // indirect github.com/urfave/cli/v3 v3.0.0-alpha9 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect golang.org/x/mod v0.21.0 // indirect + golang.org/x/sys v0.26.0 // indirect ) diff --git a/internal/wasm-tools/go.sum b/internal/wasm-tools/go.sum index d4fb71dfb0..72f90a316d 100644 --- a/internal/wasm-tools/go.sum +++ b/internal/wasm-tools/go.sum @@ -1,15 +1,32 @@ -github.com/bytecodealliance/wasm-tools-go v0.2.0 h1:JdmiZew7ewHjf+ZGGRE4gZM85Ad/PGW/5I57hepEOjQ= -github.com/bytecodealliance/wasm-tools-go v0.2.0/go.mod h1:2GnJCUlcDrslZ/L6+yYqoUnewDlBvqRS2N/0NW9ro6w= +github.com/bytecodealliance/wasm-tools-go v0.3.0 h1:9aeDFYpbi3gtIW/nJCH+P+LhFMqezGoOfzqbUZLadho= +github.com/bytecodealliance/wasm-tools-go v0.3.0/go.mod h1:VY+9FlpLi6jnhCrZLkyJjF9rjU4aEekgaRTk28MS2JE= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= +github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/olareg/olareg v0.1.0 h1:1dXBOgPrig5N7zoXyIZVQqU0QBo6sD9pbL6UYjY75CA= +github.com/olareg/olareg v0.1.0/go.mod h1:RBuU7JW7SoIIxZKzLRhq8sVtQeAHzCAtRrXEBx2KlM4= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/regclient/regclient v0.7.1 h1:qEsJrTmZd98fZKjueAbrZCSNGU+ifnr6xjlSAs3WOPs= +github.com/regclient/regclient v0.7.1/go.mod h1:+w/BFtJuw0h0nzIw/z2+1FuA2/dVXBzDq4rYmziJpMc= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= +github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli/v3 v3.0.0-alpha9 h1:P0RMy5fQm1AslQS+XCmy9UknDXctOmG/q/FZkUFnJSo= github.com/urfave/cli/v3 v3.0.0-alpha9/go.mod h1:0kK/RUFHyh+yIKSfWxwheGndfnrvYSmYFVeKCh03ZUc= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= @@ -18,8 +35,12 @@ golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From d5f195387d14ba48f977841d7b3d38459604cc5d Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Thu, 17 Oct 2024 08:08:21 -0700 Subject: [PATCH 216/444] internal/{cm,wasi}: regenerate WASI 0.2 bindings with wasm-tools-go v0.3.0 --- src/internal/cm/abi.go | 32 +- src/internal/cm/hostlayout_go122.go | 11 + src/internal/cm/hostlayout_go123.go | 9 + src/internal/cm/list.go | 28 +- src/internal/cm/option.go | 19 +- src/internal/cm/result.go | 22 +- src/internal/cm/tuple.go | 15 + src/internal/cm/variant.go | 18 +- .../wasi/cli/v0.2.0/command/command.wit | 2099 +++++++++++++++++ .../v0.2.0/environment/environment.wasm.go | 21 + .../cli/v0.2.0/environment/environment.wit.go | 12 - .../wasi/cli/v0.2.0/exit/exit.wasm.go | 9 + src/internal/wasi/cli/v0.2.0/exit/exit.wit.go | 4 - src/internal/wasi/cli/v0.2.0/run/run.wasm.go | 17 + src/internal/wasi/cli/v0.2.0/run/run.wit.go | 12 - .../wasi/cli/v0.2.0/stderr/stderr.wasm.go | 9 + .../wasi/cli/v0.2.0/stderr/stderr.wit.go | 13 +- .../wasi/cli/v0.2.0/stdin/stdin.wasm.go | 9 + .../wasi/cli/v0.2.0/stdin/stdin.wit.go | 13 +- .../wasi/cli/v0.2.0/stdout/stdout.wasm.go | 9 + .../wasi/cli/v0.2.0/stdout/stdout.wit.go | 13 +- .../terminal-input/terminal-input.wit.go | 4 - .../terminal-input/terminalinput.wasm.go | 9 + .../terminal-output/terminal-output.wit.go | 4 - .../terminal-output/terminaloutput.wasm.go | 9 + .../terminal-stderr/terminal-stderr.wit.go | 11 +- .../terminal-stderr/terminalstderr.wasm.go | 13 + .../terminal-stdin/terminal-stdin.wit.go | 11 +- .../terminal-stdin/terminalstdin.wasm.go | 13 + .../terminal-stdout/terminal-stdout.wit.go | 11 +- .../terminal-stdout/terminalstdout.wasm.go | 13 + .../monotonic-clock/monotonic-clock.wit.go | 29 +- .../monotonic-clock/monotonicclock.wasm.go | 21 + .../v0.2.0/wall-clock/wall-clock.wit.go | 13 +- .../v0.2.0/wall-clock/wallclock.wasm.go | 13 + .../v0.2.0/preopens/preopens.wasm.go | 13 + .../v0.2.0/preopens/preopens.wit.go | 11 +- .../wasi/filesystem/v0.2.0/types/abi.go | 9 +- .../filesystem/v0.2.0/types/types.wasm.go | 133 ++ .../wasi/filesystem/v0.2.0/types/types.wit.go | 183 +- .../wasi/io/v0.2.0/error/error.wit.go | 8 - .../wasi/io/v0.2.0/error/ioerror.wasm.go | 13 + src/internal/wasi/io/v0.2.0/poll/poll.wasm.go | 25 + src/internal/wasi/io/v0.2.0/poll/poll.wit.go | 16 - .../wasi/io/v0.2.0/streams/streams.wasm.go | 77 + .../wasi/io/v0.2.0/streams/streams.wit.go | 106 +- .../v0.2.0/insecure-seed/insecure-seed.wit.go | 4 - .../v0.2.0/insecure-seed/insecureseed.wasm.go | 9 + .../random/v0.2.0/insecure/insecure.wasm.go | 17 + .../random/v0.2.0/insecure/insecure.wit.go | 8 - .../wasi/random/v0.2.0/random/random.wasm.go | 17 + .../wasi/random/v0.2.0/random/random.wit.go | 8 - .../instance-network/instance-network.wit.go | 13 +- .../instance-network/instancenetwork.wasm.go | 9 + .../wasi/sockets/v0.2.0/ip-name-lookup/abi.go | 4 +- .../ip-name-lookup/ip-name-lookup.wit.go | 44 +- .../ip-name-lookup/ipnamelookup.wasm.go | 25 + .../wasi/sockets/v0.2.0/network/abi.go | 2 + .../sockets/v0.2.0/network/network.wasm.go | 9 + .../sockets/v0.2.0/network/network.wit.go | 26 +- .../tcp-create-socket.wit.go | 26 +- .../tcp-create-socket/tcpcreatesocket.wasm.go | 13 + src/internal/wasi/sockets/v0.2.0/tcp/abi.go | 8 +- .../wasi/sockets/v0.2.0/tcp/tcp.wasm.go | 125 + .../wasi/sockets/v0.2.0/tcp/tcp.wit.go | 212 +- .../udp-create-socket.wit.go | 26 +- .../udp-create-socket/udpcreatesocket.wasm.go | 13 + src/internal/wasi/sockets/v0.2.0/udp/abi.go | 4 +- .../wasi/sockets/v0.2.0/udp/udp.wasm.go | 93 + .../wasi/sockets/v0.2.0/udp/udp.wit.go | 157 +- 70 files changed, 3350 insertions(+), 684 deletions(-) create mode 100644 src/internal/cm/hostlayout_go122.go create mode 100644 src/internal/cm/hostlayout_go123.go create mode 100755 src/internal/wasi/cli/v0.2.0/command/command.wit create mode 100755 src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go create mode 100755 src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go create mode 100755 src/internal/wasi/cli/v0.2.0/run/run.wasm.go create mode 100755 src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go create mode 100755 src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go create mode 100755 src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go create mode 100755 src/internal/wasi/cli/v0.2.0/terminal-input/terminalinput.wasm.go create mode 100755 src/internal/wasi/cli/v0.2.0/terminal-output/terminaloutput.wasm.go create mode 100755 src/internal/wasi/cli/v0.2.0/terminal-stderr/terminalstderr.wasm.go create mode 100755 src/internal/wasi/cli/v0.2.0/terminal-stdin/terminalstdin.wasm.go create mode 100755 src/internal/wasi/cli/v0.2.0/terminal-stdout/terminalstdout.wasm.go create mode 100755 src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonicclock.wasm.go create mode 100755 src/internal/wasi/clocks/v0.2.0/wall-clock/wallclock.wasm.go create mode 100755 src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go create mode 100755 src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go create mode 100755 src/internal/wasi/io/v0.2.0/error/ioerror.wasm.go create mode 100755 src/internal/wasi/io/v0.2.0/poll/poll.wasm.go create mode 100755 src/internal/wasi/io/v0.2.0/streams/streams.wasm.go create mode 100755 src/internal/wasi/random/v0.2.0/insecure-seed/insecureseed.wasm.go create mode 100755 src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go create mode 100755 src/internal/wasi/random/v0.2.0/random/random.wasm.go create mode 100755 src/internal/wasi/sockets/v0.2.0/instance-network/instancenetwork.wasm.go create mode 100755 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ipnamelookup.wasm.go create mode 100755 src/internal/wasi/sockets/v0.2.0/network/network.wasm.go create mode 100755 src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcpcreatesocket.wasm.go create mode 100755 src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go create mode 100755 src/internal/wasi/sockets/v0.2.0/udp-create-socket/udpcreatesocket.wasm.go create mode 100755 src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go diff --git a/src/internal/cm/abi.go b/src/internal/cm/abi.go index 91ca1be328..4d63036784 100644 --- a/src/internal/cm/abi.go +++ b/src/internal/cm/abi.go @@ -2,6 +2,11 @@ package cm import "unsafe" +// AnyInteger is a type constraint for any integer type. +type AnyInteger interface { + ~int | ~uint | ~uintptr | ~int8 | ~uint8 | ~int16 | ~uint16 | ~int32 | ~uint32 | ~int64 | ~uint64 +} + // Reinterpret reinterprets the bits of type From into type T. // Will panic if the size of From is smaller than the size of To. func Reinterpret[T, From any](from From) (to T) { @@ -19,19 +24,19 @@ func LowerString[S ~string](s S) (*byte, uint32) { } // LiftString lifts Core WebAssembly types into a [string]. -func LiftString[T ~string, Data unsafe.Pointer | uintptr | *uint8, Len uint | uintptr | uint32 | uint64](data Data, len Len) T { +func LiftString[T ~string, Data unsafe.Pointer | uintptr | *uint8, Len AnyInteger](data Data, len Len) T { return T(unsafe.String((*uint8)(unsafe.Pointer(data)), int(len))) } // LowerList lowers a [List] into a pair of Core WebAssembly types. -func LowerList[L ~struct{ list[T] }, T any](list L) (*T, uint32) { +func LowerList[L AnyList[T], T any](list L) (*T, uint32) { l := (*List[T])(unsafe.Pointer(&list)) return l.data, uint32(l.len) } // LiftList lifts Core WebAssembly types into a [List]. -func LiftList[L List[T], T any, Data unsafe.Pointer | uintptr | *T, Len uint | uintptr | uint32 | uint64](data Data, len Len) L { - return L(NewList((*T)(unsafe.Pointer(data)), uint(len))) +func LiftList[L AnyList[T], T any, Data unsafe.Pointer | uintptr | *T, Len AnyInteger](data Data, len Len) L { + return L(NewList((*T)(unsafe.Pointer(data)), len)) } // BoolToU32 converts a value whose underlying type is [bool] into a [uint32]. @@ -84,6 +89,25 @@ func F64ToU64(v float64) uint64 { return *(*uint64)(unsafe.Pointer(&v)) } // [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md func U64ToF64(v uint64) float64 { return *(*float64)(unsafe.Pointer(&v)) } +// F32ToU64 maps the bits of a [float32] into a [uint64]. +// Used to lower a [float32] into a Core WebAssembly i64 when required by the [Canonical ABI]. +// +// [float32]: https://pkg.go.dev/builtin#float32 +// [uint64]: https://pkg.go.dev/builtin#uint64 +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +func F32ToU64(v float32) uint64 { return uint64(*(*uint32)(unsafe.Pointer(&v))) } + +// U64ToF32 maps the bits of a [uint64] into a [float32]. +// Used to lift a Core WebAssembly i64 into a [float32] when required by the [Canonical ABI]. +// +// [uint64]: https://pkg.go.dev/builtin#uint64 +// [float32]: https://pkg.go.dev/builtin#float32 +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +func U64ToF32(v uint64) float32 { + truncated := uint32(v) + return *(*float32)(unsafe.Pointer(&truncated)) +} + // PointerToU32 converts a pointer of type *T into a [uint32]. // Used to lower a pointer into a Core WebAssembly i32 as specified in the [Canonical ABI]. // diff --git a/src/internal/cm/hostlayout_go122.go b/src/internal/cm/hostlayout_go122.go new file mode 100644 index 0000000000..25c1469762 --- /dev/null +++ b/src/internal/cm/hostlayout_go122.go @@ -0,0 +1,11 @@ +//go:build !go1.23 + +package cm + +// HostLayout marks a struct as using host memory layout. +// See [structs.HostLayout] in Go 1.23 or later. +type HostLayout struct { + _ hostLayout // prevent accidental conversion with plain struct{} +} + +type hostLayout struct{} diff --git a/src/internal/cm/hostlayout_go123.go b/src/internal/cm/hostlayout_go123.go new file mode 100644 index 0000000000..4fc3a2bdfa --- /dev/null +++ b/src/internal/cm/hostlayout_go123.go @@ -0,0 +1,9 @@ +//go:build go1.23 + +package cm + +import "structs" + +// HostLayout marks a struct as using host memory layout. +// See [structs.HostLayout] in Go 1.23 or later. +type HostLayout = structs.HostLayout diff --git a/src/internal/cm/list.go b/src/internal/cm/list.go index e170efbedd..5c896d044b 100644 --- a/src/internal/cm/list.go +++ b/src/internal/cm/list.go @@ -4,14 +4,25 @@ import "unsafe" // List represents a Component Model list. // The binary representation of list is similar to a Go slice minus the cap field. -type List[T any] struct{ list[T] } +type List[T any] struct { + _ HostLayout + list[T] +} + +// AnyList is a type constraint for generic functions that accept any [List] type. +type AnyList[T any] interface { + ~struct { + _ HostLayout + list[T] + } +} // NewList returns a List[T] from data and len. -func NewList[T any](data *T, len uint) List[T] { +func NewList[T any, Len AnyInteger](data *T, len Len) List[T] { return List[T]{ - list[T]{ + list: list[T]{ data: data, - len: len, + len: uintptr(len), }, } } @@ -20,15 +31,16 @@ func NewList[T any](data *T, len uint) List[T] { // The underlying slice data is not copied, and the resulting List points at the // same array storage as the slice. func ToList[S ~[]T, T any](s S) List[T] { - return NewList[T](unsafe.SliceData([]T(s)), uint(len(s))) + return NewList[T](unsafe.SliceData([]T(s)), uintptr(len(s))) } // list represents the internal representation of a Component Model list. // It is intended to be embedded in a [List], so embedding types maintain // the methods defined on this type. type list[T any] struct { + _ HostLayout data *T - len uint + len uintptr } // Slice returns a Go slice representing the List. @@ -42,7 +54,7 @@ func (l list[T]) Data() *T { } // Len returns the length of the list. -// TODO: should this return an int instead of a uint? -func (l list[T]) Len() uint { +// TODO: should this return an int instead of a uintptr? +func (l list[T]) Len() uintptr { return l.len } diff --git a/src/internal/cm/option.go b/src/internal/cm/option.go index edc288b4c0..cf7024aa62 100644 --- a/src/internal/cm/option.go +++ b/src/internal/cm/option.go @@ -3,7 +3,10 @@ package cm // Option represents a Component Model [option] type. // // [option]: https://component-model.bytecodealliance.org/design/wit.html#options -type Option[T any] struct{ option[T] } +type Option[T any] struct { + _ HostLayout + option[T] +} // None returns an [Option] representing the none case, // equivalent to the zero value. @@ -14,7 +17,7 @@ func None[T any]() Option[T] { // Some returns an [Option] representing the some case. func Some[T any](v T) Option[T] { return Option[T]{ - option[T]{ + option: option[T]{ isSome: true, some: v, }, @@ -25,6 +28,7 @@ func Some[T any](v T) Option[T] { // The first byte is a bool representing none or some, // followed by storage for the associated type T. type option[T any] struct { + _ HostLayout isSome bool some T } @@ -42,3 +46,14 @@ func (o *option[T]) Some() *T { } return nil } + +// Value returns T if o represents the some case, +// or the zero value of T if o represents the none case. +// This does not have a pointer receiver, so it can be chained. +func (o option[T]) Value() T { + if !o.isSome { + var zero T + return zero + } + return o.some +} diff --git a/src/internal/cm/result.go b/src/internal/cm/result.go index d21275612a..82200e2782 100644 --- a/src/internal/cm/result.go +++ b/src/internal/cm/result.go @@ -17,10 +17,22 @@ type BoolResult bool // Result represents a result sized to hold the Shape type. // The size of the Shape type must be greater than or equal to the size of OK and Err types. // For results with two zero-length types, use [BoolResult]. -type Result[Shape, OK, Err any] struct{ result[Shape, OK, Err] } +type Result[Shape, OK, Err any] struct { + _ HostLayout + result[Shape, OK, Err] +} + +// AnyResult is a type constraint for generic functions that accept any [Result] type. +type AnyResult[Shape, OK, Err any] interface { + ~struct { + _ HostLayout + result[Shape, OK, Err] + } +} // result represents the internal representation of a Component Model result type. type result[Shape, OK, Err any] struct { + _ HostLayout isErr bool _ [0]OK _ [0]Err @@ -88,8 +100,8 @@ func (r *result[Shape, OK, Err]) validate() { // OK returns an OK result with shape Shape and type OK and Err. // Pass Result[OK, OK, Err] or Result[Err, OK, Err] as the first type argument. -func OK[R ~struct{ result[Shape, OK, Err] }, Shape, OK, Err any](ok OK) R { - var r struct{ result[Shape, OK, Err] } +func OK[R AnyResult[Shape, OK, Err], Shape, OK, Err any](ok OK) R { + var r Result[Shape, OK, Err] r.validate() r.isErr = ResultOK *((*OK)(unsafe.Pointer(&r.data))) = ok @@ -98,8 +110,8 @@ func OK[R ~struct{ result[Shape, OK, Err] }, Shape, OK, Err any](ok OK) R { // Err returns an error result with shape Shape and type OK and Err. // Pass Result[OK, OK, Err] or Result[Err, OK, Err] as the first type argument. -func Err[R ~struct{ result[Shape, OK, Err] }, Shape, OK, Err any](err Err) R { - var r struct{ result[Shape, OK, Err] } +func Err[R AnyResult[Shape, OK, Err], Shape, OK, Err any](err Err) R { + var r Result[Shape, OK, Err] r.validate() r.isErr = ResultErr *((*Err)(unsafe.Pointer(&r.data))) = err diff --git a/src/internal/cm/tuple.go b/src/internal/cm/tuple.go index 7b0e535c02..610a19be57 100644 --- a/src/internal/cm/tuple.go +++ b/src/internal/cm/tuple.go @@ -4,6 +4,7 @@ package cm // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple[T0, T1 any] struct { + _ HostLayout F0 T0 F1 T1 } @@ -12,6 +13,7 @@ type Tuple[T0, T1 any] struct { // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple3[T0, T1, T2 any] struct { + _ HostLayout F0 T0 F1 T1 F2 T2 @@ -21,6 +23,7 @@ type Tuple3[T0, T1, T2 any] struct { // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple4[T0, T1, T2, T3 any] struct { + _ HostLayout F0 T0 F1 T1 F2 T2 @@ -31,6 +34,7 @@ type Tuple4[T0, T1, T2, T3 any] struct { // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple5[T0, T1, T2, T3, T4 any] struct { + _ HostLayout F0 T0 F1 T1 F2 T2 @@ -42,6 +46,7 @@ type Tuple5[T0, T1, T2, T3, T4 any] struct { // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple6[T0, T1, T2, T3, T4, T5 any] struct { + _ HostLayout F0 T0 F1 T1 F2 T2 @@ -54,6 +59,7 @@ type Tuple6[T0, T1, T2, T3, T4, T5 any] struct { // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple7[T0, T1, T2, T3, T4, T5, T6 any] struct { + _ HostLayout F0 T0 F1 T1 F2 T2 @@ -67,6 +73,7 @@ type Tuple7[T0, T1, T2, T3, T4, T5, T6 any] struct { // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple8[T0, T1, T2, T3, T4, T5, T6, T7 any] struct { + _ HostLayout F0 T0 F1 T1 F2 T2 @@ -81,6 +88,7 @@ type Tuple8[T0, T1, T2, T3, T4, T5, T6, T7 any] struct { // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple9[T0, T1, T2, T3, T4, T5, T6, T7, T8 any] struct { + _ HostLayout F0 T0 F1 T1 F2 T2 @@ -96,6 +104,7 @@ type Tuple9[T0, T1, T2, T3, T4, T5, T6, T7, T8 any] struct { // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple10[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9 any] struct { + _ HostLayout F0 T0 F1 T1 F2 T2 @@ -112,6 +121,7 @@ type Tuple10[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9 any] struct { // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple11[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any] struct { + _ HostLayout F0 T0 F1 T1 F2 T2 @@ -129,6 +139,7 @@ type Tuple11[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any] struct { // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple12[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any] struct { + _ HostLayout F0 T0 F1 T1 F2 T2 @@ -147,6 +158,7 @@ type Tuple12[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any] struct { // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple13[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any] struct { + _ HostLayout F0 T0 F1 T1 F2 T2 @@ -166,6 +178,7 @@ type Tuple13[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any] struct { // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple14[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any] struct { + _ HostLayout F0 T0 F1 T1 F2 T2 @@ -186,6 +199,7 @@ type Tuple14[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any] str // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple15[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any] struct { + _ HostLayout F0 T0 F1 T1 F2 T2 @@ -207,6 +221,7 @@ type Tuple15[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any // // [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples type Tuple16[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any] struct { + _ HostLayout F0 T0 F1 T1 F2 T2 diff --git a/src/internal/cm/variant.go b/src/internal/cm/variant.go index 5ae5cf381c..24703641df 100644 --- a/src/internal/cm/variant.go +++ b/src/internal/cm/variant.go @@ -12,7 +12,18 @@ type Discriminant interface { // Variant represents a loosely-typed Component Model variant. // Shape and Align must be non-zero sized types. To create a variant with no associated // types, use an enum. -type Variant[Tag Discriminant, Shape, Align any] struct{ variant[Tag, Shape, Align] } +type Variant[Tag Discriminant, Shape, Align any] struct { + _ HostLayout + variant[Tag, Shape, Align] +} + +// AnyVariant is a type constraint for generic functions that accept any [Variant] type. +type AnyVariant[Tag Discriminant, Shape, Align any] interface { + ~struct { + _ HostLayout + variant[Tag, Shape, Align] + } +} // NewVariant returns a [Variant] with tag of type Disc, storage and GC shape of type Shape, // aligned to type Align, with a value of type T. @@ -26,7 +37,7 @@ func NewVariant[Tag Discriminant, Shape, Align any, T any](tag Tag, data T) Vari // New returns a [Variant] with tag of type Disc, storage and GC shape of type Shape, // aligned to type Align, with a value of type T. -func New[V ~struct{ variant[Tag, Shape, Align] }, Tag Discriminant, Shape, Align any, T any](tag Tag, data T) V { +func New[V AnyVariant[Tag, Shape, Align], Tag Discriminant, Shape, Align any, T any](tag Tag, data T) V { validateVariant[Tag, Shape, Align, T]() var v variant[Tag, Shape, Align] v.tag = tag @@ -35,7 +46,7 @@ func New[V ~struct{ variant[Tag, Shape, Align] }, Tag Discriminant, Shape, Align } // Case returns a non-nil *T if the [Variant] case is equal to tag, otherwise it returns nil. -func Case[T any, V ~struct{ variant[Tag, Shape, Align] }, Tag Discriminant, Shape, Align any](v *V, tag Tag) *T { +func Case[T any, V AnyVariant[Tag, Shape, Align], Tag Discriminant, Shape, Align any](v *V, tag Tag) *T { validateVariant[Tag, Shape, Align, T]() v2 := (*variant[Tag, Shape, Align])(unsafe.Pointer(v)) if v2.tag == tag { @@ -47,6 +58,7 @@ func Case[T any, V ~struct{ variant[Tag, Shape, Align] }, Tag Discriminant, Shap // variant is the internal representation of a Component Model variant. // Shape and Align must be non-zero sized types. type variant[Tag Discriminant, Shape, Align any] struct { + _ HostLayout tag Tag _ [0]Align data Shape // [unsafe.Sizeof(*(*Shape)(unsafe.Pointer(nil)))]byte diff --git a/src/internal/wasi/cli/v0.2.0/command/command.wit b/src/internal/wasi/cli/v0.2.0/command/command.wit new file mode 100755 index 0000000000..7b2af2ae9e --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/command/command.wit @@ -0,0 +1,2099 @@ +package wasi:cli@0.2.0; + +interface environment { + /// Get the POSIX-style environment variables. + /// + /// Each environment variable is provided as a pair of string variable names + /// and string value. + /// + /// Morally, these are a value import, but until value imports are available + /// in the component model, this import function should return the same + /// values each time it is called. + get-environment: func() -> list>; + + /// Get the POSIX-style arguments to the program. + get-arguments: func() -> list; + + /// Return a path that programs should use as their initial current working + /// directory, interpreting `.` as shorthand for this. + initial-cwd: func() -> option; +} + +interface exit { + /// Exit the current instance and any linked instances. + exit: func(status: result); +} + +interface run { + /// Run the program. + run: func() -> result; +} + +interface stdin { + use wasi:io/streams@0.2.0.{input-stream}; + get-stdin: func() -> input-stream; +} + +interface stdout { + use wasi:io/streams@0.2.0.{output-stream}; + get-stdout: func() -> output-stream; +} + +interface stderr { + use wasi:io/streams@0.2.0.{output-stream}; + get-stderr: func() -> output-stream; +} + +/// Terminal input. +/// +/// In the future, this may include functions for disabling echoing, +/// disabling input buffering so that keyboard events are sent through +/// immediately, querying supported features, and so on. +interface terminal-input { + /// The input side of a terminal. + resource terminal-input; +} + +/// Terminal output. +/// +/// In the future, this may include functions for querying the terminal +/// size, being notified of terminal size changes, querying supported +/// features, and so on. +interface terminal-output { + /// The output side of a terminal. + resource terminal-output; +} + +/// An interface providing an optional `terminal-input` for stdin as a +/// link-time authority. +interface terminal-stdin { + use terminal-input.{terminal-input}; + + /// If stdin is connected to a terminal, return a `terminal-input` handle + /// allowing further interaction with it. + get-terminal-stdin: func() -> option; +} + +/// An interface providing an optional `terminal-output` for stdout as a +/// link-time authority. +interface terminal-stdout { + use terminal-output.{terminal-output}; + + /// If stdout is connected to a terminal, return a `terminal-output` handle + /// allowing further interaction with it. + get-terminal-stdout: func() -> option; +} + +/// An interface providing an optional `terminal-output` for stderr as a +/// link-time authority. +interface terminal-stderr { + use terminal-output.{terminal-output}; + + /// If stderr is connected to a terminal, return a `terminal-output` handle + /// allowing further interaction with it. + get-terminal-stderr: func() -> option; +} + +world command { + import environment; + import exit; + import wasi:io/error@0.2.0; + import wasi:io/poll@0.2.0; + import wasi:io/streams@0.2.0; + import stdin; + import stdout; + import stderr; + import terminal-input; + import terminal-output; + import terminal-stdin; + import terminal-stdout; + import terminal-stderr; + import wasi:clocks/monotonic-clock@0.2.0; + import wasi:clocks/wall-clock@0.2.0; + import wasi:filesystem/types@0.2.0; + import wasi:filesystem/preopens@0.2.0; + import wasi:sockets/network@0.2.0; + import wasi:sockets/instance-network@0.2.0; + import wasi:sockets/udp@0.2.0; + import wasi:sockets/udp-create-socket@0.2.0; + import wasi:sockets/tcp@0.2.0; + import wasi:sockets/tcp-create-socket@0.2.0; + import wasi:sockets/ip-name-lookup@0.2.0; + import wasi:random/random@0.2.0; + import wasi:random/insecure@0.2.0; + import wasi:random/insecure-seed@0.2.0; + export run; +} + +package wasi:filesystem@0.2.0 { + /// WASI filesystem is a filesystem API primarily intended to let users run WASI + /// programs that access their files on their existing filesystems, without + /// significant overhead. + /// + /// It is intended to be roughly portable between Unix-family platforms and + /// Windows, though it does not hide many of the major differences. + /// + /// Paths are passed as interface-type `string`s, meaning they must consist of + /// a sequence of Unicode Scalar Values (USVs). Some filesystems may contain + /// paths which are not accessible by this API. + /// + /// The directory separator in WASI is always the forward-slash (`/`). + /// + /// All paths in WASI are relative paths, and are interpreted relative to a + /// `descriptor` referring to a base directory. If a `path` argument to any WASI + /// function starts with `/`, or if any step of resolving a `path`, including + /// `..` and symbolic link steps, reaches a directory outside of the base + /// directory, or reaches a symlink to an absolute or rooted path in the + /// underlying filesystem, the function fails with `error-code::not-permitted`. + /// + /// For more information about WASI path resolution and sandboxing, see + /// [WASI filesystem path resolution]. + /// + /// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md + interface types { + use wasi:io/streams@0.2.0.{input-stream}; + use wasi:io/streams@0.2.0.{output-stream}; + use wasi:io/streams@0.2.0.{error}; + use wasi:clocks/wall-clock@0.2.0.{datetime}; + + /// File size or length of a region within a file. + type filesize = u64; + + /// The type of a filesystem object referenced by a descriptor. + /// + /// Note: This was called `filetype` in earlier versions of WASI. + enum descriptor-type { + /// The type of the descriptor or file is unknown or is different from + /// any of the other types specified. + unknown, + /// The descriptor refers to a block device inode. + block-device, + /// The descriptor refers to a character device inode. + character-device, + /// The descriptor refers to a directory inode. + directory, + /// The descriptor refers to a named pipe. + fifo, + /// The file refers to a symbolic link inode. + symbolic-link, + /// The descriptor refers to a regular file inode. + regular-file, + /// The descriptor refers to a socket. + socket + } + + /// Descriptor flags. + /// + /// Note: This was called `fdflags` in earlier versions of WASI. + flags descriptor-flags { + /// Read mode: Data can be read. + read, + /// Write mode: Data can be written to. + write, + /// Request that writes be performed according to synchronized I/O file + /// integrity completion. The data stored in the file and the file's + /// metadata are synchronized. This is similar to `O_SYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + file-integrity-sync, + /// Request that writes be performed according to synchronized I/O data + /// integrity completion. Only the data stored in the file is + /// synchronized. This is similar to `O_DSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + data-integrity-sync, + /// Requests that reads be performed at the same level of integrety + /// requested for writes. This is similar to `O_RSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + requested-write-sync, + /// Mutating directories mode: Directory contents may be mutated. + /// + /// When this flag is unset on a descriptor, operations using the + /// descriptor which would create, rename, delete, modify the data or + /// metadata of filesystem objects, or obtain another handle which + /// would permit any of those, shall fail with `error-code::read-only` if + /// they would otherwise succeed. + /// + /// This may only be set on directories. + mutate-directory, + } + + /// Flags determining the method of how paths are resolved. + flags path-flags { + /// As long as the resolved path corresponds to a symbolic link, it is + /// expanded. + symlink-follow, + } + + /// Open flags used by `open-at`. + flags open-flags { + /// Create file if it does not exist, similar to `O_CREAT` in POSIX. + create, + /// Fail if not a directory, similar to `O_DIRECTORY` in POSIX. + directory, + /// Fail if file already exists, similar to `O_EXCL` in POSIX. + exclusive, + /// Truncate file to size 0, similar to `O_TRUNC` in POSIX. + truncate, + } + + /// Number of hard links to an inode. + type link-count = u64; + + /// File attributes. + /// + /// Note: This was called `filestat` in earlier versions of WASI. + record descriptor-stat { + /// File type. + %type: descriptor-type, + /// Number of hard links to the file. + link-count: link-count, + /// For regular files, the file size in bytes. For symbolic links, the + /// length in bytes of the pathname contained in the symbolic link. + size: filesize, + /// Last data access timestamp. + /// + /// If the `option` is none, the platform doesn't maintain an access + /// timestamp for this file. + data-access-timestamp: option, + /// Last data modification timestamp. + /// + /// If the `option` is none, the platform doesn't maintain a + /// modification timestamp for this file. + data-modification-timestamp: option, + /// Last file status-change timestamp. + /// + /// If the `option` is none, the platform doesn't maintain a + /// status-change timestamp for this file. + status-change-timestamp: option, + } + + /// When setting a timestamp, this gives the value to set it to. + variant new-timestamp { + /// Leave the timestamp set to its previous value. + no-change, + /// Set the timestamp to the current time of the system clock associated + /// with the filesystem. + now, + /// Set the timestamp to the given value. + timestamp(datetime), + } + + /// A directory entry. + record directory-entry { + /// The type of the file referred to by this directory entry. + %type: descriptor-type, + /// The name of the object. + name: string, + } + + /// Error codes returned by functions, similar to `errno` in POSIX. + /// Not all of these error codes are returned by the functions provided by this + /// API; some are used in higher-level library layers, and others are provided + /// merely for alignment with POSIX. + enum error-code { + /// Permission denied, similar to `EACCES` in POSIX. + access, + /// Resource unavailable, or operation would block, similar to `EAGAIN` and `EWOULDBLOCK` + /// in POSIX. + would-block, + /// Connection already in progress, similar to `EALREADY` in POSIX. + already, + /// Bad descriptor, similar to `EBADF` in POSIX. + bad-descriptor, + /// Device or resource busy, similar to `EBUSY` in POSIX. + busy, + /// Resource deadlock would occur, similar to `EDEADLK` in POSIX. + deadlock, + /// Storage quota exceeded, similar to `EDQUOT` in POSIX. + quota, + /// File exists, similar to `EEXIST` in POSIX. + exist, + /// File too large, similar to `EFBIG` in POSIX. + file-too-large, + /// Illegal byte sequence, similar to `EILSEQ` in POSIX. + illegal-byte-sequence, + /// Operation in progress, similar to `EINPROGRESS` in POSIX. + in-progress, + /// Interrupted function, similar to `EINTR` in POSIX. + interrupted, + /// Invalid argument, similar to `EINVAL` in POSIX. + invalid, + /// I/O error, similar to `EIO` in POSIX. + io, + /// Is a directory, similar to `EISDIR` in POSIX. + is-directory, + /// Too many levels of symbolic links, similar to `ELOOP` in POSIX. + loop, + /// Too many links, similar to `EMLINK` in POSIX. + too-many-links, + /// Message too large, similar to `EMSGSIZE` in POSIX. + message-size, + /// Filename too long, similar to `ENAMETOOLONG` in POSIX. + name-too-long, + /// No such device, similar to `ENODEV` in POSIX. + no-device, + /// No such file or directory, similar to `ENOENT` in POSIX. + no-entry, + /// No locks available, similar to `ENOLCK` in POSIX. + no-lock, + /// Not enough space, similar to `ENOMEM` in POSIX. + insufficient-memory, + /// No space left on device, similar to `ENOSPC` in POSIX. + insufficient-space, + /// Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX. + not-directory, + /// Directory not empty, similar to `ENOTEMPTY` in POSIX. + not-empty, + /// State not recoverable, similar to `ENOTRECOVERABLE` in POSIX. + not-recoverable, + /// Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX. + unsupported, + /// Inappropriate I/O control operation, similar to `ENOTTY` in POSIX. + no-tty, + /// No such device or address, similar to `ENXIO` in POSIX. + no-such-device, + /// Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX. + overflow, + /// Operation not permitted, similar to `EPERM` in POSIX. + not-permitted, + /// Broken pipe, similar to `EPIPE` in POSIX. + pipe, + /// Read-only file system, similar to `EROFS` in POSIX. + read-only, + /// Invalid seek, similar to `ESPIPE` in POSIX. + invalid-seek, + /// Text file busy, similar to `ETXTBSY` in POSIX. + text-file-busy, + /// Cross-device link, similar to `EXDEV` in POSIX. + cross-device + } + + /// File or memory access pattern advisory information. + enum advice { + /// The application has no advice to give on its behavior with respect + /// to the specified data. + normal, + /// The application expects to access the specified data sequentially + /// from lower offsets to higher offsets. + sequential, + /// The application expects to access the specified data in a random + /// order. + random, + /// The application expects to access the specified data in the near + /// future. + will-need, + /// The application expects that it will not access the specified data + /// in the near future. + dont-need, + /// The application expects to access the specified data once and then + /// not reuse it thereafter. + no-reuse + } + + /// A 128-bit hash value, split into parts because wasm doesn't have a + /// 128-bit integer type. + record metadata-hash-value { + /// 64 bits of a 128-bit hash value. + lower: u64, + /// Another 64 bits of a 128-bit hash value. + upper: u64, + } + + /// A descriptor is a reference to a filesystem object, which may be a file, + /// directory, named pipe, special file, or other object on which filesystem + /// calls may be made. + resource descriptor { + + /// Provide file advisory information on a descriptor. + /// + /// This is similar to `posix_fadvise` in POSIX. + advise: func(offset: filesize, length: filesize, advice: advice) -> result<_, error-code>; + + /// Return a stream for appending to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be appended. + /// + /// Note: This allows using `write-stream`, which is similar to `write` with + /// `O_APPEND` in in POSIX. + append-via-stream: func() -> result; + + /// Create a directory. + /// + /// Note: This is similar to `mkdirat` in POSIX. + create-directory-at: func(path: string) -> result<_, error-code>; + + /// Get flags associated with a descriptor. + /// + /// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. + /// + /// Note: This returns the value that was the `fs_flags` value returned + /// from `fdstat_get` in earlier versions of WASI. + get-flags: func() -> result; + + /// Get the dynamic type of a descriptor. + /// + /// Note: This returns the same value as the `type` field of the `fd-stat` + /// returned by `stat`, `stat-at` and similar. + /// + /// Note: This returns similar flags to the `st_mode & S_IFMT` value provided + /// by `fstat` in POSIX. + /// + /// Note: This returns the value that was the `fs_filetype` value returned + /// from `fdstat_get` in earlier versions of WASI. + get-type: func() -> result; + + /// Test whether two descriptors refer to the same filesystem object. + /// + /// In POSIX, this corresponds to testing whether the two descriptors have the + /// same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers. + /// wasi-filesystem does not expose device and inode numbers, so this function + /// may be used instead. + is-same-object: func(other: borrow) -> bool; + + /// Create a hard link. + /// + /// Note: This is similar to `linkat` in POSIX. + link-at: func(old-path-flags: path-flags, old-path: string, new-descriptor: borrow, new-path: string) -> result<_, error-code>; + + /// Return a hash of the metadata associated with a filesystem object referred + /// to by a descriptor. + /// + /// This returns a hash of the last-modification timestamp and file size, and + /// may also include the inode number, device number, birth timestamp, and + /// other metadata fields that may change when the file is modified or + /// replaced. It may also include a secret value chosen by the + /// implementation and not otherwise exposed. + /// + /// Implementations are encourated to provide the following properties: + /// + /// - If the file is not modified or replaced, the computed hash value should + /// usually not change. + /// - If the object is modified or replaced, the computed hash value should + /// usually change. + /// - The inputs to the hash should not be easily computable from the + /// computed hash. + /// + /// However, none of these is required. + metadata-hash: func() -> result; + + /// Return a hash of the metadata associated with a filesystem object referred + /// to by a directory descriptor and a relative path. + /// + /// This performs the same hash computation as `metadata-hash`. + metadata-hash-at: func(path-flags: path-flags, path: string) -> result; + + /// Open a file or directory. + /// + /// The returned descriptor is not guaranteed to be the lowest-numbered + /// descriptor not currently open/ it is randomized to prevent applications + /// from depending on making assumptions about indexes, since this is + /// error-prone in multi-threaded contexts. The returned descriptor is + /// guaranteed to be less than 2**31. + /// + /// If `flags` contains `descriptor-flags::mutate-directory`, and the base + /// descriptor doesn't have `descriptor-flags::mutate-directory` set, + /// `open-at` fails with `error-code::read-only`. + /// + /// If `flags` contains `write` or `mutate-directory`, or `open-flags` + /// contains `truncate` or `create`, and the base descriptor doesn't have + /// `descriptor-flags::mutate-directory` set, `open-at` fails with + /// `error-code::read-only`. + /// + /// Note: This is similar to `openat` in POSIX. + open-at: func(path-flags: path-flags, path: string, open-flags: open-flags, %flags: descriptor-flags) -> result; + + /// Read from a descriptor, without using and updating the descriptor's offset. + /// + /// This function returns a list of bytes containing the data that was + /// read, along with a bool which, when true, indicates that the end of the + /// file was reached. The returned list will contain up to `length` bytes; it + /// may return fewer than requested, if the end of the file is reached or + /// if the I/O operation is interrupted. + /// + /// In the future, this may change to return a `stream`. + /// + /// Note: This is similar to `pread` in POSIX. + read: func(length: filesize, offset: filesize) -> result, bool>, error-code>; + + /// Read directory entries from a directory. + /// + /// On filesystems where directories contain entries referring to themselves + /// and their parents, often named `.` and `..` respectively, these entries + /// are omitted. + /// + /// This always returns a new stream which starts at the beginning of the + /// directory. Multiple streams may be active on the same directory, and they + /// do not interfere with each other. + read-directory: func() -> result; + + /// Return a stream for reading from a file, if available. + /// + /// May fail with an error-code describing why the file cannot be read. + /// + /// Multiple read, write, and append streams may be active on the same open + /// file and they do not interfere with each other. + /// + /// Note: This allows using `read-stream`, which is similar to `read` in POSIX. + read-via-stream: func(offset: filesize) -> result; + + /// Read the contents of a symbolic link. + /// + /// If the contents contain an absolute or rooted path in the underlying + /// filesystem, this function fails with `error-code::not-permitted`. + /// + /// Note: This is similar to `readlinkat` in POSIX. + readlink-at: func(path: string) -> result; + + /// Remove a directory. + /// + /// Return `error-code::not-empty` if the directory is not empty. + /// + /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. + remove-directory-at: func(path: string) -> result<_, error-code>; + + /// Rename a filesystem object. + /// + /// Note: This is similar to `renameat` in POSIX. + rename-at: func(old-path: string, new-descriptor: borrow, new-path: string) -> result<_, error-code>; + + /// Adjust the size of an open file. If this increases the file's size, the + /// extra bytes are filled with zeros. + /// + /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. + set-size: func(size: filesize) -> result<_, error-code>; + + /// Adjust the timestamps of an open file or directory. + /// + /// Note: This is similar to `futimens` in POSIX. + /// + /// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. + set-times: func(data-access-timestamp: new-timestamp, data-modification-timestamp: new-timestamp) -> result<_, error-code>; + + /// Adjust the timestamps of a file or directory. + /// + /// Note: This is similar to `utimensat` in POSIX. + /// + /// Note: This was called `path_filestat_set_times` in earlier versions of + /// WASI. + set-times-at: func(path-flags: path-flags, path: string, data-access-timestamp: new-timestamp, data-modification-timestamp: new-timestamp) -> result<_, error-code>; + + /// Return the attributes of an open file or directory. + /// + /// Note: This is similar to `fstat` in POSIX, except that it does not return + /// device and inode information. For testing whether two descriptors refer to + /// the same underlying filesystem object, use `is-same-object`. To obtain + /// additional data that can be used do determine whether a file has been + /// modified, use `metadata-hash`. + /// + /// Note: This was called `fd_filestat_get` in earlier versions of WASI. + stat: func() -> result; + + /// Return the attributes of a file or directory. + /// + /// Note: This is similar to `fstatat` in POSIX, except that it does not + /// return device and inode information. See the `stat` description for a + /// discussion of alternatives. + /// + /// Note: This was called `path_filestat_get` in earlier versions of WASI. + stat-at: func(path-flags: path-flags, path: string) -> result; + + /// Create a symbolic link (also known as a "symlink"). + /// + /// If `old-path` starts with `/`, the function fails with + /// `error-code::not-permitted`. + /// + /// Note: This is similar to `symlinkat` in POSIX. + symlink-at: func(old-path: string, new-path: string) -> result<_, error-code>; + + /// Synchronize the data and metadata of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fsync` in POSIX. + sync: func() -> result<_, error-code>; + + /// Synchronize the data of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fdatasync` in POSIX. + sync-data: func() -> result<_, error-code>; + + /// Unlink a filesystem object that is not a directory. + /// + /// Return `error-code::is-directory` if the path refers to a directory. + /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. + unlink-file-at: func(path: string) -> result<_, error-code>; + + /// Write to a descriptor, without using and updating the descriptor's offset. + /// + /// It is valid to write past the end of a file; the file is extended to the + /// extent of the write, with bytes between the previous end and the start of + /// the write set to zero. + /// + /// In the future, this may change to take a `stream`. + /// + /// Note: This is similar to `pwrite` in POSIX. + write: func(buffer: list, offset: filesize) -> result; + + /// Return a stream for writing to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be written. + /// + /// Note: This allows using `write-stream`, which is similar to `write` in + /// POSIX. + write-via-stream: func(offset: filesize) -> result; + } + + /// A stream of directory entries. + resource directory-entry-stream { + + /// Read a single directory entry from a `directory-entry-stream`. + read-directory-entry: func() -> result, error-code>; + } + + /// Attempts to extract a filesystem-related `error-code` from the stream + /// `error` provided. + /// + /// Stream operations which return `stream-error::last-operation-failed` + /// have a payload with more information about the operation that failed. + /// This payload can be passed through to this function to see if there's + /// filesystem-related information about the error to return. + /// + /// Note that this function is fallible because not all stream-related + /// errors are filesystem-related errors. + filesystem-error-code: func(err: borrow) -> option; + } + + interface preopens { + use types.{descriptor}; + + /// Return the set of preopened directories, and their path. + get-directories: func() -> list>; + } +} + +package wasi:sockets@0.2.0 { + interface network { + /// An opaque resource that represents access to (a subset of) the network. + /// This enables context-based security for networking. + /// There is no need for this to map 1:1 to a physical network interface. + resource network; + + /// Error codes. + /// + /// In theory, every API can return any error code. + /// In practice, API's typically only return the errors documented per API + /// combined with a couple of errors that are always possible: + /// - `unknown` + /// - `access-denied` + /// - `not-supported` + /// - `out-of-memory` + /// - `concurrency-conflict` + /// + /// See each individual API for what the POSIX equivalents are. They sometimes differ + /// per API. + enum error-code { + /// Unknown error + unknown, + /// Access denied. + /// + /// POSIX equivalent: EACCES, EPERM + access-denied, + /// The operation is not supported. + /// + /// POSIX equivalent: EOPNOTSUPP + not-supported, + /// One of the arguments is invalid. + /// + /// POSIX equivalent: EINVAL + invalid-argument, + /// Not enough memory to complete the operation. + /// + /// POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY + out-of-memory, + /// The operation timed out before it could finish completely. + timeout, + /// This operation is incompatible with another asynchronous operation that is already + /// in progress. + /// + /// POSIX equivalent: EALREADY + concurrency-conflict, + /// Trying to finish an asynchronous operation that: + /// - has not been started yet, or: + /// - was already finished by a previous `finish-*` call. + /// + /// Note: this is scheduled to be removed when `future`s are natively supported. + not-in-progress, + /// The operation has been aborted because it could not be completed immediately. + /// + /// Note: this is scheduled to be removed when `future`s are natively supported. + would-block, + /// The operation is not valid in the socket's current state. + invalid-state, + /// A new socket resource could not be created because of a system limit. + new-socket-limit, + /// A bind operation failed because the provided address is not an address that the + /// `network` can bind to. + address-not-bindable, + /// A bind operation failed because the provided address is already in use or because + /// there are no ephemeral ports available. + address-in-use, + /// The remote address is not reachable + remote-unreachable, + /// The TCP connection was forcefully rejected + connection-refused, + /// The TCP connection was reset. + connection-reset, + /// A TCP connection was aborted. + connection-aborted, + /// The size of a datagram sent to a UDP socket exceeded the maximum + /// supported size. + datagram-too-large, + /// Name does not exist or has no suitable associated IP addresses. + name-unresolvable, + /// A temporary failure in name resolution occurred. + temporary-resolver-failure, + /// A permanent failure in name resolution occurred. + permanent-resolver-failure + } + enum ip-address-family { + /// Similar to `AF_INET` in POSIX. + ipv4, + /// Similar to `AF_INET6` in POSIX. + ipv6 + } + type ipv4-address = tuple; + type ipv6-address = tuple; + variant ip-address { + ipv4(ipv4-address), + ipv6(ipv6-address), + } + record ipv4-socket-address { + /// sin_port + port: u16, + /// sin_addr + address: ipv4-address, + } + record ipv6-socket-address { + /// sin6_port + port: u16, + /// sin6_flowinfo + flow-info: u32, + /// sin6_addr + address: ipv6-address, + /// sin6_scope_id + scope-id: u32, + } + variant ip-socket-address { + ipv4(ipv4-socket-address), + ipv6(ipv6-socket-address), + } + } + + /// This interface provides a value-export of the default network handle.. + interface instance-network { + use network.{network}; + + /// Get a handle to the default network. + instance-network: func() -> network; + } + + interface ip-name-lookup { + use wasi:io/poll@0.2.0.{pollable}; + use network.{network}; + use network.{error-code}; + use network.{ip-address}; + resource resolve-address-stream { + + /// Returns the next address from the resolver. + /// + /// This function should be called multiple times. On each call, it will + /// return the next address in connection order preference. If all + /// addresses have been exhausted, this function returns `none`. + /// + /// This function never returns IPv4-mapped IPv6 addresses. + /// + /// # Typical errors + /// - `name-unresolvable`: Name does not exist or has no suitable associated + /// IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) + /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. + /// (EAI_AGAIN) + /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. + /// (EAI_FAIL) + /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) + resolve-next-address: func() -> result, error-code>; + + /// Create a `pollable` which will resolve once the stream is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + } + + /// Resolve an internet host name to a list of IP addresses. + /// + /// Unicode domain names are automatically converted to ASCII using IDNA encoding. + /// If the input is an IP address string, the address is parsed and returned + /// as-is without making any external requests. + /// + /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. + /// + /// This function never blocks. It either immediately fails or immediately + /// returns successfully with a `resolve-address-stream` that can be used + /// to (asynchronously) fetch the results. + /// + /// # Typical errors + /// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address. + /// + /// # References: + /// - + /// - + /// - + /// - + resolve-addresses: func(network: borrow, name: string) -> result; + } + + interface tcp { + use wasi:io/streams@0.2.0.{input-stream}; + use wasi:io/streams@0.2.0.{output-stream}; + use wasi:io/poll@0.2.0.{pollable}; + use wasi:clocks/monotonic-clock@0.2.0.{duration}; + use network.{network}; + use network.{error-code}; + use network.{ip-socket-address}; + use network.{ip-address-family}; + enum shutdown-type { + /// Similar to `SHUT_RD` in POSIX. + receive, + /// Similar to `SHUT_WR` in POSIX. + send, + /// Similar to `SHUT_RDWR` in POSIX. + both + } + + /// A TCP socket resource. + /// + /// The socket can be in one of the following states: + /// - `unbound` + /// - `bind-in-progress` + /// - `bound` (See note below) + /// - `listen-in-progress` + /// - `listening` + /// - `connect-in-progress` + /// - `connected` + /// - `closed` + /// See + /// for a more information. + /// + /// Note: Except where explicitly mentioned, whenever this documentation uses + /// the term "bound" without backticks it actually means: in the `bound` state *or + /// higher*. + /// (i.e. `bound`, `listen-in-progress`, `listening`, `connect-in-progress` or `connected`) + /// + /// In addition to the general error codes documented on the + /// `network::error-code` type, TCP socket methods may always return + /// `error(invalid-state)` when in the `closed` state. + resource tcp-socket { + + /// Accept a new client socket. + /// + /// The returned socket is bound and in the `connected` state. The following properties + /// are inherited from the listener socket: + /// - `address-family` + /// - `keep-alive-enabled` + /// - `keep-alive-idle-time` + /// - `keep-alive-interval` + /// - `keep-alive-count` + /// - `hop-limit` + /// - `receive-buffer-size` + /// - `send-buffer-size` + /// + /// On success, this function returns the newly accepted client socket along with + /// a pair of streams that can be used to read & write to the connection. + /// + /// # Typical errors + /// - `invalid-state`: Socket is not in the `listening` state. (EINVAL) + /// - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) + /// - `connection-aborted`: An incoming connection was pending, but was terminated + /// by the client before this listener could accept it. (ECONNABORTED) + /// - `new-socket-limit`: The new socket resource could not be created because of + /// a system limit. (EMFILE, ENFILE) + /// + /// # References + /// - + /// - + /// - + /// - + accept: func() -> result, error-code>; + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + address-family: func() -> ip-address-family; + finish-bind: func() -> result<_, error-code>; + finish-connect: func() -> result, error-code>; + finish-listen: func() -> result<_, error-code>; + + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The TTL value must be 1 or higher. + hop-limit: func() -> result; + + /// Whether the socket is in the `listening` state. + /// + /// Equivalent to the SO_ACCEPTCONN socket option. + is-listening: func() -> bool; + + /// The maximum amount of keepalive packets TCP should send before aborting the connection. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or + /// rounded. + /// I.e. after setting a value, reading the same setting back may return a different + /// value. + /// + /// Equivalent to the TCP_KEEPCNT socket option. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + keep-alive-count: func() -> result; + + /// Enables or disables keepalive. + /// + /// The keepalive behavior can be adjusted using: + /// - `keep-alive-idle-time` + /// - `keep-alive-interval` + /// - `keep-alive-count` + /// These properties can be configured while `keep-alive-enabled` is false, but only + /// come into effect when `keep-alive-enabled` is true. + /// + /// Equivalent to the SO_KEEPALIVE socket option. + keep-alive-enabled: func() -> result; + + /// Amount of time the connection has to be idle before TCP starts sending keepalive + /// packets. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or + /// rounded. + /// I.e. after setting a value, reading the same setting back may return a different + /// value. + /// + /// Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS) + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + keep-alive-idle-time: func() -> result; + + /// The time between keepalive packets. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or + /// rounded. + /// I.e. after setting a value, reading the same setting back may return a different + /// value. + /// + /// Equivalent to the TCP_KEEPINTVL socket option. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + keep-alive-interval: func() -> result; + + /// Get the bound local address. + /// + /// POSIX mentions: + /// > If the socket has not been bound to a local name, the value + /// > stored in the object pointed to by `address` is unspecified. + /// + /// WASI is stricter and requires `local-address` to return `invalid-state` when the + /// socket hasn't been bound yet. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not bound to any local address. + /// + /// # References + /// - + /// - + /// - + /// - + local-address: func() -> result; + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or + /// rounded. + /// I.e. after setting a value, reading the same setting back may return a different + /// value. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + receive-buffer-size: func() -> result; + + /// Get the remote address. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + remote-address: func() -> result; + send-buffer-size: func() -> result; + set-hop-limit: func(value: u8) -> result<_, error-code>; + set-keep-alive-count: func(value: u32) -> result<_, error-code>; + set-keep-alive-enabled: func(value: bool) -> result<_, error-code>; + set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>; + set-keep-alive-interval: func(value: duration) -> result<_, error-code>; + + /// Hints the desired listen queue size. Implementations are free to ignore this. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or + /// rounded. + /// + /// # Typical errors + /// - `not-supported`: (set) The platform does not support changing the backlog + /// size after the initial listen. + /// - `invalid-argument`: (set) The provided value was 0. + /// - `invalid-state`: (set) The socket is in the `connect-in-progress` or + /// `connected` state. + set-listen-backlog-size: func(value: u64) -> result<_, error-code>; + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + set-send-buffer-size: func(value: u64) -> result<_, error-code>; + + /// Initiate a graceful shutdown. + /// + /// - `receive`: The socket is not expecting to receive any data from + /// the peer. The `input-stream` associated with this socket will be + /// closed. Any data still in the receive queue at time of calling + /// this method will be discarded. + /// - `send`: The socket has no more data to send to the peer. The `output-stream` + /// associated with this socket will be closed and a FIN packet will be sent. + /// - `both`: Same effect as `receive` & `send` combined. + /// + /// This function is idempotent. Shutting a down a direction more than once + /// has no effect and returns `ok`. + /// + /// The shutdown function does not close (drop) the socket. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code>; + + /// Bind the socket to a specific network on the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the + /// implementation to decide which + /// network interface(s) to bind to. + /// If the TCP/UDP port is zero, the socket will be bound to a random free port. + /// + /// Bind can be attempted multiple times on the same socket, even with + /// different arguments on each iteration. But never concurrently and + /// only as long as the previous bind failed. Once a bind succeeds, the + /// binding can't be changed anymore. + /// + /// # Typical errors + /// - `invalid-argument`: The `local-address` has the wrong address family. + /// (EAFNOSUPPORT, EFAULT on Windows) + /// - `invalid-argument`: `local-address` is not a unicast address. (EINVAL) + /// - `invalid-argument`: `local-address` is an IPv4-mapped IPv6 address. + /// (EINVAL) + /// - `invalid-state`: The socket is already bound. (EINVAL) + /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS + /// on Windows) + /// - `address-in-use`: Address is already in use. (EADDRINUSE) + /// - `address-not-bindable`: `local-address` is not an address that the `network` + /// can bind to. (EADDRNOTAVAIL) + /// - `not-in-progress`: A `bind` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. + /// (EWOULDBLOCK, EAGAIN) + /// + /// # Implementors note + /// When binding to a non-zero port, this bind operation shouldn't be affected by + /// the TIME_WAIT + /// state of a recently closed socket on the same local address. In practice this + /// means that the SO_REUSEADDR + /// socket option should be set implicitly on all platforms, except on Windows where + /// this is the default behavior + /// and SO_REUSEADDR performs something different entirely. + /// + /// Unlike in POSIX, in WASI the bind operation is async. This enables + /// interactive WASI hosts to inject permission prompts. Runtimes that + /// don't want to make use of this ability can simply call the native + /// `bind` as part of either `start-bind` or `finish-bind`. + /// + /// # References + /// - + /// - + /// - + /// - + start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + + /// Connect to a remote endpoint. + /// + /// On success: + /// - the socket is transitioned into the `connection` state. + /// - a pair of streams is returned that can be used to read & write to the connection + /// + /// After a failed connection attempt, the socket will be in the `closed` + /// state and the only valid action left is to `drop` the socket. A single + /// socket can not be used to connect more than once. + /// + /// # Typical errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. + /// (EAFNOSUPPORT) + /// - `invalid-argument`: `remote-address` is not a unicast address. (EINVAL, + /// ENETUNREACH on Linux, EAFNOSUPPORT on MacOS) + /// - `invalid-argument`: `remote-address` is an IPv4-mapped IPv6 address. + /// (EINVAL, EADDRNOTAVAIL on Illumos) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY + /// (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL + /// on Windows) + /// - `invalid-argument`: The socket is already attached to a different network. + /// The `network` passed to `connect` must be identical to the one passed to `bind`. + /// - `invalid-state`: The socket is already in the `connected` state. + /// (EISCONN) + /// - `invalid-state`: The socket is already in the `listening` state. + /// (EOPNOTSUPP, EINVAL on Windows) + /// - `timeout`: Connection timed out. (ETIMEDOUT) + /// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) + /// - `connection-reset`: The connection was reset. (ECONNRESET) + /// - `connection-aborted`: The connection was aborted. (ECONNABORTED) + /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, + /// EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `address-in-use`: Tried to perform an implicit bind, but there were + /// no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// - `not-in-progress`: A connect operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. + /// (EWOULDBLOCK, EAGAIN) + /// + /// # Implementors note + /// The POSIX equivalent of `start-connect` is the regular `connect` syscall. + /// Because all WASI sockets are non-blocking this is expected to return + /// EINPROGRESS, which should be translated to `ok()` in WASI. + /// + /// The POSIX equivalent of `finish-connect` is a `poll` for event `POLLOUT` + /// with a timeout of 0 on the socket descriptor. Followed by a check for + /// the `SO_ERROR` socket option, in case the poll signaled readiness. + /// + /// # References + /// - + /// - + /// - + /// - + start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code>; + + /// Start listening for new connections. + /// + /// Transitions the socket into the `listening` state. + /// + /// Unlike POSIX, the socket must already be explicitly bound. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not bound to any local address. (EDESTADDRREQ) + /// - `invalid-state`: The socket is already in the `connected` state. + /// (EISCONN, EINVAL on BSD) + /// - `invalid-state`: The socket is already in the `listening` state. + /// - `address-in-use`: Tried to perform an implicit bind, but there were + /// no ephemeral ports available. (EADDRINUSE) + /// - `not-in-progress`: A listen operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. + /// (EWOULDBLOCK, EAGAIN) + /// + /// # Implementors note + /// Unlike in POSIX, in WASI the listen operation is async. This enables + /// interactive WASI hosts to inject permission prompts. Runtimes that + /// don't want to make use of this ability can simply call the native + /// `listen` as part of either `start-listen` or `finish-listen`. + /// + /// # References + /// - + /// - + /// - + /// - + start-listen: func() -> result<_, error-code>; + + /// Create a `pollable` which can be used to poll for, or block on, + /// completion of any of the asynchronous operations of this socket. + /// + /// When `finish-bind`, `finish-listen`, `finish-connect` or `accept` + /// return `error(would-block)`, this pollable can be used to wait for + /// their success or failure, after which the method can be retried. + /// + /// The pollable is not limited to the async operation that happens to be + /// in progress at the time of calling `subscribe` (if any). Theoretically, + /// `subscribe` only has to be called once per socket and can then be + /// (re)used for the remainder of the socket's lifetime. + /// + /// See + /// for a more information. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + } + } + + interface tcp-create-socket { + use network.{network}; + use network.{error-code}; + use network.{ip-address-family}; + use tcp.{tcp-socket}; + + /// Create a new TCP socket. + /// + /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. + /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. + /// + /// This function does not require a network capability handle. This is considered + /// to be safe because + /// at time of creation, the socket is not bound to any `network` yet. Up to the moment + /// `bind`/`connect` + /// is called, the socket is effectively an in-memory configuration object, unable + /// to communicate with the outside world. + /// + /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous + /// operations. + /// + /// # Typical errors + /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) + /// - `new-socket-limit`: The new socket resource could not be created because of + /// a system limit. (EMFILE, ENFILE) + /// + /// # References + /// - + /// - + /// - + /// - + create-tcp-socket: func(address-family: ip-address-family) -> result; + } + + interface udp { + use wasi:io/poll@0.2.0.{pollable}; + use network.{network}; + use network.{error-code}; + use network.{ip-socket-address}; + use network.{ip-address-family}; + + /// A received datagram. + record incoming-datagram { + /// The payload. + /// + /// Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. + data: list, + /// The source address. + /// + /// This field is guaranteed to match the remote address the stream was initialized + /// with, if any. + /// + /// Equivalent to the `src_addr` out parameter of `recvfrom`. + remote-address: ip-socket-address, + } + + /// A datagram to be sent out. + record outgoing-datagram { + /// The payload. + data: list, + /// The destination address. + /// + /// The requirements on this field depend on how the stream was initialized: + /// - with a remote address: this field must be None or match the stream's remote + /// address exactly. + /// - without a remote address: this field is required. + /// + /// If this value is None, the send operation is equivalent to `send` in POSIX. Otherwise + /// it is equivalent to `sendto`. + remote-address: option, + } + + /// A UDP socket handle. + resource udp-socket { + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + address-family: func() -> ip-address-family; + finish-bind: func() -> result<_, error-code>; + + /// Get the current bound address. + /// + /// POSIX mentions: + /// > If the socket has not been bound to a local name, the value + /// > stored in the object pointed to by `address` is unspecified. + /// + /// WASI is stricter and requires `local-address` to return `invalid-state` when the + /// socket hasn't been bound yet. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not bound to any local address. + /// + /// # References + /// - + /// - + /// - + /// - + local-address: func() -> result; + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or + /// rounded. + /// I.e. after setting a value, reading the same setting back may return a different + /// value. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + receive-buffer-size: func() -> result; + + /// Get the address the socket is currently streaming to. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not streaming to a specific remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + remote-address: func() -> result; + send-buffer-size: func() -> result; + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + set-send-buffer-size: func(value: u64) -> result<_, error-code>; + set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; + + /// Bind the socket to a specific network on the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the + /// implementation to decide which + /// network interface(s) to bind to. + /// If the port is zero, the socket will be bound to a random free port. + /// + /// # Typical errors + /// - `invalid-argument`: The `local-address` has the wrong address family. + /// (EAFNOSUPPORT, EFAULT on Windows) + /// - `invalid-state`: The socket is already bound. (EINVAL) + /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS + /// on Windows) + /// - `address-in-use`: Address is already in use. (EADDRINUSE) + /// - `address-not-bindable`: `local-address` is not an address that the `network` + /// can bind to. (EADDRNOTAVAIL) + /// - `not-in-progress`: A `bind` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. + /// (EWOULDBLOCK, EAGAIN) + /// + /// # Implementors note + /// Unlike in POSIX, in WASI the bind operation is async. This enables + /// interactive WASI hosts to inject permission prompts. Runtimes that + /// don't want to make use of this ability can simply call the native + /// `bind` as part of either `start-bind` or `finish-bind`. + /// + /// # References + /// - + /// - + /// - + /// - + start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + + /// Set up inbound & outbound communication channels, optionally to a specific peer. + /// + /// This function only changes the local socket configuration and does not generate + /// any network traffic. + /// On success, the `remote-address` of the socket is updated. The `local-address` + /// may be updated as well, + /// based on the best network path to `remote-address`. + /// + /// When a `remote-address` is provided, the returned streams are limited to communicating + /// with that specific peer: + /// - `send` can only be used to send to this destination. + /// - `receive` will only return datagrams sent from the provided `remote-address`. + /// + /// This method may be called multiple times on the same socket to change its association, + /// but + /// only the most recently returned pair of streams will be operational. Implementations + /// may trap if + /// the streams returned by a previous invocation haven't been dropped yet before + /// calling `stream` again. + /// + /// The POSIX equivalent in pseudo-code is: + /// ```text + /// if (was previously connected) { + /// connect(s, AF_UNSPEC) + /// } + /// if (remote_address is Some) { + /// connect(s, remote_address) + /// } + /// ``` + /// + /// Unlike in POSIX, the socket must already be explicitly bound. + /// + /// # Typical errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. + /// (EAFNOSUPPORT) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY + /// (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, + /// EADDRNOTAVAIL) + /// - `invalid-state`: The socket is not bound. + /// - `address-in-use`: Tried to perform an implicit bind, but there were + /// no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, + /// ENETRESET, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `connection-refused`: The connection was refused. (ECONNREFUSED) + /// + /// # References + /// - + /// - + /// - + /// - + %stream: func(remote-address: option) -> result, error-code>; + + /// Create a `pollable` which will resolve once the socket is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The TTL value must be 1 or higher. + unicast-hop-limit: func() -> result; + } + resource incoming-datagram-stream { + + /// Receive messages on the socket. + /// + /// This function attempts to receive up to `max-results` datagrams on the socket + /// without blocking. + /// The returned list may contain fewer elements than requested, but never more. + /// + /// This function returns successfully with an empty list when either: + /// - `max-results` is 0, or: + /// - `max-results` is greater than 0, but no results are immediately available. + /// This function never returns `error(would-block)`. + /// + /// # Typical errors + /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET + /// on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `connection-refused`: The connection was refused. (ECONNREFUSED) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + receive: func(max-results: u64) -> result, error-code>; + + /// Create a `pollable` which will resolve once the stream is ready to receive again. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + } + resource outgoing-datagram-stream { + + /// Check readiness for sending. This function never blocks. + /// + /// Returns the number of datagrams permitted for the next call to `send`, + /// or an error. Calling `send` with more datagrams than this function has + /// permitted will trap. + /// + /// When this function returns ok(0), the `subscribe` pollable will + /// become ready when this function will report at least ok(1), or an + /// error. + /// + /// Never returns `would-block`. + check-send: func() -> result; + + /// Send messages on the socket. + /// + /// This function attempts to send all provided `datagrams` on the socket without + /// blocking and + /// returns how many messages were actually sent (or queued for sending). This function + /// never + /// returns `error(would-block)`. If none of the datagrams were able to be sent, `ok(0)` + /// is returned. + /// + /// This function semantically behaves the same as iterating the `datagrams` list + /// and sequentially + /// sending each individual datagram until either the end of the list has been reached + /// or the first error occurred. + /// If at least one datagram has been sent successfully, this function never returns + /// an error. + /// + /// If the input list is empty, the function returns `ok(0)`. + /// + /// Each call to `send` must be permitted by a preceding `check-send`. Implementations + /// must trap if + /// either `check-send` was not called or `datagrams` contains more items than `check-send` + /// permitted. + /// + /// # Typical errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. + /// (EAFNOSUPPORT) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY + /// (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, + /// EADDRNOTAVAIL) + /// - `invalid-argument`: The socket is in "connected" mode and `remote-address` + /// is `some` value that does not match the address passed to `stream`. (EISCONN) + /// - `invalid-argument`: The socket is not "connected" and no value for `remote-address` + /// was provided. (EDESTADDRREQ) + /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, + /// ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `connection-refused`: The connection was refused. (ECONNREFUSED) + /// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + send: func(datagrams: list) -> result; + + /// Create a `pollable` which will resolve once the stream is ready to send again. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + } + } + + interface udp-create-socket { + use network.{network}; + use network.{error-code}; + use network.{ip-address-family}; + use udp.{udp-socket}; + + /// Create a new UDP socket. + /// + /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. + /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. + /// + /// This function does not require a network capability handle. This is considered + /// to be safe because + /// at time of creation, the socket is not bound to any `network` yet. Up to the moment + /// `bind` is called, + /// the socket is effectively an in-memory configuration object, unable to communicate + /// with the outside world. + /// + /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous + /// operations. + /// + /// # Typical errors + /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) + /// - `new-socket-limit`: The new socket resource could not be created because of + /// a system limit. (EMFILE, ENFILE) + /// + /// # References: + /// - + /// - + /// - + /// - + create-udp-socket: func(address-family: ip-address-family) -> result; + } +} + +package wasi:clocks@0.2.0 { + /// WASI Monotonic Clock is a clock API intended to let users measure elapsed + /// time. + /// + /// It is intended to be portable at least between Unix-family platforms and + /// Windows. + /// + /// A monotonic clock is a clock which has an unspecified initial value, and + /// successive reads of the clock will produce non-decreasing values. + /// + /// It is intended for measuring elapsed time. + interface monotonic-clock { + use wasi:io/poll@0.2.0.{pollable}; + + /// An instant in time, in nanoseconds. An instant is relative to an + /// unspecified initial value, and can only be compared to instances from + /// the same monotonic-clock. + type instant = u64; + + /// A duration of time, in nanoseconds. + type duration = u64; + + /// Read the current value of the clock. + /// + /// The clock is monotonic, therefore calling this function repeatedly will + /// produce a sequence of non-decreasing values. + now: func() -> instant; + + /// Query the resolution of the clock. Returns the duration of time + /// corresponding to a clock tick. + resolution: func() -> duration; + + /// Create a `pollable` which will resolve once the specified instant + /// occured. + subscribe-instant: func(when: instant) -> pollable; + + /// Create a `pollable` which will resolve once the given duration has + /// elapsed, starting at the time at which this function was called. + /// occured. + subscribe-duration: func(when: duration) -> pollable; + } + + /// WASI Wall Clock is a clock API intended to let users query the current + /// time. The name "wall" makes an analogy to a "clock on the wall", which + /// is not necessarily monotonic as it may be reset. + /// + /// It is intended to be portable at least between Unix-family platforms and + /// Windows. + /// + /// A wall clock is a clock which measures the date and time according to + /// some external reference. + /// + /// External references may be reset, so this clock is not necessarily + /// monotonic, making it unsuitable for measuring elapsed time. + /// + /// It is intended for reporting the current date and time for humans. + interface wall-clock { + /// A time and date in seconds plus nanoseconds. + record datetime { + seconds: u64, + nanoseconds: u32, + } + + /// Read the current value of the clock. + /// + /// This clock is not monotonic, therefore calling this function repeatedly + /// will not necessarily produce a sequence of non-decreasing values. + /// + /// The returned timestamps represent the number of seconds since + /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], + /// also known as [Unix Time]. + /// + /// The nanoseconds field of the output is always less than 1000000000. + /// + /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 + /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time + now: func() -> datetime; + + /// Query the resolution of the clock. + /// + /// The nanoseconds field of the output is always less than 1000000000. + resolution: func() -> datetime; + } +} + +package wasi:io@0.2.0 { + interface error { + /// A resource which represents some error information. + /// + /// The only method provided by this resource is `to-debug-string`, + /// which provides some human-readable information about the error. + /// + /// In the `wasi:io` package, this resource is returned through the + /// `wasi:io/streams/stream-error` type. + /// + /// To provide more specific error information, other interfaces may + /// provide functions to further "downcast" this error into more specific + /// error information. For example, `error`s returned in streams derived + /// from filesystem types to be described using the filesystem's own + /// error-code type, using the function + /// `wasi:filesystem/types/filesystem-error-code`, which takes a parameter + /// `borrow` and returns + /// `option`. + /// + /// The set of functions which can "downcast" an `error` into a more + /// concrete type is open. + resource error { + + /// Returns a string that is suitable to assist humans in debugging + /// this error. + /// + /// WARNING: The returned string should not be consumed mechanically! + /// It may change across platforms, hosts, or other implementation + /// details. Parsing this string is a major platform-compatibility + /// hazard. + to-debug-string: func() -> string; + } + } + + /// A poll API intended to let users wait for I/O events on multiple handles + /// at once. + interface poll { + /// `pollable` represents a single I/O event which may be ready, or not. + resource pollable { + + /// `block` returns immediately if the pollable is ready, and otherwise + /// blocks until ready. + /// + /// This function is equivalent to calling `poll.poll` on a list + /// containing only this pollable. + block: func(); + + /// Return the readiness of a pollable. This function never blocks. + /// + /// Returns `true` when the pollable is ready, and `false` otherwise. + ready: func() -> bool; + } + + /// Poll for completion on a set of pollables. + /// + /// This function takes a list of pollables, which identify I/O sources of + /// interest, and waits until one or more of the events is ready for I/O. + /// + /// The result `list` contains one or more indices of handles in the + /// argument list that is ready for I/O. + /// + /// If the list contains more elements than can be indexed with a `u32` + /// value, this function traps. + /// + /// A timeout can be implemented by adding a pollable from the + /// wasi-clocks API to the list. + /// + /// This function does not return a `result`; polling in itself does not + /// do any I/O so it doesn't fail. If any of the I/O sources identified by + /// the pollables has an error, it is indicated by marking the source as + /// being reaedy for I/O. + poll: func(in: list>) -> list; + } + + /// WASI I/O is an I/O abstraction API which is currently focused on providing + /// stream types. + /// + /// In the future, the component model is expected to add built-in stream types; + /// when it does, they are expected to subsume this API. + interface streams { + use error.{error}; + use poll.{pollable}; + + /// An error for input-stream and output-stream operations. + variant stream-error { + /// The last operation (a write or flush) failed before completion. + /// + /// More information is available in the `error` payload. + last-operation-failed(error), + /// The stream is closed: no more input will be accepted by the + /// stream. A closed output-stream will return this error on all + /// future operations. + closed, + } + + /// An input bytestream. + /// + /// `input-stream`s are *non-blocking* to the extent practical on underlying + /// platforms. I/O operations always return promptly; if fewer bytes are + /// promptly available than requested, they return the number of bytes promptly + /// available, which could even be zero. To wait for data to be available, + /// use the `subscribe` function to obtain a `pollable` which can be polled + /// for using `wasi:io/poll`. + resource input-stream { + + /// Read bytes from a stream, after blocking until at least one byte can + /// be read. Except for blocking, behavior is identical to `read`. + blocking-read: func(len: u64) -> result, stream-error>; + + /// Skip bytes from a stream, after blocking until at least one byte + /// can be skipped. Except for blocking behavior, identical to `skip`. + blocking-skip: func(len: u64) -> result; + + /// Perform a non-blocking read from the stream. + /// + /// When the source of a `read` is binary data, the bytes from the source + /// are returned verbatim. When the source of a `read` is known to the + /// implementation to be text, bytes containing the UTF-8 encoding of the + /// text are returned. + /// + /// This function returns a list of bytes containing the read data, + /// when successful. The returned list will contain up to `len` bytes; + /// it may return fewer than requested, but not more. The list is + /// empty when no bytes are available for reading at this time. The + /// pollable given by `subscribe` will be ready when more bytes are + /// available. + /// + /// This function fails with a `stream-error` when the operation + /// encounters an error, giving `last-operation-failed`, or when the + /// stream is closed, giving `closed`. + /// + /// When the caller gives a `len` of 0, it represents a request to + /// read 0 bytes. If the stream is still open, this call should + /// succeed and return an empty list, or otherwise fail with `closed`. + /// + /// The `len` parameter is a `u64`, which could represent a list of u8 which + /// is not possible to allocate in wasm32, or not desirable to allocate as + /// as a return value by the callee. The callee may return a list of bytes + /// less than `len` in size while more bytes are available for reading. + read: func(len: u64) -> result, stream-error>; + + /// Skip bytes from a stream. Returns number of bytes skipped. + /// + /// Behaves identical to `read`, except instead of returning a list + /// of bytes, returns the number of bytes consumed from the stream. + skip: func(len: u64) -> result; + + /// Create a `pollable` which will resolve once either the specified stream + /// has bytes available to read or the other end of the stream has been + /// closed. + /// The created `pollable` is a child resource of the `input-stream`. + /// Implementations may trap if the `input-stream` is dropped before + /// all derived `pollable`s created with this function are dropped. + subscribe: func() -> pollable; + } + + /// An output bytestream. + /// + /// `output-stream`s are *non-blocking* to the extent practical on + /// underlying platforms. Except where specified otherwise, I/O operations also + /// always return promptly, after the number of bytes that can be written + /// promptly, which could even be zero. To wait for the stream to be ready to + /// accept data, the `subscribe` function to obtain a `pollable` which can be + /// polled for using `wasi:io/poll`. + resource output-stream { + + /// Request to flush buffered output, and block until flush completes + /// and stream is ready for writing again. + blocking-flush: func() -> result<_, stream-error>; + + /// Read from one stream and write to another, with blocking. + /// + /// This is similar to `splice`, except that it blocks until the + /// `output-stream` is ready for writing, and the `input-stream` + /// is ready for reading, before performing the `splice`. + blocking-splice: func(src: borrow, len: u64) -> result; + + /// Perform a write of up to 4096 bytes, and then flush the stream. Block + /// until all of these operations are complete, or an error occurs. + /// + /// This is a convenience wrapper around the use of `check-write`, + /// `subscribe`, `write`, and `flush`, and is implemented with the + /// following pseudo-code: + /// + /// ```text + /// let pollable = this.subscribe(); + /// while !contents.is_empty() { + /// // Wait for the stream to become writable + /// pollable.block(); + /// let Ok(n) = this.check-write(); // eliding error handling + /// let len = min(n, contents.len()); + /// let (chunk, rest) = contents.split_at(len); + /// this.write(chunk ); // eliding error handling + /// contents = rest; + /// } + /// this.flush(); + /// // Wait for completion of `flush` + /// pollable.block(); + /// // Check for any errors that arose during `flush` + /// let _ = this.check-write(); // eliding error handling + /// ``` + blocking-write-and-flush: func(contents: list) -> result<_, stream-error>; + + /// Perform a write of up to 4096 zeroes, and then flush the stream. + /// Block until all of these operations are complete, or an error + /// occurs. + /// + /// This is a convenience wrapper around the use of `check-write`, + /// `subscribe`, `write-zeroes`, and `flush`, and is implemented with + /// the following pseudo-code: + /// + /// ```text + /// let pollable = this.subscribe(); + /// while num_zeroes != 0 { + /// // Wait for the stream to become writable + /// pollable.block(); + /// let Ok(n) = this.check-write(); // eliding error handling + /// let len = min(n, num_zeroes); + /// this.write-zeroes(len); // eliding error handling + /// num_zeroes -= len; + /// } + /// this.flush(); + /// // Wait for completion of `flush` + /// pollable.block(); + /// // Check for any errors that arose during `flush` + /// let _ = this.check-write(); // eliding error handling + /// ``` + blocking-write-zeroes-and-flush: func(len: u64) -> result<_, stream-error>; + + /// Check readiness for writing. This function never blocks. + /// + /// Returns the number of bytes permitted for the next call to `write`, + /// or an error. Calling `write` with more bytes than this function has + /// permitted will trap. + /// + /// When this function returns 0 bytes, the `subscribe` pollable will + /// become ready when this function will report at least 1 byte, or an + /// error. + check-write: func() -> result; + + /// Request to flush buffered output. This function never blocks. + /// + /// This tells the output-stream that the caller intends any buffered + /// output to be flushed. the output which is expected to be flushed + /// is all that has been passed to `write` prior to this call. + /// + /// Upon calling this function, the `output-stream` will not accept any + /// writes (`check-write` will return `ok(0)`) until the flush has + /// completed. The `subscribe` pollable will become ready when the + /// flush has completed and the stream can accept more writes. + flush: func() -> result<_, stream-error>; + + /// Read from one stream and write to another. + /// + /// The behavior of splice is equivelant to: + /// 1. calling `check-write` on the `output-stream` + /// 2. calling `read` on the `input-stream` with the smaller of the + /// `check-write` permitted length and the `len` provided to `splice` + /// 3. calling `write` on the `output-stream` with that read data. + /// + /// Any error reported by the call to `check-write`, `read`, or + /// `write` ends the splice and reports that error. + /// + /// This function returns the number of bytes transferred; it may be less + /// than `len`. + splice: func(src: borrow, len: u64) -> result; + + /// Create a `pollable` which will resolve once the output-stream + /// is ready for more writing, or an error has occured. When this + /// pollable is ready, `check-write` will return `ok(n)` with n>0, or an + /// error. + /// + /// If the stream is closed, this pollable is always ready immediately. + /// + /// The created `pollable` is a child resource of the `output-stream`. + /// Implementations may trap if the `output-stream` is dropped before + /// all derived `pollable`s created with this function are dropped. + subscribe: func() -> pollable; + + /// Perform a write. This function never blocks. + /// + /// When the destination of a `write` is binary data, the bytes from + /// `contents` are written verbatim. When the destination of a `write` is + /// known to the implementation to be text, the bytes of `contents` are + /// transcoded from UTF-8 into the encoding of the destination and then + /// written. + /// + /// Precondition: check-write gave permit of Ok(n) and contents has a + /// length of less than or equal to n. Otherwise, this function will trap. + /// + /// returns Err(closed) without writing if the stream has closed since + /// the last call to check-write provided a permit. + write: func(contents: list) -> result<_, stream-error>; + + /// Write zeroes to a stream. + /// + /// This should be used precisely like `write` with the exact same + /// preconditions (must use check-write first), but instead of + /// passing a list of bytes, you simply pass the number of zero-bytes + /// that should be written. + write-zeroes: func(len: u64) -> result<_, stream-error>; + } + } +} + +package wasi:random@0.2.0 { + /// The insecure-seed interface for seeding hash-map DoS resistance. + /// + /// It is intended to be portable at least between Unix-family platforms and + /// Windows. + interface insecure-seed { + /// Return a 128-bit value that may contain a pseudo-random value. + /// + /// The returned value is not required to be computed from a CSPRNG, and may + /// even be entirely deterministic. Host implementations are encouraged to + /// provide pseudo-random values to any program exposed to + /// attacker-controlled content, to enable DoS protection built into many + /// languages' hash-map implementations. + /// + /// This function is intended to only be called once, by a source language + /// to initialize Denial Of Service (DoS) protection in its hash-map + /// implementation. + /// + /// # Expected future evolution + /// + /// This will likely be changed to a value import, to prevent it from being + /// called multiple times and potentially used for purposes other than DoS + /// protection. + insecure-seed: func() -> tuple; + } + + /// The insecure interface for insecure pseudo-random numbers. + /// + /// It is intended to be portable at least between Unix-family platforms and + /// Windows. + interface insecure { + /// Return `len` insecure pseudo-random bytes. + /// + /// This function is not cryptographically secure. Do not use it for + /// anything related to security. + /// + /// There are no requirements on the values of the returned bytes, however + /// implementations are encouraged to return evenly distributed values with + /// a long period. + get-insecure-random-bytes: func(len: u64) -> list; + + /// Return an insecure pseudo-random `u64` value. + /// + /// This function returns the same type of pseudo-random data as + /// `get-insecure-random-bytes`, represented as a `u64`. + get-insecure-random-u64: func() -> u64; + } + + /// WASI Random is a random data API. + /// + /// It is intended to be portable at least between Unix-family platforms and + /// Windows. + interface random { + /// Return `len` cryptographically-secure random or pseudo-random bytes. + /// + /// This function must produce data at least as cryptographically secure and + /// fast as an adequately seeded cryptographically-secure pseudo-random + /// number generator (CSPRNG). It must not block, from the perspective of + /// the calling program, under any circumstances, including on the first + /// request and on requests for numbers of bytes. The returned data must + /// always be unpredictable. + /// + /// This function must always return fresh data. Deterministic environments + /// must omit this function, rather than implementing it with deterministic + /// data. + get-random-bytes: func(len: u64) -> list; + + /// Return a cryptographically-secure random or pseudo-random `u64` value. + /// + /// This function returns the same type of data as `get-random-bytes`, + /// represented as a `u64`. + get-random-u64: func() -> u64; + } +} diff --git a/src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go b/src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go new file mode 100755 index 0000000000..07d9509af7 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go @@ -0,0 +1,21 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package environment + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". + +//go:wasmimport wasi:cli/environment@0.2.0 get-environment +//go:noescape +func wasmimport_GetEnvironment(result *cm.List[[2]string]) + +//go:wasmimport wasi:cli/environment@0.2.0 get-arguments +//go:noescape +func wasmimport_GetArguments(result *cm.List[string]) + +//go:wasmimport wasi:cli/environment@0.2.0 initial-cwd +//go:noescape +func wasmimport_InitialCWD(result *cm.Option[string]) diff --git a/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go b/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go index a3cd386c22..0b84245156 100644 --- a/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go +++ b/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go @@ -26,10 +26,6 @@ func GetEnvironment() (result cm.List[[2]string]) { return } -//go:wasmimport wasi:cli/environment@0.2.0 get-environment -//go:noescape -func wasmimport_GetEnvironment(result *cm.List[[2]string]) - // GetArguments represents the imported function "get-arguments". // // Get the POSIX-style arguments to the program. @@ -42,10 +38,6 @@ func GetArguments() (result cm.List[string]) { return } -//go:wasmimport wasi:cli/environment@0.2.0 get-arguments -//go:noescape -func wasmimport_GetArguments(result *cm.List[string]) - // InitialCWD represents the imported function "initial-cwd". // // Return a path that programs should use as their initial current working @@ -58,7 +50,3 @@ func InitialCWD() (result cm.Option[string]) { wasmimport_InitialCWD(&result) return } - -//go:wasmimport wasi:cli/environment@0.2.0 initial-cwd -//go:noescape -func wasmimport_InitialCWD(result *cm.Option[string]) diff --git a/src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go b/src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go new file mode 100755 index 0000000000..849d5f503f --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go @@ -0,0 +1,9 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package exit + +// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". + +//go:wasmimport wasi:cli/exit@0.2.0 exit +//go:noescape +func wasmimport_Exit(status0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go b/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go index b506fa1857..f3f172e68b 100644 --- a/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go +++ b/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go @@ -19,7 +19,3 @@ func Exit(status cm.BoolResult) { wasmimport_Exit((uint32)(status0)) return } - -//go:wasmimport wasi:cli/exit@0.2.0 exit -//go:noescape -func wasmimport_Exit(status0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/run/run.wasm.go b/src/internal/wasi/cli/v0.2.0/run/run.wasm.go new file mode 100755 index 0000000000..d7e20574e7 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/run/run.wasm.go @@ -0,0 +1,17 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package run + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". + +//go:wasmexport wasi:cli/run@0.2.0#run +//export wasi:cli/run@0.2.0#run +func wasmexport_Run() (result0 uint32) { + result := Exports.Run() + result0 = cm.BoolToU32(result) + return +} diff --git a/src/internal/wasi/cli/v0.2.0/run/run.wit.go b/src/internal/wasi/cli/v0.2.0/run/run.wit.go index 51eb5fb311..4cea75d300 100644 --- a/src/internal/wasi/cli/v0.2.0/run/run.wit.go +++ b/src/internal/wasi/cli/v0.2.0/run/run.wit.go @@ -2,15 +2,3 @@ // Package run represents the exported interface "wasi:cli/run@0.2.0". package run - -import ( - "internal/cm" -) - -//go:wasmexport wasi:cli/run@0.2.0#run -//export wasi:cli/run@0.2.0#run -func wasmexport_Run() (result0 uint32) { - result := Exports.Run() - result0 = cm.BoolToU32(result) - return -} diff --git a/src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go new file mode 100755 index 0000000000..462cf172ca --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go @@ -0,0 +1,9 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package stderr + +// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". + +//go:wasmimport wasi:cli/stderr@0.2.0 get-stderr +//go:noescape +func wasmimport_GetStderr() (result0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go index 4486147535..8ebfdeed5f 100644 --- a/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go +++ b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go @@ -8,17 +8,18 @@ import ( "internal/wasi/io/v0.2.0/streams" ) +// OutputStream represents the imported type alias "wasi:cli/stderr@0.2.0#output-stream". +// +// See [streams.OutputStream] for more information. +type OutputStream = streams.OutputStream + // GetStderr represents the imported function "get-stderr". // // get-stderr: func() -> output-stream // //go:nosplit -func GetStderr() (result streams.OutputStream) { +func GetStderr() (result OutputStream) { result0 := wasmimport_GetStderr() - result = cm.Reinterpret[streams.OutputStream]((uint32)(result0)) + result = cm.Reinterpret[OutputStream]((uint32)(result0)) return } - -//go:wasmimport wasi:cli/stderr@0.2.0 get-stderr -//go:noescape -func wasmimport_GetStderr() (result0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go new file mode 100755 index 0000000000..374eb2531f --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go @@ -0,0 +1,9 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package stdin + +// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". + +//go:wasmimport wasi:cli/stdin@0.2.0 get-stdin +//go:noescape +func wasmimport_GetStdin() (result0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go index c777175698..e697cd15d4 100644 --- a/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go +++ b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go @@ -8,17 +8,18 @@ import ( "internal/wasi/io/v0.2.0/streams" ) +// InputStream represents the imported type alias "wasi:cli/stdin@0.2.0#input-stream". +// +// See [streams.InputStream] for more information. +type InputStream = streams.InputStream + // GetStdin represents the imported function "get-stdin". // // get-stdin: func() -> input-stream // //go:nosplit -func GetStdin() (result streams.InputStream) { +func GetStdin() (result InputStream) { result0 := wasmimport_GetStdin() - result = cm.Reinterpret[streams.InputStream]((uint32)(result0)) + result = cm.Reinterpret[InputStream]((uint32)(result0)) return } - -//go:wasmimport wasi:cli/stdin@0.2.0 get-stdin -//go:noescape -func wasmimport_GetStdin() (result0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go new file mode 100755 index 0000000000..68e4a3dac9 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go @@ -0,0 +1,9 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package stdout + +// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". + +//go:wasmimport wasi:cli/stdout@0.2.0 get-stdout +//go:noescape +func wasmimport_GetStdout() (result0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go index 4a7007f0e0..a9934fe2f3 100644 --- a/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go +++ b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go @@ -8,17 +8,18 @@ import ( "internal/wasi/io/v0.2.0/streams" ) +// OutputStream represents the imported type alias "wasi:cli/stdout@0.2.0#output-stream". +// +// See [streams.OutputStream] for more information. +type OutputStream = streams.OutputStream + // GetStdout represents the imported function "get-stdout". // // get-stdout: func() -> output-stream // //go:nosplit -func GetStdout() (result streams.OutputStream) { +func GetStdout() (result OutputStream) { result0 := wasmimport_GetStdout() - result = cm.Reinterpret[streams.OutputStream]((uint32)(result0)) + result = cm.Reinterpret[OutputStream]((uint32)(result0)) return } - -//go:wasmimport wasi:cli/stdout@0.2.0 get-stdout -//go:noescape -func wasmimport_GetStdout() (result0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go index 795be5d093..8313be74b2 100644 --- a/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go +++ b/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go @@ -30,7 +30,3 @@ func (self TerminalInput) ResourceDrop() { wasmimport_TerminalInputResourceDrop((uint32)(self0)) return } - -//go:wasmimport wasi:cli/terminal-input@0.2.0 [resource-drop]terminal-input -//go:noescape -func wasmimport_TerminalInputResourceDrop(self0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-input/terminalinput.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-input/terminalinput.wasm.go new file mode 100755 index 0000000000..1df3794f11 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-input/terminalinput.wasm.go @@ -0,0 +1,9 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package terminalinput + +// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". + +//go:wasmimport wasi:cli/terminal-input@0.2.0 [resource-drop]terminal-input +//go:noescape +func wasmimport_TerminalInputResourceDrop(self0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go index eb97c9ee7e..00d7231560 100644 --- a/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go +++ b/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go @@ -30,7 +30,3 @@ func (self TerminalOutput) ResourceDrop() { wasmimport_TerminalOutputResourceDrop((uint32)(self0)) return } - -//go:wasmimport wasi:cli/terminal-output@0.2.0 [resource-drop]terminal-output -//go:noescape -func wasmimport_TerminalOutputResourceDrop(self0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-output/terminaloutput.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-output/terminaloutput.wasm.go new file mode 100755 index 0000000000..fb35fc418e --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-output/terminaloutput.wasm.go @@ -0,0 +1,9 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package terminaloutput + +// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". + +//go:wasmimport wasi:cli/terminal-output@0.2.0 [resource-drop]terminal-output +//go:noescape +func wasmimport_TerminalOutputResourceDrop(self0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go index 72215a8b7c..9f942f3efb 100644 --- a/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go +++ b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go @@ -11,6 +11,11 @@ import ( terminaloutput "internal/wasi/cli/v0.2.0/terminal-output" ) +// TerminalOutput represents the imported type alias "wasi:cli/terminal-stderr@0.2.0#terminal-output". +// +// See [terminaloutput.TerminalOutput] for more information. +type TerminalOutput = terminaloutput.TerminalOutput + // GetTerminalStderr represents the imported function "get-terminal-stderr". // // If stderr is connected to a terminal, return a `terminal-output` handle @@ -19,11 +24,7 @@ import ( // get-terminal-stderr: func() -> option // //go:nosplit -func GetTerminalStderr() (result cm.Option[terminaloutput.TerminalOutput]) { +func GetTerminalStderr() (result cm.Option[TerminalOutput]) { wasmimport_GetTerminalStderr(&result) return } - -//go:wasmimport wasi:cli/terminal-stderr@0.2.0 get-terminal-stderr -//go:noescape -func wasmimport_GetTerminalStderr(result *cm.Option[terminaloutput.TerminalOutput]) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminalstderr.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminalstderr.wasm.go new file mode 100755 index 0000000000..ea8902ee89 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminalstderr.wasm.go @@ -0,0 +1,13 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package terminalstderr + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". + +//go:wasmimport wasi:cli/terminal-stderr@0.2.0 get-terminal-stderr +//go:noescape +func wasmimport_GetTerminalStderr(result *cm.Option[TerminalOutput]) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go index 1a6091d7c8..33a35eff36 100644 --- a/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go +++ b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go @@ -11,6 +11,11 @@ import ( terminalinput "internal/wasi/cli/v0.2.0/terminal-input" ) +// TerminalInput represents the imported type alias "wasi:cli/terminal-stdin@0.2.0#terminal-input". +// +// See [terminalinput.TerminalInput] for more information. +type TerminalInput = terminalinput.TerminalInput + // GetTerminalStdin represents the imported function "get-terminal-stdin". // // If stdin is connected to a terminal, return a `terminal-input` handle @@ -19,11 +24,7 @@ import ( // get-terminal-stdin: func() -> option // //go:nosplit -func GetTerminalStdin() (result cm.Option[terminalinput.TerminalInput]) { +func GetTerminalStdin() (result cm.Option[TerminalInput]) { wasmimport_GetTerminalStdin(&result) return } - -//go:wasmimport wasi:cli/terminal-stdin@0.2.0 get-terminal-stdin -//go:noescape -func wasmimport_GetTerminalStdin(result *cm.Option[terminalinput.TerminalInput]) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminalstdin.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminalstdin.wasm.go new file mode 100755 index 0000000000..d9417f353a --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminalstdin.wasm.go @@ -0,0 +1,13 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package terminalstdin + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". + +//go:wasmimport wasi:cli/terminal-stdin@0.2.0 get-terminal-stdin +//go:noescape +func wasmimport_GetTerminalStdin(result *cm.Option[TerminalInput]) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go index 3951449458..e6be82dd30 100644 --- a/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go +++ b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go @@ -11,6 +11,11 @@ import ( terminaloutput "internal/wasi/cli/v0.2.0/terminal-output" ) +// TerminalOutput represents the imported type alias "wasi:cli/terminal-stdout@0.2.0#terminal-output". +// +// See [terminaloutput.TerminalOutput] for more information. +type TerminalOutput = terminaloutput.TerminalOutput + // GetTerminalStdout represents the imported function "get-terminal-stdout". // // If stdout is connected to a terminal, return a `terminal-output` handle @@ -19,11 +24,7 @@ import ( // get-terminal-stdout: func() -> option // //go:nosplit -func GetTerminalStdout() (result cm.Option[terminaloutput.TerminalOutput]) { +func GetTerminalStdout() (result cm.Option[TerminalOutput]) { wasmimport_GetTerminalStdout(&result) return } - -//go:wasmimport wasi:cli/terminal-stdout@0.2.0 get-terminal-stdout -//go:noescape -func wasmimport_GetTerminalStdout(result *cm.Option[terminaloutput.TerminalOutput]) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminalstdout.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminalstdout.wasm.go new file mode 100755 index 0000000000..1c9e3719b8 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminalstdout.wasm.go @@ -0,0 +1,13 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package terminalstdout + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". + +//go:wasmimport wasi:cli/terminal-stdout@0.2.0 get-terminal-stdout +//go:noescape +func wasmimport_GetTerminalStdout(result *cm.Option[TerminalOutput]) diff --git a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go index 580bd2b552..7cfa7d1405 100644 --- a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go +++ b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go @@ -19,6 +19,11 @@ import ( "internal/wasi/io/v0.2.0/poll" ) +// Pollable represents the imported type alias "wasi:clocks/monotonic-clock@0.2.0#pollable". +// +// See [poll.Pollable] for more information. +type Pollable = poll.Pollable + // Instant represents the u64 "wasi:clocks/monotonic-clock@0.2.0#instant". // // An instant in time, in nanoseconds. An instant is relative to an @@ -51,10 +56,6 @@ func Now() (result Instant) { return } -//go:wasmimport wasi:clocks/monotonic-clock@0.2.0 now -//go:noescape -func wasmimport_Now() (result0 uint64) - // Resolution represents the imported function "resolution". // // Query the resolution of the clock. Returns the duration of time @@ -69,10 +70,6 @@ func Resolution() (result Duration) { return } -//go:wasmimport wasi:clocks/monotonic-clock@0.2.0 resolution -//go:noescape -func wasmimport_Resolution() (result0 uint64) - // SubscribeInstant represents the imported function "subscribe-instant". // // Create a `pollable` which will resolve once the specified instant @@ -81,17 +78,13 @@ func wasmimport_Resolution() (result0 uint64) // subscribe-instant: func(when: instant) -> pollable // //go:nosplit -func SubscribeInstant(when Instant) (result poll.Pollable) { +func SubscribeInstant(when Instant) (result Pollable) { when0 := (uint64)(when) result0 := wasmimport_SubscribeInstant((uint64)(when0)) - result = cm.Reinterpret[poll.Pollable]((uint32)(result0)) + result = cm.Reinterpret[Pollable]((uint32)(result0)) return } -//go:wasmimport wasi:clocks/monotonic-clock@0.2.0 subscribe-instant -//go:noescape -func wasmimport_SubscribeInstant(when0 uint64) (result0 uint32) - // SubscribeDuration represents the imported function "subscribe-duration". // // Create a `pollable` which will resolve once the given duration has @@ -101,13 +94,9 @@ func wasmimport_SubscribeInstant(when0 uint64) (result0 uint32) // subscribe-duration: func(when: duration) -> pollable // //go:nosplit -func SubscribeDuration(when Duration) (result poll.Pollable) { +func SubscribeDuration(when Duration) (result Pollable) { when0 := (uint64)(when) result0 := wasmimport_SubscribeDuration((uint64)(when0)) - result = cm.Reinterpret[poll.Pollable]((uint32)(result0)) + result = cm.Reinterpret[Pollable]((uint32)(result0)) return } - -//go:wasmimport wasi:clocks/monotonic-clock@0.2.0 subscribe-duration -//go:noescape -func wasmimport_SubscribeDuration(when0 uint64) (result0 uint32) diff --git a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonicclock.wasm.go b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonicclock.wasm.go new file mode 100755 index 0000000000..36a1720a6d --- /dev/null +++ b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonicclock.wasm.go @@ -0,0 +1,21 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package monotonicclock + +// This file contains wasmimport and wasmexport declarations for "wasi:clocks@0.2.0". + +//go:wasmimport wasi:clocks/monotonic-clock@0.2.0 now +//go:noescape +func wasmimport_Now() (result0 uint64) + +//go:wasmimport wasi:clocks/monotonic-clock@0.2.0 resolution +//go:noescape +func wasmimport_Resolution() (result0 uint64) + +//go:wasmimport wasi:clocks/monotonic-clock@0.2.0 subscribe-instant +//go:noescape +func wasmimport_SubscribeInstant(when0 uint64) (result0 uint32) + +//go:wasmimport wasi:clocks/monotonic-clock@0.2.0 subscribe-duration +//go:noescape +func wasmimport_SubscribeDuration(when0 uint64) (result0 uint32) diff --git a/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go b/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go index a59a313d24..7473c3e2c5 100644 --- a/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go +++ b/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go @@ -18,6 +18,10 @@ // It is intended for reporting the current date and time for humans. package wallclock +import ( + "internal/cm" +) + // DateTime represents the record "wasi:clocks/wall-clock@0.2.0#datetime". // // A time and date in seconds plus nanoseconds. @@ -27,6 +31,7 @@ package wallclock // nanoseconds: u32, // } type DateTime struct { + _ cm.HostLayout Seconds uint64 Nanoseconds uint32 } @@ -55,10 +60,6 @@ func Now() (result DateTime) { return } -//go:wasmimport wasi:clocks/wall-clock@0.2.0 now -//go:noescape -func wasmimport_Now(result *DateTime) - // Resolution represents the imported function "resolution". // // Query the resolution of the clock. @@ -72,7 +73,3 @@ func Resolution() (result DateTime) { wasmimport_Resolution(&result) return } - -//go:wasmimport wasi:clocks/wall-clock@0.2.0 resolution -//go:noescape -func wasmimport_Resolution(result *DateTime) diff --git a/src/internal/wasi/clocks/v0.2.0/wall-clock/wallclock.wasm.go b/src/internal/wasi/clocks/v0.2.0/wall-clock/wallclock.wasm.go new file mode 100755 index 0000000000..321ff3f1c3 --- /dev/null +++ b/src/internal/wasi/clocks/v0.2.0/wall-clock/wallclock.wasm.go @@ -0,0 +1,13 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package wallclock + +// This file contains wasmimport and wasmexport declarations for "wasi:clocks@0.2.0". + +//go:wasmimport wasi:clocks/wall-clock@0.2.0 now +//go:noescape +func wasmimport_Now(result *DateTime) + +//go:wasmimport wasi:clocks/wall-clock@0.2.0 resolution +//go:noescape +func wasmimport_Resolution(result *DateTime) diff --git a/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go new file mode 100755 index 0000000000..c5bae30593 --- /dev/null +++ b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go @@ -0,0 +1,13 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package preopens + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:filesystem@0.2.0". + +//go:wasmimport wasi:filesystem/preopens@0.2.0 get-directories +//go:noescape +func wasmimport_GetDirectories(result *cm.List[cm.Tuple[Descriptor, string]]) diff --git a/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go index d57dea5179..f655e854cb 100644 --- a/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go +++ b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go @@ -8,6 +8,11 @@ import ( "internal/wasi/filesystem/v0.2.0/types" ) +// Descriptor represents the imported type alias "wasi:filesystem/preopens@0.2.0#descriptor". +// +// See [types.Descriptor] for more information. +type Descriptor = types.Descriptor + // GetDirectories represents the imported function "get-directories". // // Return the set of preopened directories, and their path. @@ -15,11 +20,7 @@ import ( // get-directories: func() -> list> // //go:nosplit -func GetDirectories() (result cm.List[cm.Tuple[types.Descriptor, string]]) { +func GetDirectories() (result cm.List[cm.Tuple[Descriptor, string]]) { wasmimport_GetDirectories(&result) return } - -//go:wasmimport wasi:filesystem/preopens@0.2.0 get-directories -//go:noescape -func wasmimport_GetDirectories(result *cm.List[cm.Tuple[types.Descriptor, string]]) diff --git a/src/internal/wasi/filesystem/v0.2.0/types/abi.go b/src/internal/wasi/filesystem/v0.2.0/types/abi.go index 4cf4f32804..40ee28aa8a 100644 --- a/src/internal/wasi/filesystem/v0.2.0/types/abi.go +++ b/src/internal/wasi/filesystem/v0.2.0/types/abi.go @@ -8,18 +8,15 @@ import ( "unsafe" ) -// DateTimeShape is used for storage in variant or result types. -type DateTimeShape struct { - shape [unsafe.Sizeof(wallclock.DateTime{})]byte -} - // MetadataHashValueShape is used for storage in variant or result types. type MetadataHashValueShape struct { + _ cm.HostLayout shape [unsafe.Sizeof(MetadataHashValue{})]byte } // TupleListU8BoolShape is used for storage in variant or result types. type TupleListU8BoolShape struct { + _ cm.HostLayout shape [unsafe.Sizeof(cm.Tuple[cm.List[uint8], bool]{})]byte } @@ -42,10 +39,12 @@ func lower_NewTimestamp(v NewTimestamp) (f0 uint32, f1 uint64, f2 uint32) { // DescriptorStatShape is used for storage in variant or result types. type DescriptorStatShape struct { + _ cm.HostLayout shape [unsafe.Sizeof(DescriptorStat{})]byte } // OptionDirectoryEntryShape is used for storage in variant or result types. type OptionDirectoryEntryShape struct { + _ cm.HostLayout shape [unsafe.Sizeof(cm.Option[DirectoryEntry]{})]byte } diff --git a/src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go b/src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go new file mode 100755 index 0000000000..d1a37fb2f1 --- /dev/null +++ b/src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go @@ -0,0 +1,133 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package types + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:filesystem@0.2.0". + +//go:wasmimport wasi:filesystem/types@0.2.0 [resource-drop]descriptor +//go:noescape +func wasmimport_DescriptorResourceDrop(self0 uint32) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.advise +//go:noescape +func wasmimport_DescriptorAdvise(self0 uint32, offset0 uint64, length0 uint64, advice0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.append-via-stream +//go:noescape +func wasmimport_DescriptorAppendViaStream(self0 uint32, result *cm.Result[OutputStream, OutputStream, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.create-directory-at +//go:noescape +func wasmimport_DescriptorCreateDirectoryAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.get-flags +//go:noescape +func wasmimport_DescriptorGetFlags(self0 uint32, result *cm.Result[DescriptorFlags, DescriptorFlags, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.get-type +//go:noescape +func wasmimport_DescriptorGetType(self0 uint32, result *cm.Result[DescriptorType, DescriptorType, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.is-same-object +//go:noescape +func wasmimport_DescriptorIsSameObject(self0 uint32, other0 uint32) (result0 uint32) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.link-at +//go:noescape +func wasmimport_DescriptorLinkAt(self0 uint32, oldPathFlags0 uint32, oldPath0 *uint8, oldPath1 uint32, newDescriptor0 uint32, newPath0 *uint8, newPath1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.metadata-hash +//go:noescape +func wasmimport_DescriptorMetadataHash(self0 uint32, result *cm.Result[MetadataHashValueShape, MetadataHashValue, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.metadata-hash-at +//go:noescape +func wasmimport_DescriptorMetadataHashAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, result *cm.Result[MetadataHashValueShape, MetadataHashValue, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.open-at +//go:noescape +func wasmimport_DescriptorOpenAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, openFlags0 uint32, flags0 uint32, result *cm.Result[Descriptor, Descriptor, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.read +//go:noescape +func wasmimport_DescriptorRead(self0 uint32, length0 uint64, offset0 uint64, result *cm.Result[TupleListU8BoolShape, cm.Tuple[cm.List[uint8], bool], ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.read-directory +//go:noescape +func wasmimport_DescriptorReadDirectory(self0 uint32, result *cm.Result[DirectoryEntryStream, DirectoryEntryStream, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.read-via-stream +//go:noescape +func wasmimport_DescriptorReadViaStream(self0 uint32, offset0 uint64, result *cm.Result[InputStream, InputStream, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.readlink-at +//go:noescape +func wasmimport_DescriptorReadLinkAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.Result[string, string, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.remove-directory-at +//go:noescape +func wasmimport_DescriptorRemoveDirectoryAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.rename-at +//go:noescape +func wasmimport_DescriptorRenameAt(self0 uint32, oldPath0 *uint8, oldPath1 uint32, newDescriptor0 uint32, newPath0 *uint8, newPath1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.set-size +//go:noescape +func wasmimport_DescriptorSetSize(self0 uint32, size0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.set-times +//go:noescape +func wasmimport_DescriptorSetTimes(self0 uint32, dataAccessTimestamp0 uint32, dataAccessTimestamp1 uint64, dataAccessTimestamp2 uint32, dataModificationTimestamp0 uint32, dataModificationTimestamp1 uint64, dataModificationTimestamp2 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.set-times-at +//go:noescape +func wasmimport_DescriptorSetTimesAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, dataAccessTimestamp0 uint32, dataAccessTimestamp1 uint64, dataAccessTimestamp2 uint32, dataModificationTimestamp0 uint32, dataModificationTimestamp1 uint64, dataModificationTimestamp2 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.stat +//go:noescape +func wasmimport_DescriptorStat(self0 uint32, result *cm.Result[DescriptorStatShape, DescriptorStat, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.stat-at +//go:noescape +func wasmimport_DescriptorStatAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, result *cm.Result[DescriptorStatShape, DescriptorStat, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.symlink-at +//go:noescape +func wasmimport_DescriptorSymlinkAt(self0 uint32, oldPath0 *uint8, oldPath1 uint32, newPath0 *uint8, newPath1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.sync +//go:noescape +func wasmimport_DescriptorSync(self0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.sync-data +//go:noescape +func wasmimport_DescriptorSyncData(self0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.unlink-file-at +//go:noescape +func wasmimport_DescriptorUnlinkFileAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.write +//go:noescape +func wasmimport_DescriptorWrite(self0 uint32, buffer0 *uint8, buffer1 uint32, offset0 uint64, result *cm.Result[uint64, FileSize, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.write-via-stream +//go:noescape +func wasmimport_DescriptorWriteViaStream(self0 uint32, offset0 uint64, result *cm.Result[OutputStream, OutputStream, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [resource-drop]directory-entry-stream +//go:noescape +func wasmimport_DirectoryEntryStreamResourceDrop(self0 uint32) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]directory-entry-stream.read-directory-entry +//go:noescape +func wasmimport_DirectoryEntryStreamReadDirectoryEntry(self0 uint32, result *cm.Result[OptionDirectoryEntryShape, cm.Option[DirectoryEntry], ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 filesystem-error-code +//go:noescape +func wasmimport_FilesystemErrorCode(err0 uint32, result *cm.Option[ErrorCode]) diff --git a/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go b/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go index ecceefe924..cac7de9580 100644 --- a/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go +++ b/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go @@ -31,10 +31,29 @@ package types import ( "internal/cm" wallclock "internal/wasi/clocks/v0.2.0/wall-clock" - ioerror "internal/wasi/io/v0.2.0/error" "internal/wasi/io/v0.2.0/streams" ) +// InputStream represents the imported type alias "wasi:filesystem/types@0.2.0#input-stream". +// +// See [streams.InputStream] for more information. +type InputStream = streams.InputStream + +// OutputStream represents the imported type alias "wasi:filesystem/types@0.2.0#output-stream". +// +// See [streams.OutputStream] for more information. +type OutputStream = streams.OutputStream + +// Error represents the imported type alias "wasi:filesystem/types@0.2.0#error". +// +// See [streams.Error] for more information. +type Error = streams.Error + +// DateTime represents the type alias "wasi:filesystem/types@0.2.0#datetime". +// +// See [wallclock.DateTime] for more information. +type DateTime = wallclock.DateTime + // FileSize represents the u64 "wasi:filesystem/types@0.2.0#filesize". // // File size or length of a region within a file. @@ -227,6 +246,7 @@ type LinkCount uint64 // status-change-timestamp: option, // } type DescriptorStat struct { + _ cm.HostLayout // File type. Type DescriptorType @@ -241,19 +261,19 @@ type DescriptorStat struct { // // If the `option` is none, the platform doesn't maintain an access // timestamp for this file. - DataAccessTimestamp cm.Option[wallclock.DateTime] + DataAccessTimestamp cm.Option[DateTime] // Last data modification timestamp. // // If the `option` is none, the platform doesn't maintain a // modification timestamp for this file. - DataModificationTimestamp cm.Option[wallclock.DateTime] + DataModificationTimestamp cm.Option[DateTime] // Last file status-change timestamp. // // If the `option` is none, the platform doesn't maintain a // status-change timestamp for this file. - StatusChangeTimestamp cm.Option[wallclock.DateTime] + StatusChangeTimestamp cm.Option[DateTime] } // NewTimestamp represents the variant "wasi:filesystem/types@0.2.0#new-timestamp". @@ -265,7 +285,7 @@ type DescriptorStat struct { // now, // timestamp(datetime), // } -type NewTimestamp cm.Variant[uint8, wallclock.DateTime, wallclock.DateTime] +type NewTimestamp cm.Variant[uint8, DateTime, DateTime] // NewTimestampNoChange returns a [NewTimestamp] of case "no-change". // @@ -297,13 +317,24 @@ func (self *NewTimestamp) Now() bool { // NewTimestampTimestamp returns a [NewTimestamp] of case "timestamp". // // Set the timestamp to the given value. -func NewTimestampTimestamp(data wallclock.DateTime) NewTimestamp { +func NewTimestampTimestamp(data DateTime) NewTimestamp { return cm.New[NewTimestamp](2, data) } -// Timestamp returns a non-nil *[wallclock.DateTime] if [NewTimestamp] represents the variant case "timestamp". -func (self *NewTimestamp) Timestamp() *wallclock.DateTime { - return cm.Case[wallclock.DateTime](self, 2) +// Timestamp returns a non-nil *[DateTime] if [NewTimestamp] represents the variant case "timestamp". +func (self *NewTimestamp) Timestamp() *DateTime { + return cm.Case[DateTime](self, 2) +} + +var stringsNewTimestamp = [3]string{ + "no-change", + "now", + "timestamp", +} + +// String implements [fmt.Stringer], returning the variant case name of v. +func (v NewTimestamp) String() string { + return stringsNewTimestamp[v.Tag()] } // DirectoryEntry represents the record "wasi:filesystem/types@0.2.0#directory-entry". @@ -315,6 +346,7 @@ func (self *NewTimestamp) Timestamp() *wallclock.DateTime { // name: string, // } type DirectoryEntry struct { + _ cm.HostLayout // The type of the file referred to by this directory entry. Type DescriptorType @@ -593,6 +625,7 @@ func (e Advice) String() string { // upper: u64, // } type MetadataHashValue struct { + _ cm.HostLayout // 64 bits of a 128-bit hash value. Lower uint64 @@ -620,10 +653,6 @@ func (self Descriptor) ResourceDrop() { return } -//go:wasmimport wasi:filesystem/types@0.2.0 [resource-drop]descriptor -//go:noescape -func wasmimport_DescriptorResourceDrop(self0 uint32) - // Advise represents the imported method "advise". // // Provide file advisory information on a descriptor. @@ -642,10 +671,6 @@ func (self Descriptor) Advise(offset FileSize, length FileSize, advice Advice) ( return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.advise -//go:noescape -func wasmimport_DescriptorAdvise(self0 uint32, offset0 uint64, length0 uint64, advice0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) - // AppendViaStream represents the imported method "append-via-stream". // // Return a stream for appending to a file, if available. @@ -658,16 +683,12 @@ func wasmimport_DescriptorAdvise(self0 uint32, offset0 uint64, length0 uint64, a // append-via-stream: func() -> result // //go:nosplit -func (self Descriptor) AppendViaStream() (result cm.Result[streams.OutputStream, streams.OutputStream, ErrorCode]) { +func (self Descriptor) AppendViaStream() (result cm.Result[OutputStream, OutputStream, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_DescriptorAppendViaStream((uint32)(self0), &result) return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.append-via-stream -//go:noescape -func wasmimport_DescriptorAppendViaStream(self0 uint32, result *cm.Result[streams.OutputStream, streams.OutputStream, ErrorCode]) - // CreateDirectoryAt represents the imported method "create-directory-at". // // Create a directory. @@ -684,10 +705,6 @@ func (self Descriptor) CreateDirectoryAt(path string) (result cm.Result[ErrorCod return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.create-directory-at -//go:noescape -func wasmimport_DescriptorCreateDirectoryAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) - // GetFlags represents the imported method "get-flags". // // Get flags associated with a descriptor. @@ -706,10 +723,6 @@ func (self Descriptor) GetFlags() (result cm.Result[DescriptorFlags, DescriptorF return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.get-flags -//go:noescape -func wasmimport_DescriptorGetFlags(self0 uint32, result *cm.Result[DescriptorFlags, DescriptorFlags, ErrorCode]) - // GetType represents the imported method "get-type". // // Get the dynamic type of a descriptor. @@ -732,10 +745,6 @@ func (self Descriptor) GetType() (result cm.Result[DescriptorType, DescriptorTyp return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.get-type -//go:noescape -func wasmimport_DescriptorGetType(self0 uint32, result *cm.Result[DescriptorType, DescriptorType, ErrorCode]) - // IsSameObject represents the imported method "is-same-object". // // Test whether two descriptors refer to the same filesystem object. @@ -756,10 +765,6 @@ func (self Descriptor) IsSameObject(other Descriptor) (result bool) { return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.is-same-object -//go:noescape -func wasmimport_DescriptorIsSameObject(self0 uint32, other0 uint32) (result0 uint32) - // LinkAt represents the imported method "link-at". // // Create a hard link. @@ -780,10 +785,6 @@ func (self Descriptor) LinkAt(oldPathFlags PathFlags, oldPath string, newDescrip return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.link-at -//go:noescape -func wasmimport_DescriptorLinkAt(self0 uint32, oldPathFlags0 uint32, oldPath0 *uint8, oldPath1 uint32, newDescriptor0 uint32, newPath0 *uint8, newPath1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) - // MetadataHash represents the imported method "metadata-hash". // // Return a hash of the metadata associated with a filesystem object referred @@ -815,10 +816,6 @@ func (self Descriptor) MetadataHash() (result cm.Result[MetadataHashValueShape, return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.metadata-hash -//go:noescape -func wasmimport_DescriptorMetadataHash(self0 uint32, result *cm.Result[MetadataHashValueShape, MetadataHashValue, ErrorCode]) - // MetadataHashAt represents the imported method "metadata-hash-at". // // Return a hash of the metadata associated with a filesystem object referred @@ -838,10 +835,6 @@ func (self Descriptor) MetadataHashAt(pathFlags PathFlags, path string) (result return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.metadata-hash-at -//go:noescape -func wasmimport_DescriptorMetadataHashAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, result *cm.Result[MetadataHashValueShape, MetadataHashValue, ErrorCode]) - // OpenAt represents the imported method "open-at". // // Open a file or directory. @@ -877,10 +870,6 @@ func (self Descriptor) OpenAt(pathFlags PathFlags, path string, openFlags OpenFl return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.open-at -//go:noescape -func wasmimport_DescriptorOpenAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, openFlags0 uint32, flags0 uint32, result *cm.Result[Descriptor, Descriptor, ErrorCode]) - // Read represents the imported method "read". // // Read from a descriptor, without using and updating the descriptor's offset. @@ -907,10 +896,6 @@ func (self Descriptor) Read(length FileSize, offset FileSize) (result cm.Result[ return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.read -//go:noescape -func wasmimport_DescriptorRead(self0 uint32, length0 uint64, offset0 uint64, result *cm.Result[TupleListU8BoolShape, cm.Tuple[cm.List[uint8], bool], ErrorCode]) - // ReadDirectory represents the imported method "read-directory". // // Read directory entries from a directory. @@ -932,10 +917,6 @@ func (self Descriptor) ReadDirectory() (result cm.Result[DirectoryEntryStream, D return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.read-directory -//go:noescape -func wasmimport_DescriptorReadDirectory(self0 uint32, result *cm.Result[DirectoryEntryStream, DirectoryEntryStream, ErrorCode]) - // ReadViaStream represents the imported method "read-via-stream". // // Return a stream for reading from a file, if available. @@ -950,17 +931,13 @@ func wasmimport_DescriptorReadDirectory(self0 uint32, result *cm.Result[Director // read-via-stream: func(offset: filesize) -> result // //go:nosplit -func (self Descriptor) ReadViaStream(offset FileSize) (result cm.Result[streams.InputStream, streams.InputStream, ErrorCode]) { +func (self Descriptor) ReadViaStream(offset FileSize) (result cm.Result[InputStream, InputStream, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) offset0 := (uint64)(offset) wasmimport_DescriptorReadViaStream((uint32)(self0), (uint64)(offset0), &result) return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.read-via-stream -//go:noescape -func wasmimport_DescriptorReadViaStream(self0 uint32, offset0 uint64, result *cm.Result[streams.InputStream, streams.InputStream, ErrorCode]) - // ReadLinkAt represents the imported method "readlink-at". // // Read the contents of a symbolic link. @@ -980,10 +957,6 @@ func (self Descriptor) ReadLinkAt(path string) (result cm.Result[string, string, return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.readlink-at -//go:noescape -func wasmimport_DescriptorReadLinkAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.Result[string, string, ErrorCode]) - // RemoveDirectoryAt represents the imported method "remove-directory-at". // // Remove a directory. @@ -1002,10 +975,6 @@ func (self Descriptor) RemoveDirectoryAt(path string) (result cm.Result[ErrorCod return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.remove-directory-at -//go:noescape -func wasmimport_DescriptorRemoveDirectoryAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) - // RenameAt represents the imported method "rename-at". // // Rename a filesystem object. @@ -1025,10 +994,6 @@ func (self Descriptor) RenameAt(oldPath string, newDescriptor Descriptor, newPat return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.rename-at -//go:noescape -func wasmimport_DescriptorRenameAt(self0 uint32, oldPath0 *uint8, oldPath1 uint32, newDescriptor0 uint32, newPath0 *uint8, newPath1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) - // SetSize represents the imported method "set-size". // // Adjust the size of an open file. If this increases the file's size, the @@ -1046,10 +1011,6 @@ func (self Descriptor) SetSize(size FileSize) (result cm.Result[ErrorCode, struc return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.set-size -//go:noescape -func wasmimport_DescriptorSetSize(self0 uint32, size0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) - // SetTimes represents the imported method "set-times". // // Adjust the timestamps of an open file or directory. @@ -1070,10 +1031,6 @@ func (self Descriptor) SetTimes(dataAccessTimestamp NewTimestamp, dataModificati return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.set-times -//go:noescape -func wasmimport_DescriptorSetTimes(self0 uint32, dataAccessTimestamp0 uint32, dataAccessTimestamp1 uint64, dataAccessTimestamp2 uint32, dataModificationTimestamp0 uint32, dataModificationTimestamp1 uint64, dataModificationTimestamp2 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) - // SetTimesAt represents the imported method "set-times-at". // // Adjust the timestamps of a file or directory. @@ -1097,10 +1054,6 @@ func (self Descriptor) SetTimesAt(pathFlags PathFlags, path string, dataAccessTi return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.set-times-at -//go:noescape -func wasmimport_DescriptorSetTimesAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, dataAccessTimestamp0 uint32, dataAccessTimestamp1 uint64, dataAccessTimestamp2 uint32, dataModificationTimestamp0 uint32, dataModificationTimestamp1 uint64, dataModificationTimestamp2 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) - // Stat represents the imported method "stat". // // Return the attributes of an open file or directory. @@ -1122,10 +1075,6 @@ func (self Descriptor) Stat() (result cm.Result[DescriptorStatShape, DescriptorS return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.stat -//go:noescape -func wasmimport_DescriptorStat(self0 uint32, result *cm.Result[DescriptorStatShape, DescriptorStat, ErrorCode]) - // StatAt represents the imported method "stat-at". // // Return the attributes of a file or directory. @@ -1148,10 +1097,6 @@ func (self Descriptor) StatAt(pathFlags PathFlags, path string) (result cm.Resul return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.stat-at -//go:noescape -func wasmimport_DescriptorStatAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, result *cm.Result[DescriptorStatShape, DescriptorStat, ErrorCode]) - // SymlinkAt represents the imported method "symlink-at". // // Create a symbolic link (also known as a "symlink"). @@ -1172,10 +1117,6 @@ func (self Descriptor) SymlinkAt(oldPath string, newPath string) (result cm.Resu return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.symlink-at -//go:noescape -func wasmimport_DescriptorSymlinkAt(self0 uint32, oldPath0 *uint8, oldPath1 uint32, newPath0 *uint8, newPath1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) - // Sync represents the imported method "sync". // // Synchronize the data and metadata of a file to disk. @@ -1194,10 +1135,6 @@ func (self Descriptor) Sync() (result cm.Result[ErrorCode, struct{}, ErrorCode]) return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.sync -//go:noescape -func wasmimport_DescriptorSync(self0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) - // SyncData represents the imported method "sync-data". // // Synchronize the data of a file to disk. @@ -1216,10 +1153,6 @@ func (self Descriptor) SyncData() (result cm.Result[ErrorCode, struct{}, ErrorCo return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.sync-data -//go:noescape -func wasmimport_DescriptorSyncData(self0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) - // UnlinkFileAt represents the imported method "unlink-file-at". // // Unlink a filesystem object that is not a directory. @@ -1237,10 +1170,6 @@ func (self Descriptor) UnlinkFileAt(path string) (result cm.Result[ErrorCode, st return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.unlink-file-at -//go:noescape -func wasmimport_DescriptorUnlinkFileAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) - // Write represents the imported method "write". // // Write to a descriptor, without using and updating the descriptor's offset. @@ -1264,10 +1193,6 @@ func (self Descriptor) Write(buffer cm.List[uint8], offset FileSize) (result cm. return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.write -//go:noescape -func wasmimport_DescriptorWrite(self0 uint32, buffer0 *uint8, buffer1 uint32, offset0 uint64, result *cm.Result[uint64, FileSize, ErrorCode]) - // WriteViaStream represents the imported method "write-via-stream". // // Return a stream for writing to a file, if available. @@ -1280,17 +1205,13 @@ func wasmimport_DescriptorWrite(self0 uint32, buffer0 *uint8, buffer1 uint32, of // write-via-stream: func(offset: filesize) -> result // //go:nosplit -func (self Descriptor) WriteViaStream(offset FileSize) (result cm.Result[streams.OutputStream, streams.OutputStream, ErrorCode]) { +func (self Descriptor) WriteViaStream(offset FileSize) (result cm.Result[OutputStream, OutputStream, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) offset0 := (uint64)(offset) wasmimport_DescriptorWriteViaStream((uint32)(self0), (uint64)(offset0), &result) return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.write-via-stream -//go:noescape -func wasmimport_DescriptorWriteViaStream(self0 uint32, offset0 uint64, result *cm.Result[streams.OutputStream, streams.OutputStream, ErrorCode]) - // DirectoryEntryStream represents the imported resource "wasi:filesystem/types@0.2.0#directory-entry-stream". // // A stream of directory entries. @@ -1309,10 +1230,6 @@ func (self DirectoryEntryStream) ResourceDrop() { return } -//go:wasmimport wasi:filesystem/types@0.2.0 [resource-drop]directory-entry-stream -//go:noescape -func wasmimport_DirectoryEntryStreamResourceDrop(self0 uint32) - // ReadDirectoryEntry represents the imported method "read-directory-entry". // // Read a single directory entry from a `directory-entry-stream`. @@ -1326,10 +1243,6 @@ func (self DirectoryEntryStream) ReadDirectoryEntry() (result cm.Result[OptionDi return } -//go:wasmimport wasi:filesystem/types@0.2.0 [method]directory-entry-stream.read-directory-entry -//go:noescape -func wasmimport_DirectoryEntryStreamReadDirectoryEntry(self0 uint32, result *cm.Result[OptionDirectoryEntryShape, cm.Option[DirectoryEntry], ErrorCode]) - // FilesystemErrorCode represents the imported function "filesystem-error-code". // // Attempts to extract a filesystem-related `error-code` from the stream @@ -1346,12 +1259,8 @@ func wasmimport_DirectoryEntryStreamReadDirectoryEntry(self0 uint32, result *cm. // filesystem-error-code: func(err: borrow) -> option // //go:nosplit -func FilesystemErrorCode(err ioerror.Error) (result cm.Option[ErrorCode]) { +func FilesystemErrorCode(err Error) (result cm.Option[ErrorCode]) { err0 := cm.Reinterpret[uint32](err) wasmimport_FilesystemErrorCode((uint32)(err0), &result) return } - -//go:wasmimport wasi:filesystem/types@0.2.0 filesystem-error-code -//go:noescape -func wasmimport_FilesystemErrorCode(err0 uint32, result *cm.Option[ErrorCode]) diff --git a/src/internal/wasi/io/v0.2.0/error/error.wit.go b/src/internal/wasi/io/v0.2.0/error/error.wit.go index e7f92a8d3f..20827b053a 100644 --- a/src/internal/wasi/io/v0.2.0/error/error.wit.go +++ b/src/internal/wasi/io/v0.2.0/error/error.wit.go @@ -43,10 +43,6 @@ func (self Error) ResourceDrop() { return } -//go:wasmimport wasi:io/error@0.2.0 [resource-drop]error -//go:noescape -func wasmimport_ErrorResourceDrop(self0 uint32) - // ToDebugString represents the imported method "to-debug-string". // // Returns a string that is suitable to assist humans in debugging @@ -65,7 +61,3 @@ func (self Error) ToDebugString() (result string) { wasmimport_ErrorToDebugString((uint32)(self0), &result) return } - -//go:wasmimport wasi:io/error@0.2.0 [method]error.to-debug-string -//go:noescape -func wasmimport_ErrorToDebugString(self0 uint32, result *string) diff --git a/src/internal/wasi/io/v0.2.0/error/ioerror.wasm.go b/src/internal/wasi/io/v0.2.0/error/ioerror.wasm.go new file mode 100755 index 0000000000..e254b5d86f --- /dev/null +++ b/src/internal/wasi/io/v0.2.0/error/ioerror.wasm.go @@ -0,0 +1,13 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package ioerror + +// This file contains wasmimport and wasmexport declarations for "wasi:io@0.2.0". + +//go:wasmimport wasi:io/error@0.2.0 [resource-drop]error +//go:noescape +func wasmimport_ErrorResourceDrop(self0 uint32) + +//go:wasmimport wasi:io/error@0.2.0 [method]error.to-debug-string +//go:noescape +func wasmimport_ErrorToDebugString(self0 uint32, result *string) diff --git a/src/internal/wasi/io/v0.2.0/poll/poll.wasm.go b/src/internal/wasi/io/v0.2.0/poll/poll.wasm.go new file mode 100755 index 0000000000..d807d77280 --- /dev/null +++ b/src/internal/wasi/io/v0.2.0/poll/poll.wasm.go @@ -0,0 +1,25 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package poll + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:io@0.2.0". + +//go:wasmimport wasi:io/poll@0.2.0 [resource-drop]pollable +//go:noescape +func wasmimport_PollableResourceDrop(self0 uint32) + +//go:wasmimport wasi:io/poll@0.2.0 [method]pollable.block +//go:noescape +func wasmimport_PollableBlock(self0 uint32) + +//go:wasmimport wasi:io/poll@0.2.0 [method]pollable.ready +//go:noescape +func wasmimport_PollableReady(self0 uint32) (result0 uint32) + +//go:wasmimport wasi:io/poll@0.2.0 poll +//go:noescape +func wasmimport_Poll(in0 *Pollable, in1 uint32, result *cm.List[uint32]) diff --git a/src/internal/wasi/io/v0.2.0/poll/poll.wit.go b/src/internal/wasi/io/v0.2.0/poll/poll.wit.go index 626ac50e1e..a3d5163944 100644 --- a/src/internal/wasi/io/v0.2.0/poll/poll.wit.go +++ b/src/internal/wasi/io/v0.2.0/poll/poll.wit.go @@ -28,10 +28,6 @@ func (self Pollable) ResourceDrop() { return } -//go:wasmimport wasi:io/poll@0.2.0 [resource-drop]pollable -//go:noescape -func wasmimport_PollableResourceDrop(self0 uint32) - // Block represents the imported method "block". // // `block` returns immediately if the pollable is ready, and otherwise @@ -49,10 +45,6 @@ func (self Pollable) Block() { return } -//go:wasmimport wasi:io/poll@0.2.0 [method]pollable.block -//go:noescape -func wasmimport_PollableBlock(self0 uint32) - // Ready represents the imported method "ready". // // Return the readiness of a pollable. This function never blocks. @@ -69,10 +61,6 @@ func (self Pollable) Ready() (result bool) { return } -//go:wasmimport wasi:io/poll@0.2.0 [method]pollable.ready -//go:noescape -func wasmimport_PollableReady(self0 uint32) (result0 uint32) - // Poll represents the imported function "poll". // // Poll for completion on a set of pollables. @@ -102,7 +90,3 @@ func Poll(in cm.List[Pollable]) (result cm.List[uint32]) { wasmimport_Poll((*Pollable)(in0), (uint32)(in1), &result) return } - -//go:wasmimport wasi:io/poll@0.2.0 poll -//go:noescape -func wasmimport_Poll(in0 *Pollable, in1 uint32, result *cm.List[uint32]) diff --git a/src/internal/wasi/io/v0.2.0/streams/streams.wasm.go b/src/internal/wasi/io/v0.2.0/streams/streams.wasm.go new file mode 100755 index 0000000000..c317ea5c15 --- /dev/null +++ b/src/internal/wasi/io/v0.2.0/streams/streams.wasm.go @@ -0,0 +1,77 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package streams + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:io@0.2.0". + +//go:wasmimport wasi:io/streams@0.2.0 [resource-drop]input-stream +//go:noescape +func wasmimport_InputStreamResourceDrop(self0 uint32) + +//go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.blocking-read +//go:noescape +func wasmimport_InputStreamBlockingRead(self0 uint32, len0 uint64, result *cm.Result[cm.List[uint8], cm.List[uint8], StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.blocking-skip +//go:noescape +func wasmimport_InputStreamBlockingSkip(self0 uint32, len0 uint64, result *cm.Result[uint64, uint64, StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.read +//go:noescape +func wasmimport_InputStreamRead(self0 uint32, len0 uint64, result *cm.Result[cm.List[uint8], cm.List[uint8], StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.skip +//go:noescape +func wasmimport_InputStreamSkip(self0 uint32, len0 uint64, result *cm.Result[uint64, uint64, StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.subscribe +//go:noescape +func wasmimport_InputStreamSubscribe(self0 uint32) (result0 uint32) + +//go:wasmimport wasi:io/streams@0.2.0 [resource-drop]output-stream +//go:noescape +func wasmimport_OutputStreamResourceDrop(self0 uint32) + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-flush +//go:noescape +func wasmimport_OutputStreamBlockingFlush(self0 uint32, result *cm.Result[StreamError, struct{}, StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-splice +//go:noescape +func wasmimport_OutputStreamBlockingSplice(self0 uint32, src0 uint32, len0 uint64, result *cm.Result[uint64, uint64, StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-write-and-flush +//go:noescape +func wasmimport_OutputStreamBlockingWriteAndFlush(self0 uint32, contents0 *uint8, contents1 uint32, result *cm.Result[StreamError, struct{}, StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-write-zeroes-and-flush +//go:noescape +func wasmimport_OutputStreamBlockingWriteZeroesAndFlush(self0 uint32, len0 uint64, result *cm.Result[StreamError, struct{}, StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.check-write +//go:noescape +func wasmimport_OutputStreamCheckWrite(self0 uint32, result *cm.Result[uint64, uint64, StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.flush +//go:noescape +func wasmimport_OutputStreamFlush(self0 uint32, result *cm.Result[StreamError, struct{}, StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.splice +//go:noescape +func wasmimport_OutputStreamSplice(self0 uint32, src0 uint32, len0 uint64, result *cm.Result[uint64, uint64, StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.subscribe +//go:noescape +func wasmimport_OutputStreamSubscribe(self0 uint32) (result0 uint32) + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.write +//go:noescape +func wasmimport_OutputStreamWrite(self0 uint32, contents0 *uint8, contents1 uint32, result *cm.Result[StreamError, struct{}, StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.write-zeroes +//go:noescape +func wasmimport_OutputStreamWriteZeroes(self0 uint32, len0 uint64, result *cm.Result[StreamError, struct{}, StreamError]) diff --git a/src/internal/wasi/io/v0.2.0/streams/streams.wit.go b/src/internal/wasi/io/v0.2.0/streams/streams.wit.go index 01fc0288f0..a4dcc970e2 100644 --- a/src/internal/wasi/io/v0.2.0/streams/streams.wit.go +++ b/src/internal/wasi/io/v0.2.0/streams/streams.wit.go @@ -15,6 +15,16 @@ import ( "internal/wasi/io/v0.2.0/poll" ) +// Error represents the imported type alias "wasi:io/streams@0.2.0#error". +// +// See [ioerror.Error] for more information. +type Error = ioerror.Error + +// Pollable represents the imported type alias "wasi:io/streams@0.2.0#pollable". +// +// See [poll.Pollable] for more information. +type Pollable = poll.Pollable + // StreamError represents the imported variant "wasi:io/streams@0.2.0#stream-error". // // An error for input-stream and output-stream operations. @@ -23,20 +33,20 @@ import ( // last-operation-failed(error), // closed, // } -type StreamError cm.Variant[uint8, ioerror.Error, ioerror.Error] +type StreamError cm.Variant[uint8, Error, Error] // StreamErrorLastOperationFailed returns a [StreamError] of case "last-operation-failed". // // The last operation (a write or flush) failed before completion. // // More information is available in the `error` payload. -func StreamErrorLastOperationFailed(data ioerror.Error) StreamError { +func StreamErrorLastOperationFailed(data Error) StreamError { return cm.New[StreamError](0, data) } -// LastOperationFailed returns a non-nil *[ioerror.Error] if [StreamError] represents the variant case "last-operation-failed". -func (self *StreamError) LastOperationFailed() *ioerror.Error { - return cm.Case[ioerror.Error](self, 0) +// LastOperationFailed returns a non-nil *[Error] if [StreamError] represents the variant case "last-operation-failed". +func (self *StreamError) LastOperationFailed() *Error { + return cm.Case[Error](self, 0) } // StreamErrorClosed returns a [StreamError] of case "closed". @@ -54,6 +64,16 @@ func (self *StreamError) Closed() bool { return self.Tag() == 1 } +var stringsStreamError = [2]string{ + "last-operation-failed", + "closed", +} + +// String implements [fmt.Stringer], returning the variant case name of v. +func (v StreamError) String() string { + return stringsStreamError[v.Tag()] +} + // InputStream represents the imported resource "wasi:io/streams@0.2.0#input-stream". // // An input bytestream. @@ -79,10 +99,6 @@ func (self InputStream) ResourceDrop() { return } -//go:wasmimport wasi:io/streams@0.2.0 [resource-drop]input-stream -//go:noescape -func wasmimport_InputStreamResourceDrop(self0 uint32) - // BlockingRead represents the imported method "blocking-read". // // Read bytes from a stream, after blocking until at least one byte can @@ -98,10 +114,6 @@ func (self InputStream) BlockingRead(len_ uint64) (result cm.Result[cm.List[uint return } -//go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.blocking-read -//go:noescape -func wasmimport_InputStreamBlockingRead(self0 uint32, len0 uint64, result *cm.Result[cm.List[uint8], cm.List[uint8], StreamError]) - // BlockingSkip represents the imported method "blocking-skip". // // Skip bytes from a stream, after blocking until at least one byte @@ -117,10 +129,6 @@ func (self InputStream) BlockingSkip(len_ uint64) (result cm.Result[uint64, uint return } -//go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.blocking-skip -//go:noescape -func wasmimport_InputStreamBlockingSkip(self0 uint32, len0 uint64, result *cm.Result[uint64, uint64, StreamError]) - // Read represents the imported method "read". // // Perform a non-blocking read from the stream. @@ -160,10 +168,6 @@ func (self InputStream) Read(len_ uint64) (result cm.Result[cm.List[uint8], cm.L return } -//go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.read -//go:noescape -func wasmimport_InputStreamRead(self0 uint32, len0 uint64, result *cm.Result[cm.List[uint8], cm.List[uint8], StreamError]) - // Skip represents the imported method "skip". // // Skip bytes from a stream. Returns number of bytes skipped. @@ -181,10 +185,6 @@ func (self InputStream) Skip(len_ uint64) (result cm.Result[uint64, uint64, Stre return } -//go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.skip -//go:noescape -func wasmimport_InputStreamSkip(self0 uint32, len0 uint64, result *cm.Result[uint64, uint64, StreamError]) - // Subscribe represents the imported method "subscribe". // // Create a `pollable` which will resolve once either the specified stream @@ -197,17 +197,13 @@ func wasmimport_InputStreamSkip(self0 uint32, len0 uint64, result *cm.Result[uin // subscribe: func() -> pollable // //go:nosplit -func (self InputStream) Subscribe() (result poll.Pollable) { +func (self InputStream) Subscribe() (result Pollable) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_InputStreamSubscribe((uint32)(self0)) - result = cm.Reinterpret[poll.Pollable]((uint32)(result0)) + result = cm.Reinterpret[Pollable]((uint32)(result0)) return } -//go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.subscribe -//go:noescape -func wasmimport_InputStreamSubscribe(self0 uint32) (result0 uint32) - // OutputStream represents the imported resource "wasi:io/streams@0.2.0#output-stream". // // An output bytestream. @@ -233,10 +229,6 @@ func (self OutputStream) ResourceDrop() { return } -//go:wasmimport wasi:io/streams@0.2.0 [resource-drop]output-stream -//go:noescape -func wasmimport_OutputStreamResourceDrop(self0 uint32) - // BlockingFlush represents the imported method "blocking-flush". // // Request to flush buffered output, and block until flush completes @@ -251,10 +243,6 @@ func (self OutputStream) BlockingFlush() (result cm.Result[StreamError, struct{} return } -//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-flush -//go:noescape -func wasmimport_OutputStreamBlockingFlush(self0 uint32, result *cm.Result[StreamError, struct{}, StreamError]) - // BlockingSplice represents the imported method "blocking-splice". // // Read from one stream and write to another, with blocking. @@ -274,10 +262,6 @@ func (self OutputStream) BlockingSplice(src InputStream, len_ uint64) (result cm return } -//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-splice -//go:noescape -func wasmimport_OutputStreamBlockingSplice(self0 uint32, src0 uint32, len0 uint64, result *cm.Result[uint64, uint64, StreamError]) - // BlockingWriteAndFlush represents the imported method "blocking-write-and-flush". // // Perform a write of up to 4096 bytes, and then flush the stream. Block @@ -313,10 +297,6 @@ func (self OutputStream) BlockingWriteAndFlush(contents cm.List[uint8]) (result return } -//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-write-and-flush -//go:noescape -func wasmimport_OutputStreamBlockingWriteAndFlush(self0 uint32, contents0 *uint8, contents1 uint32, result *cm.Result[StreamError, struct{}, StreamError]) - // BlockingWriteZeroesAndFlush represents the imported method "blocking-write-zeroes-and-flush". // // Perform a write of up to 4096 zeroes, and then flush the stream. @@ -352,10 +332,6 @@ func (self OutputStream) BlockingWriteZeroesAndFlush(len_ uint64) (result cm.Res return } -//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-write-zeroes-and-flush -//go:noescape -func wasmimport_OutputStreamBlockingWriteZeroesAndFlush(self0 uint32, len0 uint64, result *cm.Result[StreamError, struct{}, StreamError]) - // CheckWrite represents the imported method "check-write". // // Check readiness for writing. This function never blocks. @@ -377,10 +353,6 @@ func (self OutputStream) CheckWrite() (result cm.Result[uint64, uint64, StreamEr return } -//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.check-write -//go:noescape -func wasmimport_OutputStreamCheckWrite(self0 uint32, result *cm.Result[uint64, uint64, StreamError]) - // Flush represents the imported method "flush". // // Request to flush buffered output. This function never blocks. @@ -403,10 +375,6 @@ func (self OutputStream) Flush() (result cm.Result[StreamError, struct{}, Stream return } -//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.flush -//go:noescape -func wasmimport_OutputStreamFlush(self0 uint32, result *cm.Result[StreamError, struct{}, StreamError]) - // Splice represents the imported method "splice". // // Read from one stream and write to another. @@ -434,10 +402,6 @@ func (self OutputStream) Splice(src InputStream, len_ uint64) (result cm.Result[ return } -//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.splice -//go:noescape -func wasmimport_OutputStreamSplice(self0 uint32, src0 uint32, len0 uint64, result *cm.Result[uint64, uint64, StreamError]) - // Subscribe represents the imported method "subscribe". // // Create a `pollable` which will resolve once the output-stream @@ -454,17 +418,13 @@ func wasmimport_OutputStreamSplice(self0 uint32, src0 uint32, len0 uint64, resul // subscribe: func() -> pollable // //go:nosplit -func (self OutputStream) Subscribe() (result poll.Pollable) { +func (self OutputStream) Subscribe() (result Pollable) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_OutputStreamSubscribe((uint32)(self0)) - result = cm.Reinterpret[poll.Pollable]((uint32)(result0)) + result = cm.Reinterpret[Pollable]((uint32)(result0)) return } -//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.subscribe -//go:noescape -func wasmimport_OutputStreamSubscribe(self0 uint32) (result0 uint32) - // Write represents the imported method "write". // // Perform a write. This function never blocks. @@ -491,10 +451,6 @@ func (self OutputStream) Write(contents cm.List[uint8]) (result cm.Result[Stream return } -//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.write -//go:noescape -func wasmimport_OutputStreamWrite(self0 uint32, contents0 *uint8, contents1 uint32, result *cm.Result[StreamError, struct{}, StreamError]) - // WriteZeroes represents the imported method "write-zeroes". // // Write zeroes to a stream. @@ -513,7 +469,3 @@ func (self OutputStream) WriteZeroes(len_ uint64) (result cm.Result[StreamError, wasmimport_OutputStreamWriteZeroes((uint32)(self0), (uint64)(len0), &result) return } - -//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.write-zeroes -//go:noescape -func wasmimport_OutputStreamWriteZeroes(self0 uint32, len0 uint64, result *cm.Result[StreamError, struct{}, StreamError]) diff --git a/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go b/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go index 3b8f33c065..6f36367382 100644 --- a/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go +++ b/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go @@ -35,7 +35,3 @@ func InsecureSeed() (result [2]uint64) { wasmimport_InsecureSeed(&result) return } - -//go:wasmimport wasi:random/insecure-seed@0.2.0 insecure-seed -//go:noescape -func wasmimport_InsecureSeed(result *[2]uint64) diff --git a/src/internal/wasi/random/v0.2.0/insecure-seed/insecureseed.wasm.go b/src/internal/wasi/random/v0.2.0/insecure-seed/insecureseed.wasm.go new file mode 100755 index 0000000000..e94356df03 --- /dev/null +++ b/src/internal/wasi/random/v0.2.0/insecure-seed/insecureseed.wasm.go @@ -0,0 +1,9 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package insecureseed + +// This file contains wasmimport and wasmexport declarations for "wasi:random@0.2.0". + +//go:wasmimport wasi:random/insecure-seed@0.2.0 insecure-seed +//go:noescape +func wasmimport_InsecureSeed(result *[2]uint64) diff --git a/src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go b/src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go new file mode 100755 index 0000000000..ea62b81317 --- /dev/null +++ b/src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go @@ -0,0 +1,17 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package insecure + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:random@0.2.0". + +//go:wasmimport wasi:random/insecure@0.2.0 get-insecure-random-bytes +//go:noescape +func wasmimport_GetInsecureRandomBytes(len0 uint64, result *cm.List[uint8]) + +//go:wasmimport wasi:random/insecure@0.2.0 get-insecure-random-u64 +//go:noescape +func wasmimport_GetInsecureRandomU64() (result0 uint64) diff --git a/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go b/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go index 44cdbd7cfd..625ca5a905 100644 --- a/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go +++ b/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go @@ -32,10 +32,6 @@ func GetInsecureRandomBytes(len_ uint64) (result cm.List[uint8]) { return } -//go:wasmimport wasi:random/insecure@0.2.0 get-insecure-random-bytes -//go:noescape -func wasmimport_GetInsecureRandomBytes(len0 uint64, result *cm.List[uint8]) - // GetInsecureRandomU64 represents the imported function "get-insecure-random-u64". // // Return an insecure pseudo-random `u64` value. @@ -51,7 +47,3 @@ func GetInsecureRandomU64() (result uint64) { result = (uint64)((uint64)(result0)) return } - -//go:wasmimport wasi:random/insecure@0.2.0 get-insecure-random-u64 -//go:noescape -func wasmimport_GetInsecureRandomU64() (result0 uint64) diff --git a/src/internal/wasi/random/v0.2.0/random/random.wasm.go b/src/internal/wasi/random/v0.2.0/random/random.wasm.go new file mode 100755 index 0000000000..1738d49aee --- /dev/null +++ b/src/internal/wasi/random/v0.2.0/random/random.wasm.go @@ -0,0 +1,17 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package random + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:random@0.2.0". + +//go:wasmimport wasi:random/random@0.2.0 get-random-bytes +//go:noescape +func wasmimport_GetRandomBytes(len0 uint64, result *cm.List[uint8]) + +//go:wasmimport wasi:random/random@0.2.0 get-random-u64 +//go:noescape +func wasmimport_GetRandomU64() (result0 uint64) diff --git a/src/internal/wasi/random/v0.2.0/random/random.wit.go b/src/internal/wasi/random/v0.2.0/random/random.wit.go index cf0929bf5b..49b054bd24 100644 --- a/src/internal/wasi/random/v0.2.0/random/random.wit.go +++ b/src/internal/wasi/random/v0.2.0/random/random.wit.go @@ -36,10 +36,6 @@ func GetRandomBytes(len_ uint64) (result cm.List[uint8]) { return } -//go:wasmimport wasi:random/random@0.2.0 get-random-bytes -//go:noescape -func wasmimport_GetRandomBytes(len0 uint64, result *cm.List[uint8]) - // GetRandomU64 represents the imported function "get-random-u64". // // Return a cryptographically-secure random or pseudo-random `u64` value. @@ -55,7 +51,3 @@ func GetRandomU64() (result uint64) { result = (uint64)((uint64)(result0)) return } - -//go:wasmimport wasi:random/random@0.2.0 get-random-u64 -//go:noescape -func wasmimport_GetRandomU64() (result0 uint64) diff --git a/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go b/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go index 4740360171..378bba6896 100644 --- a/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go @@ -10,6 +10,11 @@ import ( "internal/wasi/sockets/v0.2.0/network" ) +// Network represents the imported type alias "wasi:sockets/instance-network@0.2.0#network". +// +// See [network.Network] for more information. +type Network = network.Network + // InstanceNetwork represents the imported function "instance-network". // // Get a handle to the default network. @@ -17,12 +22,8 @@ import ( // instance-network: func() -> network // //go:nosplit -func InstanceNetwork() (result network.Network) { +func InstanceNetwork() (result Network) { result0 := wasmimport_InstanceNetwork() - result = cm.Reinterpret[network.Network]((uint32)(result0)) + result = cm.Reinterpret[Network]((uint32)(result0)) return } - -//go:wasmimport wasi:sockets/instance-network@0.2.0 instance-network -//go:noescape -func wasmimport_InstanceNetwork() (result0 uint32) diff --git a/src/internal/wasi/sockets/v0.2.0/instance-network/instancenetwork.wasm.go b/src/internal/wasi/sockets/v0.2.0/instance-network/instancenetwork.wasm.go new file mode 100755 index 0000000000..eb113e217c --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/instance-network/instancenetwork.wasm.go @@ -0,0 +1,9 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package instancenetwork + +// This file contains wasmimport and wasmexport declarations for "wasi:sockets@0.2.0". + +//go:wasmimport wasi:sockets/instance-network@0.2.0 instance-network +//go:noescape +func wasmimport_InstanceNetwork() (result0 uint32) diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go index fbc790e008..3d73f356a0 100644 --- a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go +++ b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go @@ -4,11 +4,11 @@ package ipnamelookup import ( "internal/cm" - "internal/wasi/sockets/v0.2.0/network" "unsafe" ) // OptionIPAddressShape is used for storage in variant or result types. type OptionIPAddressShape struct { - shape [unsafe.Sizeof(cm.Option[network.IPAddress]{})]byte + _ cm.HostLayout + shape [unsafe.Sizeof(cm.Option[IPAddress]{})]byte } diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go index f4c7632f7b..6e982857da 100644 --- a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go @@ -9,6 +9,26 @@ import ( "internal/wasi/sockets/v0.2.0/network" ) +// Pollable represents the imported type alias "wasi:sockets/ip-name-lookup@0.2.0#pollable". +// +// See [poll.Pollable] for more information. +type Pollable = poll.Pollable + +// Network represents the imported type alias "wasi:sockets/ip-name-lookup@0.2.0#network". +// +// See [network.Network] for more information. +type Network = network.Network + +// ErrorCode represents the type alias "wasi:sockets/ip-name-lookup@0.2.0#error-code". +// +// See [network.ErrorCode] for more information. +type ErrorCode = network.ErrorCode + +// IPAddress represents the type alias "wasi:sockets/ip-name-lookup@0.2.0#ip-address". +// +// See [network.IPAddress] for more information. +type IPAddress = network.IPAddress + // ResolveAddressStream represents the imported resource "wasi:sockets/ip-name-lookup@0.2.0#resolve-address-stream". // // resource resolve-address-stream @@ -25,10 +45,6 @@ func (self ResolveAddressStream) ResourceDrop() { return } -//go:wasmimport wasi:sockets/ip-name-lookup@0.2.0 [resource-drop]resolve-address-stream -//go:noescape -func wasmimport_ResolveAddressStreamResourceDrop(self0 uint32) - // ResolveNextAddress represents the imported method "resolve-next-address". // // Returns the next address from the resolver. @@ -51,16 +67,12 @@ func wasmimport_ResolveAddressStreamResourceDrop(self0 uint32) // resolve-next-address: func() -> result, error-code> // //go:nosplit -func (self ResolveAddressStream) ResolveNextAddress() (result cm.Result[OptionIPAddressShape, cm.Option[network.IPAddress], network.ErrorCode]) { +func (self ResolveAddressStream) ResolveNextAddress() (result cm.Result[OptionIPAddressShape, cm.Option[IPAddress], ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_ResolveAddressStreamResolveNextAddress((uint32)(self0), &result) return } -//go:wasmimport wasi:sockets/ip-name-lookup@0.2.0 [method]resolve-address-stream.resolve-next-address -//go:noescape -func wasmimport_ResolveAddressStreamResolveNextAddress(self0 uint32, result *cm.Result[OptionIPAddressShape, cm.Option[network.IPAddress], network.ErrorCode]) - // Subscribe represents the imported method "subscribe". // // Create a `pollable` which will resolve once the stream is ready for I/O. @@ -71,17 +83,13 @@ func wasmimport_ResolveAddressStreamResolveNextAddress(self0 uint32, result *cm. // subscribe: func() -> pollable // //go:nosplit -func (self ResolveAddressStream) Subscribe() (result poll.Pollable) { +func (self ResolveAddressStream) Subscribe() (result Pollable) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_ResolveAddressStreamSubscribe((uint32)(self0)) - result = cm.Reinterpret[poll.Pollable]((uint32)(result0)) + result = cm.Reinterpret[Pollable]((uint32)(result0)) return } -//go:wasmimport wasi:sockets/ip-name-lookup@0.2.0 [method]resolve-address-stream.subscribe -//go:noescape -func wasmimport_ResolveAddressStreamSubscribe(self0 uint32) (result0 uint32) - // ResolveAddresses represents the imported function "resolve-addresses". // // Resolve an internet host name to a list of IP addresses. @@ -109,13 +117,9 @@ func wasmimport_ResolveAddressStreamSubscribe(self0 uint32) (result0 uint32) // error-code> // //go:nosplit -func ResolveAddresses(network_ network.Network, name string) (result cm.Result[ResolveAddressStream, ResolveAddressStream, network.ErrorCode]) { +func ResolveAddresses(network_ Network, name string) (result cm.Result[ResolveAddressStream, ResolveAddressStream, ErrorCode]) { network0 := cm.Reinterpret[uint32](network_) name0, name1 := cm.LowerString(name) wasmimport_ResolveAddresses((uint32)(network0), (*uint8)(name0), (uint32)(name1), &result) return } - -//go:wasmimport wasi:sockets/ip-name-lookup@0.2.0 resolve-addresses -//go:noescape -func wasmimport_ResolveAddresses(network0 uint32, name0 *uint8, name1 uint32, result *cm.Result[ResolveAddressStream, ResolveAddressStream, network.ErrorCode]) diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ipnamelookup.wasm.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ipnamelookup.wasm.go new file mode 100755 index 0000000000..6693408f69 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ipnamelookup.wasm.go @@ -0,0 +1,25 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package ipnamelookup + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:sockets@0.2.0". + +//go:wasmimport wasi:sockets/ip-name-lookup@0.2.0 [resource-drop]resolve-address-stream +//go:noescape +func wasmimport_ResolveAddressStreamResourceDrop(self0 uint32) + +//go:wasmimport wasi:sockets/ip-name-lookup@0.2.0 [method]resolve-address-stream.resolve-next-address +//go:noescape +func wasmimport_ResolveAddressStreamResolveNextAddress(self0 uint32, result *cm.Result[OptionIPAddressShape, cm.Option[IPAddress], ErrorCode]) + +//go:wasmimport wasi:sockets/ip-name-lookup@0.2.0 [method]resolve-address-stream.subscribe +//go:noescape +func wasmimport_ResolveAddressStreamSubscribe(self0 uint32) (result0 uint32) + +//go:wasmimport wasi:sockets/ip-name-lookup@0.2.0 resolve-addresses +//go:noescape +func wasmimport_ResolveAddresses(network0 uint32, name0 *uint8, name1 uint32, result *cm.Result[ResolveAddressStream, ResolveAddressStream, ErrorCode]) diff --git a/src/internal/wasi/sockets/v0.2.0/network/abi.go b/src/internal/wasi/sockets/v0.2.0/network/abi.go index 54820be8aa..0f42e109db 100644 --- a/src/internal/wasi/sockets/v0.2.0/network/abi.go +++ b/src/internal/wasi/sockets/v0.2.0/network/abi.go @@ -3,10 +3,12 @@ package network import ( + "internal/cm" "unsafe" ) // IPv6SocketAddressShape is used for storage in variant or result types. type IPv6SocketAddressShape struct { + _ cm.HostLayout shape [unsafe.Sizeof(IPv6SocketAddress{})]byte } diff --git a/src/internal/wasi/sockets/v0.2.0/network/network.wasm.go b/src/internal/wasi/sockets/v0.2.0/network/network.wasm.go new file mode 100755 index 0000000000..012a79ffc7 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/network/network.wasm.go @@ -0,0 +1,9 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package network + +// This file contains wasmimport and wasmexport declarations for "wasi:sockets@0.2.0". + +//go:wasmimport wasi:sockets/network@0.2.0 [resource-drop]network +//go:noescape +func wasmimport_NetworkResourceDrop(self0 uint32) diff --git a/src/internal/wasi/sockets/v0.2.0/network/network.wit.go b/src/internal/wasi/sockets/v0.2.0/network/network.wit.go index 58ad4e9b6e..93a8e82002 100644 --- a/src/internal/wasi/sockets/v0.2.0/network/network.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/network/network.wit.go @@ -27,10 +27,6 @@ func (self Network) ResourceDrop() { return } -//go:wasmimport wasi:sockets/network@0.2.0 [resource-drop]network -//go:noescape -func wasmimport_NetworkResourceDrop(self0 uint32) - // ErrorCode represents the enum "wasi:sockets/network@0.2.0#error-code". // // Error codes. @@ -250,6 +246,16 @@ func (self *IPAddress) IPv6() *IPv6Address { return cm.Case[IPv6Address](self, 1) } +var stringsIPAddress = [2]string{ + "ipv4", + "ipv6", +} + +// String implements [fmt.Stringer], returning the variant case name of v. +func (v IPAddress) String() string { + return stringsIPAddress[v.Tag()] +} + // IPv4SocketAddress represents the record "wasi:sockets/network@0.2.0#ipv4-socket-address". // // record ipv4-socket-address { @@ -257,6 +263,7 @@ func (self *IPAddress) IPv6() *IPv6Address { // address: ipv4-address, // } type IPv4SocketAddress struct { + _ cm.HostLayout // sin_port Port uint16 @@ -273,6 +280,7 @@ type IPv4SocketAddress struct { // scope-id: u32, // } type IPv6SocketAddress struct { + _ cm.HostLayout // sin6_port Port uint16 @@ -313,3 +321,13 @@ func IPSocketAddressIPv6(data IPv6SocketAddress) IPSocketAddress { func (self *IPSocketAddress) IPv6() *IPv6SocketAddress { return cm.Case[IPv6SocketAddress](self, 1) } + +var stringsIPSocketAddress = [2]string{ + "ipv4", + "ipv6", +} + +// String implements [fmt.Stringer], returning the variant case name of v. +func (v IPSocketAddress) String() string { + return stringsIPSocketAddress[v.Tag()] +} diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go index 44e24502b8..4462a33c6a 100644 --- a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go @@ -9,6 +9,26 @@ import ( "internal/wasi/sockets/v0.2.0/tcp" ) +// Network represents the imported type alias "wasi:sockets/tcp-create-socket@0.2.0#network". +// +// See [network.Network] for more information. +type Network = network.Network + +// ErrorCode represents the type alias "wasi:sockets/tcp-create-socket@0.2.0#error-code". +// +// See [network.ErrorCode] for more information. +type ErrorCode = network.ErrorCode + +// IPAddressFamily represents the type alias "wasi:sockets/tcp-create-socket@0.2.0#ip-address-family". +// +// See [network.IPAddressFamily] for more information. +type IPAddressFamily = network.IPAddressFamily + +// TCPSocket represents the imported type alias "wasi:sockets/tcp-create-socket@0.2.0#tcp-socket". +// +// See [tcp.TCPSocket] for more information. +type TCPSocket = tcp.TCPSocket + // CreateTCPSocket represents the imported function "create-tcp-socket". // // Create a new TCP socket. @@ -41,12 +61,8 @@ import ( // error-code> // //go:nosplit -func CreateTCPSocket(addressFamily network.IPAddressFamily) (result cm.Result[tcp.TCPSocket, tcp.TCPSocket, network.ErrorCode]) { +func CreateTCPSocket(addressFamily IPAddressFamily) (result cm.Result[TCPSocket, TCPSocket, ErrorCode]) { addressFamily0 := (uint32)(addressFamily) wasmimport_CreateTCPSocket((uint32)(addressFamily0), &result) return } - -//go:wasmimport wasi:sockets/tcp-create-socket@0.2.0 create-tcp-socket -//go:noescape -func wasmimport_CreateTCPSocket(addressFamily0 uint32, result *cm.Result[tcp.TCPSocket, tcp.TCPSocket, network.ErrorCode]) diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcpcreatesocket.wasm.go b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcpcreatesocket.wasm.go new file mode 100755 index 0000000000..9adf8e4158 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcpcreatesocket.wasm.go @@ -0,0 +1,13 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package tcpcreatesocket + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:sockets@0.2.0". + +//go:wasmimport wasi:sockets/tcp-create-socket@0.2.0 create-tcp-socket +//go:noescape +func wasmimport_CreateTCPSocket(addressFamily0 uint32, result *cm.Result[TCPSocket, TCPSocket, ErrorCode]) diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/abi.go b/src/internal/wasi/sockets/v0.2.0/tcp/abi.go index f6b37f118e..8174d298fd 100644 --- a/src/internal/wasi/sockets/v0.2.0/tcp/abi.go +++ b/src/internal/wasi/sockets/v0.2.0/tcp/abi.go @@ -4,23 +4,25 @@ package tcp import ( "internal/cm" - "internal/wasi/io/v0.2.0/streams" "internal/wasi/sockets/v0.2.0/network" "unsafe" ) // TupleTCPSocketInputStreamOutputStreamShape is used for storage in variant or result types. type TupleTCPSocketInputStreamOutputStreamShape struct { - shape [unsafe.Sizeof(cm.Tuple3[TCPSocket, streams.InputStream, streams.OutputStream]{})]byte + _ cm.HostLayout + shape [unsafe.Sizeof(cm.Tuple3[TCPSocket, InputStream, OutputStream]{})]byte } // TupleInputStreamOutputStreamShape is used for storage in variant or result types. type TupleInputStreamOutputStreamShape struct { - shape [unsafe.Sizeof(cm.Tuple[streams.InputStream, streams.OutputStream]{})]byte + _ cm.HostLayout + shape [unsafe.Sizeof(cm.Tuple[InputStream, OutputStream]{})]byte } // IPSocketAddressShape is used for storage in variant or result types. type IPSocketAddressShape struct { + _ cm.HostLayout shape [unsafe.Sizeof(network.IPSocketAddress{})]byte } diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go new file mode 100755 index 0000000000..1257fcb146 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go @@ -0,0 +1,125 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package tcp + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:sockets@0.2.0". + +//go:wasmimport wasi:sockets/tcp@0.2.0 [resource-drop]tcp-socket +//go:noescape +func wasmimport_TCPSocketResourceDrop(self0 uint32) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.accept +//go:noescape +func wasmimport_TCPSocketAccept(self0 uint32, result *cm.Result[TupleTCPSocketInputStreamOutputStreamShape, cm.Tuple3[TCPSocket, InputStream, OutputStream], ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.address-family +//go:noescape +func wasmimport_TCPSocketAddressFamily(self0 uint32) (result0 uint32) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.finish-bind +//go:noescape +func wasmimport_TCPSocketFinishBind(self0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.finish-connect +//go:noescape +func wasmimport_TCPSocketFinishConnect(self0 uint32, result *cm.Result[TupleInputStreamOutputStreamShape, cm.Tuple[InputStream, OutputStream], ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.finish-listen +//go:noescape +func wasmimport_TCPSocketFinishListen(self0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.hop-limit +//go:noescape +func wasmimport_TCPSocketHopLimit(self0 uint32, result *cm.Result[uint8, uint8, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.is-listening +//go:noescape +func wasmimport_TCPSocketIsListening(self0 uint32) (result0 uint32) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-count +//go:noescape +func wasmimport_TCPSocketKeepAliveCount(self0 uint32, result *cm.Result[uint32, uint32, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-enabled +//go:noescape +func wasmimport_TCPSocketKeepAliveEnabled(self0 uint32, result *cm.Result[bool, bool, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-idle-time +//go:noescape +func wasmimport_TCPSocketKeepAliveIdleTime(self0 uint32, result *cm.Result[uint64, Duration, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-interval +//go:noescape +func wasmimport_TCPSocketKeepAliveInterval(self0 uint32, result *cm.Result[uint64, Duration, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.local-address +//go:noescape +func wasmimport_TCPSocketLocalAddress(self0 uint32, result *cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.receive-buffer-size +//go:noescape +func wasmimport_TCPSocketReceiveBufferSize(self0 uint32, result *cm.Result[uint64, uint64, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.remote-address +//go:noescape +func wasmimport_TCPSocketRemoteAddress(self0 uint32, result *cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.send-buffer-size +//go:noescape +func wasmimport_TCPSocketSendBufferSize(self0 uint32, result *cm.Result[uint64, uint64, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-hop-limit +//go:noescape +func wasmimport_TCPSocketSetHopLimit(self0 uint32, value0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-count +//go:noescape +func wasmimport_TCPSocketSetKeepAliveCount(self0 uint32, value0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-enabled +//go:noescape +func wasmimport_TCPSocketSetKeepAliveEnabled(self0 uint32, value0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-idle-time +//go:noescape +func wasmimport_TCPSocketSetKeepAliveIdleTime(self0 uint32, value0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-interval +//go:noescape +func wasmimport_TCPSocketSetKeepAliveInterval(self0 uint32, value0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-listen-backlog-size +//go:noescape +func wasmimport_TCPSocketSetListenBacklogSize(self0 uint32, value0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-receive-buffer-size +//go:noescape +func wasmimport_TCPSocketSetReceiveBufferSize(self0 uint32, value0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-send-buffer-size +//go:noescape +func wasmimport_TCPSocketSetSendBufferSize(self0 uint32, value0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.shutdown +//go:noescape +func wasmimport_TCPSocketShutdown(self0 uint32, shutdownType0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.start-bind +//go:noescape +func wasmimport_TCPSocketStartBind(self0 uint32, network0 uint32, localAddress0 uint32, localAddress1 uint32, localAddress2 uint32, localAddress3 uint32, localAddress4 uint32, localAddress5 uint32, localAddress6 uint32, localAddress7 uint32, localAddress8 uint32, localAddress9 uint32, localAddress10 uint32, localAddress11 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.start-connect +//go:noescape +func wasmimport_TCPSocketStartConnect(self0 uint32, network0 uint32, remoteAddress0 uint32, remoteAddress1 uint32, remoteAddress2 uint32, remoteAddress3 uint32, remoteAddress4 uint32, remoteAddress5 uint32, remoteAddress6 uint32, remoteAddress7 uint32, remoteAddress8 uint32, remoteAddress9 uint32, remoteAddress10 uint32, remoteAddress11 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.start-listen +//go:noescape +func wasmimport_TCPSocketStartListen(self0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.subscribe +//go:noescape +func wasmimport_TCPSocketSubscribe(self0 uint32) (result0 uint32) diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go index 30eefbd87e..5eb102cd30 100644 --- a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go @@ -11,6 +11,46 @@ import ( "internal/wasi/sockets/v0.2.0/network" ) +// InputStream represents the imported type alias "wasi:sockets/tcp@0.2.0#input-stream". +// +// See [streams.InputStream] for more information. +type InputStream = streams.InputStream + +// OutputStream represents the imported type alias "wasi:sockets/tcp@0.2.0#output-stream". +// +// See [streams.OutputStream] for more information. +type OutputStream = streams.OutputStream + +// Pollable represents the imported type alias "wasi:sockets/tcp@0.2.0#pollable". +// +// See [poll.Pollable] for more information. +type Pollable = poll.Pollable + +// Duration represents the type alias "wasi:sockets/tcp@0.2.0#duration". +// +// See [monotonicclock.Duration] for more information. +type Duration = monotonicclock.Duration + +// Network represents the imported type alias "wasi:sockets/tcp@0.2.0#network". +// +// See [network.Network] for more information. +type Network = network.Network + +// ErrorCode represents the type alias "wasi:sockets/tcp@0.2.0#error-code". +// +// See [network.ErrorCode] for more information. +type ErrorCode = network.ErrorCode + +// IPSocketAddress represents the type alias "wasi:sockets/tcp@0.2.0#ip-socket-address". +// +// See [network.IPSocketAddress] for more information. +type IPSocketAddress = network.IPSocketAddress + +// IPAddressFamily represents the type alias "wasi:sockets/tcp@0.2.0#ip-address-family". +// +// See [network.IPAddressFamily] for more information. +type IPAddressFamily = network.IPAddressFamily + // ShutdownType represents the enum "wasi:sockets/tcp@0.2.0#shutdown-type". // // enum shutdown-type { @@ -81,10 +121,6 @@ func (self TCPSocket) ResourceDrop() { return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [resource-drop]tcp-socket -//go:noescape -func wasmimport_TCPSocketResourceDrop(self0 uint32) - // Accept represents the imported method "accept". // // Accept a new client socket. @@ -120,16 +156,12 @@ func wasmimport_TCPSocketResourceDrop(self0 uint32) // accept: func() -> result, error-code> // //go:nosplit -func (self TCPSocket) Accept() (result cm.Result[TupleTCPSocketInputStreamOutputStreamShape, cm.Tuple3[TCPSocket, streams.InputStream, streams.OutputStream], network.ErrorCode]) { +func (self TCPSocket) Accept() (result cm.Result[TupleTCPSocketInputStreamOutputStreamShape, cm.Tuple3[TCPSocket, InputStream, OutputStream], ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketAccept((uint32)(self0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.accept -//go:noescape -func wasmimport_TCPSocketAccept(self0 uint32, result *cm.Result[TupleTCPSocketInputStreamOutputStreamShape, cm.Tuple3[TCPSocket, streams.InputStream, streams.OutputStream], network.ErrorCode]) - // AddressFamily represents the imported method "address-family". // // Whether this is a IPv4 or IPv6 socket. @@ -139,62 +171,46 @@ func wasmimport_TCPSocketAccept(self0 uint32, result *cm.Result[TupleTCPSocketIn // address-family: func() -> ip-address-family // //go:nosplit -func (self TCPSocket) AddressFamily() (result network.IPAddressFamily) { +func (self TCPSocket) AddressFamily() (result IPAddressFamily) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_TCPSocketAddressFamily((uint32)(self0)) result = (network.IPAddressFamily)((uint32)(result0)) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.address-family -//go:noescape -func wasmimport_TCPSocketAddressFamily(self0 uint32) (result0 uint32) - // FinishBind represents the imported method "finish-bind". // // finish-bind: func() -> result<_, error-code> // //go:nosplit -func (self TCPSocket) FinishBind() (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { +func (self TCPSocket) FinishBind() (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketFinishBind((uint32)(self0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.finish-bind -//go:noescape -func wasmimport_TCPSocketFinishBind(self0 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) - // FinishConnect represents the imported method "finish-connect". // // finish-connect: func() -> result, error-code> // //go:nosplit -func (self TCPSocket) FinishConnect() (result cm.Result[TupleInputStreamOutputStreamShape, cm.Tuple[streams.InputStream, streams.OutputStream], network.ErrorCode]) { +func (self TCPSocket) FinishConnect() (result cm.Result[TupleInputStreamOutputStreamShape, cm.Tuple[InputStream, OutputStream], ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketFinishConnect((uint32)(self0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.finish-connect -//go:noescape -func wasmimport_TCPSocketFinishConnect(self0 uint32, result *cm.Result[TupleInputStreamOutputStreamShape, cm.Tuple[streams.InputStream, streams.OutputStream], network.ErrorCode]) - // FinishListen represents the imported method "finish-listen". // // finish-listen: func() -> result<_, error-code> // //go:nosplit -func (self TCPSocket) FinishListen() (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { +func (self TCPSocket) FinishListen() (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketFinishListen((uint32)(self0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.finish-listen -//go:noescape -func wasmimport_TCPSocketFinishListen(self0 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) - // HopLimit represents the imported method "hop-limit". // // Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. @@ -207,16 +223,12 @@ func wasmimport_TCPSocketFinishListen(self0 uint32, result *cm.Result[network.Er // hop-limit: func() -> result // //go:nosplit -func (self TCPSocket) HopLimit() (result cm.Result[uint8, uint8, network.ErrorCode]) { +func (self TCPSocket) HopLimit() (result cm.Result[uint8, uint8, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketHopLimit((uint32)(self0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.hop-limit -//go:noescape -func wasmimport_TCPSocketHopLimit(self0 uint32, result *cm.Result[uint8, uint8, network.ErrorCode]) - // IsListening represents the imported method "is-listening". // // Whether the socket is in the `listening` state. @@ -233,10 +245,6 @@ func (self TCPSocket) IsListening() (result bool) { return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.is-listening -//go:noescape -func wasmimport_TCPSocketIsListening(self0 uint32) (result0 uint32) - // KeepAliveCount represents the imported method "keep-alive-count". // // The maximum amount of keepalive packets TCP should send before aborting the connection. @@ -255,16 +263,12 @@ func wasmimport_TCPSocketIsListening(self0 uint32) (result0 uint32) // keep-alive-count: func() -> result // //go:nosplit -func (self TCPSocket) KeepAliveCount() (result cm.Result[uint32, uint32, network.ErrorCode]) { +func (self TCPSocket) KeepAliveCount() (result cm.Result[uint32, uint32, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketKeepAliveCount((uint32)(self0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-count -//go:noescape -func wasmimport_TCPSocketKeepAliveCount(self0 uint32, result *cm.Result[uint32, uint32, network.ErrorCode]) - // KeepAliveEnabled represents the imported method "keep-alive-enabled". // // Enables or disables keepalive. @@ -281,16 +285,12 @@ func wasmimport_TCPSocketKeepAliveCount(self0 uint32, result *cm.Result[uint32, // keep-alive-enabled: func() -> result // //go:nosplit -func (self TCPSocket) KeepAliveEnabled() (result cm.Result[bool, bool, network.ErrorCode]) { +func (self TCPSocket) KeepAliveEnabled() (result cm.Result[bool, bool, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketKeepAliveEnabled((uint32)(self0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-enabled -//go:noescape -func wasmimport_TCPSocketKeepAliveEnabled(self0 uint32, result *cm.Result[bool, bool, network.ErrorCode]) - // KeepAliveIdleTime represents the imported method "keep-alive-idle-time". // // Amount of time the connection has to be idle before TCP starts sending keepalive @@ -310,16 +310,12 @@ func wasmimport_TCPSocketKeepAliveEnabled(self0 uint32, result *cm.Result[bool, // keep-alive-idle-time: func() -> result // //go:nosplit -func (self TCPSocket) KeepAliveIdleTime() (result cm.Result[uint64, monotonicclock.Duration, network.ErrorCode]) { +func (self TCPSocket) KeepAliveIdleTime() (result cm.Result[uint64, Duration, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketKeepAliveIdleTime((uint32)(self0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-idle-time -//go:noescape -func wasmimport_TCPSocketKeepAliveIdleTime(self0 uint32, result *cm.Result[uint64, monotonicclock.Duration, network.ErrorCode]) - // KeepAliveInterval represents the imported method "keep-alive-interval". // // The time between keepalive packets. @@ -338,16 +334,12 @@ func wasmimport_TCPSocketKeepAliveIdleTime(self0 uint32, result *cm.Result[uint6 // keep-alive-interval: func() -> result // //go:nosplit -func (self TCPSocket) KeepAliveInterval() (result cm.Result[uint64, monotonicclock.Duration, network.ErrorCode]) { +func (self TCPSocket) KeepAliveInterval() (result cm.Result[uint64, Duration, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketKeepAliveInterval((uint32)(self0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-interval -//go:noescape -func wasmimport_TCPSocketKeepAliveInterval(self0 uint32, result *cm.Result[uint64, monotonicclock.Duration, network.ErrorCode]) - // LocalAddress represents the imported method "local-address". // // Get the bound local address. @@ -371,16 +363,12 @@ func wasmimport_TCPSocketKeepAliveInterval(self0 uint32, result *cm.Result[uint6 // local-address: func() -> result // //go:nosplit -func (self TCPSocket) LocalAddress() (result cm.Result[IPSocketAddressShape, network.IPSocketAddress, network.ErrorCode]) { +func (self TCPSocket) LocalAddress() (result cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketLocalAddress((uint32)(self0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.local-address -//go:noescape -func wasmimport_TCPSocketLocalAddress(self0 uint32, result *cm.Result[IPSocketAddressShape, network.IPSocketAddress, network.ErrorCode]) - // ReceiveBufferSize represents the imported method "receive-buffer-size". // // The kernel buffer space reserved for sends/receives on this socket. @@ -399,16 +387,12 @@ func wasmimport_TCPSocketLocalAddress(self0 uint32, result *cm.Result[IPSocketAd // receive-buffer-size: func() -> result // //go:nosplit -func (self TCPSocket) ReceiveBufferSize() (result cm.Result[uint64, uint64, network.ErrorCode]) { +func (self TCPSocket) ReceiveBufferSize() (result cm.Result[uint64, uint64, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketReceiveBufferSize((uint32)(self0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.receive-buffer-size -//go:noescape -func wasmimport_TCPSocketReceiveBufferSize(self0 uint32, result *cm.Result[uint64, uint64, network.ErrorCode]) - // RemoteAddress represents the imported method "remote-address". // // Get the remote address. @@ -425,111 +409,83 @@ func wasmimport_TCPSocketReceiveBufferSize(self0 uint32, result *cm.Result[uint6 // remote-address: func() -> result // //go:nosplit -func (self TCPSocket) RemoteAddress() (result cm.Result[IPSocketAddressShape, network.IPSocketAddress, network.ErrorCode]) { +func (self TCPSocket) RemoteAddress() (result cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketRemoteAddress((uint32)(self0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.remote-address -//go:noescape -func wasmimport_TCPSocketRemoteAddress(self0 uint32, result *cm.Result[IPSocketAddressShape, network.IPSocketAddress, network.ErrorCode]) - // SendBufferSize represents the imported method "send-buffer-size". // // send-buffer-size: func() -> result // //go:nosplit -func (self TCPSocket) SendBufferSize() (result cm.Result[uint64, uint64, network.ErrorCode]) { +func (self TCPSocket) SendBufferSize() (result cm.Result[uint64, uint64, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketSendBufferSize((uint32)(self0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.send-buffer-size -//go:noescape -func wasmimport_TCPSocketSendBufferSize(self0 uint32, result *cm.Result[uint64, uint64, network.ErrorCode]) - // SetHopLimit represents the imported method "set-hop-limit". // // set-hop-limit: func(value: u8) -> result<_, error-code> // //go:nosplit -func (self TCPSocket) SetHopLimit(value uint8) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { +func (self TCPSocket) SetHopLimit(value uint8) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint32)(value) wasmimport_TCPSocketSetHopLimit((uint32)(self0), (uint32)(value0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-hop-limit -//go:noescape -func wasmimport_TCPSocketSetHopLimit(self0 uint32, value0 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) - // SetKeepAliveCount represents the imported method "set-keep-alive-count". // // set-keep-alive-count: func(value: u32) -> result<_, error-code> // //go:nosplit -func (self TCPSocket) SetKeepAliveCount(value uint32) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { +func (self TCPSocket) SetKeepAliveCount(value uint32) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint32)(value) wasmimport_TCPSocketSetKeepAliveCount((uint32)(self0), (uint32)(value0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-count -//go:noescape -func wasmimport_TCPSocketSetKeepAliveCount(self0 uint32, value0 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) - // SetKeepAliveEnabled represents the imported method "set-keep-alive-enabled". // // set-keep-alive-enabled: func(value: bool) -> result<_, error-code> // //go:nosplit -func (self TCPSocket) SetKeepAliveEnabled(value bool) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { +func (self TCPSocket) SetKeepAliveEnabled(value bool) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := cm.BoolToU32(value) wasmimport_TCPSocketSetKeepAliveEnabled((uint32)(self0), (uint32)(value0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-enabled -//go:noescape -func wasmimport_TCPSocketSetKeepAliveEnabled(self0 uint32, value0 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) - // SetKeepAliveIdleTime represents the imported method "set-keep-alive-idle-time". // // set-keep-alive-idle-time: func(value: duration) -> result<_, error-code> // //go:nosplit -func (self TCPSocket) SetKeepAliveIdleTime(value monotonicclock.Duration) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { +func (self TCPSocket) SetKeepAliveIdleTime(value Duration) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint64)(value) wasmimport_TCPSocketSetKeepAliveIdleTime((uint32)(self0), (uint64)(value0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-idle-time -//go:noescape -func wasmimport_TCPSocketSetKeepAliveIdleTime(self0 uint32, value0 uint64, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) - // SetKeepAliveInterval represents the imported method "set-keep-alive-interval". // // set-keep-alive-interval: func(value: duration) -> result<_, error-code> // //go:nosplit -func (self TCPSocket) SetKeepAliveInterval(value monotonicclock.Duration) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { +func (self TCPSocket) SetKeepAliveInterval(value Duration) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint64)(value) wasmimport_TCPSocketSetKeepAliveInterval((uint32)(self0), (uint64)(value0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-interval -//go:noescape -func wasmimport_TCPSocketSetKeepAliveInterval(self0 uint32, value0 uint64, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) - // SetListenBacklogSize represents the imported method "set-listen-backlog-size". // // Hints the desired listen queue size. Implementations are free to ignore this. @@ -548,49 +504,37 @@ func wasmimport_TCPSocketSetKeepAliveInterval(self0 uint32, value0 uint64, resul // set-listen-backlog-size: func(value: u64) -> result<_, error-code> // //go:nosplit -func (self TCPSocket) SetListenBacklogSize(value uint64) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { +func (self TCPSocket) SetListenBacklogSize(value uint64) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint64)(value) wasmimport_TCPSocketSetListenBacklogSize((uint32)(self0), (uint64)(value0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-listen-backlog-size -//go:noescape -func wasmimport_TCPSocketSetListenBacklogSize(self0 uint32, value0 uint64, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) - // SetReceiveBufferSize represents the imported method "set-receive-buffer-size". // // set-receive-buffer-size: func(value: u64) -> result<_, error-code> // //go:nosplit -func (self TCPSocket) SetReceiveBufferSize(value uint64) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { +func (self TCPSocket) SetReceiveBufferSize(value uint64) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint64)(value) wasmimport_TCPSocketSetReceiveBufferSize((uint32)(self0), (uint64)(value0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-receive-buffer-size -//go:noescape -func wasmimport_TCPSocketSetReceiveBufferSize(self0 uint32, value0 uint64, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) - // SetSendBufferSize represents the imported method "set-send-buffer-size". // // set-send-buffer-size: func(value: u64) -> result<_, error-code> // //go:nosplit -func (self TCPSocket) SetSendBufferSize(value uint64) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { +func (self TCPSocket) SetSendBufferSize(value uint64) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint64)(value) wasmimport_TCPSocketSetSendBufferSize((uint32)(self0), (uint64)(value0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-send-buffer-size -//go:noescape -func wasmimport_TCPSocketSetSendBufferSize(self0 uint32, value0 uint64, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) - // Shutdown represents the imported method "shutdown". // // Initiate a graceful shutdown. @@ -620,17 +564,13 @@ func wasmimport_TCPSocketSetSendBufferSize(self0 uint32, value0 uint64, result * // shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code> // //go:nosplit -func (self TCPSocket) Shutdown(shutdownType ShutdownType) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { +func (self TCPSocket) Shutdown(shutdownType ShutdownType) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) shutdownType0 := (uint32)(shutdownType) wasmimport_TCPSocketShutdown((uint32)(self0), (uint32)(shutdownType0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.shutdown -//go:noescape -func wasmimport_TCPSocketShutdown(self0 uint32, shutdownType0 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) - // StartBind represents the imported method "start-bind". // // Bind the socket to a specific network on the provided IP address and port. @@ -685,7 +625,7 @@ func wasmimport_TCPSocketShutdown(self0 uint32, shutdownType0 uint32, result *cm // result<_, error-code> // //go:nosplit -func (self TCPSocket) StartBind(network_ network.Network, localAddress network.IPSocketAddress) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { +func (self TCPSocket) StartBind(network_ Network, localAddress IPSocketAddress) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) network0 := cm.Reinterpret[uint32](network_) localAddress0, localAddress1, localAddress2, localAddress3, localAddress4, localAddress5, localAddress6, localAddress7, localAddress8, localAddress9, localAddress10, localAddress11 := lower_IPSocketAddress(localAddress) @@ -693,10 +633,6 @@ func (self TCPSocket) StartBind(network_ network.Network, localAddress network.I return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.start-bind -//go:noescape -func wasmimport_TCPSocketStartBind(self0 uint32, network0 uint32, localAddress0 uint32, localAddress1 uint32, localAddress2 uint32, localAddress3 uint32, localAddress4 uint32, localAddress5 uint32, localAddress6 uint32, localAddress7 uint32, localAddress8 uint32, localAddress9 uint32, localAddress10 uint32, localAddress11 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) - // StartConnect represents the imported method "start-connect". // // Connect to a remote endpoint. @@ -757,7 +693,7 @@ func wasmimport_TCPSocketStartBind(self0 uint32, network0 uint32, localAddress0 // -> result<_, error-code> // //go:nosplit -func (self TCPSocket) StartConnect(network_ network.Network, remoteAddress network.IPSocketAddress) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { +func (self TCPSocket) StartConnect(network_ Network, remoteAddress IPSocketAddress) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) network0 := cm.Reinterpret[uint32](network_) remoteAddress0, remoteAddress1, remoteAddress2, remoteAddress3, remoteAddress4, remoteAddress5, remoteAddress6, remoteAddress7, remoteAddress8, remoteAddress9, remoteAddress10, remoteAddress11 := lower_IPSocketAddress(remoteAddress) @@ -765,10 +701,6 @@ func (self TCPSocket) StartConnect(network_ network.Network, remoteAddress netwo return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.start-connect -//go:noescape -func wasmimport_TCPSocketStartConnect(self0 uint32, network0 uint32, remoteAddress0 uint32, remoteAddress1 uint32, remoteAddress2 uint32, remoteAddress3 uint32, remoteAddress4 uint32, remoteAddress5 uint32, remoteAddress6 uint32, remoteAddress7 uint32, remoteAddress8 uint32, remoteAddress9 uint32, remoteAddress10 uint32, remoteAddress11 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) - // StartListen represents the imported method "start-listen". // // Start listening for new connections. @@ -803,16 +735,12 @@ func wasmimport_TCPSocketStartConnect(self0 uint32, network0 uint32, remoteAddre // start-listen: func() -> result<_, error-code> // //go:nosplit -func (self TCPSocket) StartListen() (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { +func (self TCPSocket) StartListen() (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketStartListen((uint32)(self0), &result) return } -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.start-listen -//go:noescape -func wasmimport_TCPSocketStartListen(self0 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) - // Subscribe represents the imported method "subscribe". // // Create a `pollable` which can be used to poll for, or block on, @@ -836,13 +764,9 @@ func wasmimport_TCPSocketStartListen(self0 uint32, result *cm.Result[network.Err // subscribe: func() -> pollable // //go:nosplit -func (self TCPSocket) Subscribe() (result poll.Pollable) { +func (self TCPSocket) Subscribe() (result Pollable) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_TCPSocketSubscribe((uint32)(self0)) - result = cm.Reinterpret[poll.Pollable]((uint32)(result0)) + result = cm.Reinterpret[Pollable]((uint32)(result0)) return } - -//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.subscribe -//go:noescape -func wasmimport_TCPSocketSubscribe(self0 uint32) (result0 uint32) diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go index dc21be4d9c..c0f31d725d 100644 --- a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go @@ -9,6 +9,26 @@ import ( "internal/wasi/sockets/v0.2.0/udp" ) +// Network represents the imported type alias "wasi:sockets/udp-create-socket@0.2.0#network". +// +// See [network.Network] for more information. +type Network = network.Network + +// ErrorCode represents the type alias "wasi:sockets/udp-create-socket@0.2.0#error-code". +// +// See [network.ErrorCode] for more information. +type ErrorCode = network.ErrorCode + +// IPAddressFamily represents the type alias "wasi:sockets/udp-create-socket@0.2.0#ip-address-family". +// +// See [network.IPAddressFamily] for more information. +type IPAddressFamily = network.IPAddressFamily + +// UDPSocket represents the imported type alias "wasi:sockets/udp-create-socket@0.2.0#udp-socket". +// +// See [udp.UDPSocket] for more information. +type UDPSocket = udp.UDPSocket + // CreateUDPSocket represents the imported function "create-udp-socket". // // Create a new UDP socket. @@ -41,12 +61,8 @@ import ( // error-code> // //go:nosplit -func CreateUDPSocket(addressFamily network.IPAddressFamily) (result cm.Result[udp.UDPSocket, udp.UDPSocket, network.ErrorCode]) { +func CreateUDPSocket(addressFamily IPAddressFamily) (result cm.Result[UDPSocket, UDPSocket, ErrorCode]) { addressFamily0 := (uint32)(addressFamily) wasmimport_CreateUDPSocket((uint32)(addressFamily0), &result) return } - -//go:wasmimport wasi:sockets/udp-create-socket@0.2.0 create-udp-socket -//go:noescape -func wasmimport_CreateUDPSocket(addressFamily0 uint32, result *cm.Result[udp.UDPSocket, udp.UDPSocket, network.ErrorCode]) diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udpcreatesocket.wasm.go b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udpcreatesocket.wasm.go new file mode 100755 index 0000000000..0f56e12bb2 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udpcreatesocket.wasm.go @@ -0,0 +1,13 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package udpcreatesocket + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:sockets@0.2.0". + +//go:wasmimport wasi:sockets/udp-create-socket@0.2.0 create-udp-socket +//go:noescape +func wasmimport_CreateUDPSocket(addressFamily0 uint32, result *cm.Result[UDPSocket, UDPSocket, ErrorCode]) diff --git a/src/internal/wasi/sockets/v0.2.0/udp/abi.go b/src/internal/wasi/sockets/v0.2.0/udp/abi.go index 23a96c983e..71d00f9d6b 100644 --- a/src/internal/wasi/sockets/v0.2.0/udp/abi.go +++ b/src/internal/wasi/sockets/v0.2.0/udp/abi.go @@ -10,6 +10,7 @@ import ( // IPSocketAddressShape is used for storage in variant or result types. type IPSocketAddressShape struct { + _ cm.HostLayout shape [unsafe.Sizeof(network.IPSocketAddress{})]byte } @@ -76,10 +77,11 @@ func lower_IPSocketAddress(v network.IPSocketAddress) (f0 uint32, f1 uint32, f2 // TupleIncomingDatagramStreamOutgoingDatagramStreamShape is used for storage in variant or result types. type TupleIncomingDatagramStreamOutgoingDatagramStreamShape struct { + _ cm.HostLayout shape [unsafe.Sizeof(cm.Tuple[IncomingDatagramStream, OutgoingDatagramStream]{})]byte } -func lower_OptionIPSocketAddress(v cm.Option[network.IPSocketAddress]) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32, f8 uint32, f9 uint32, f10 uint32, f11 uint32, f12 uint32) { +func lower_OptionIPSocketAddress(v cm.Option[IPSocketAddress]) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32, f8 uint32, f9 uint32, f10 uint32, f11 uint32, f12 uint32) { some := v.Some() if some != nil { f0 = 1 diff --git a/src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go b/src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go new file mode 100755 index 0000000000..3e3b9e554a --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go @@ -0,0 +1,93 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package udp + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:sockets@0.2.0". + +//go:wasmimport wasi:sockets/udp@0.2.0 [resource-drop]udp-socket +//go:noescape +func wasmimport_UDPSocketResourceDrop(self0 uint32) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.address-family +//go:noescape +func wasmimport_UDPSocketAddressFamily(self0 uint32) (result0 uint32) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.finish-bind +//go:noescape +func wasmimport_UDPSocketFinishBind(self0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.local-address +//go:noescape +func wasmimport_UDPSocketLocalAddress(self0 uint32, result *cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.receive-buffer-size +//go:noescape +func wasmimport_UDPSocketReceiveBufferSize(self0 uint32, result *cm.Result[uint64, uint64, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.remote-address +//go:noescape +func wasmimport_UDPSocketRemoteAddress(self0 uint32, result *cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.send-buffer-size +//go:noescape +func wasmimport_UDPSocketSendBufferSize(self0 uint32, result *cm.Result[uint64, uint64, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.set-receive-buffer-size +//go:noescape +func wasmimport_UDPSocketSetReceiveBufferSize(self0 uint32, value0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.set-send-buffer-size +//go:noescape +func wasmimport_UDPSocketSetSendBufferSize(self0 uint32, value0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.set-unicast-hop-limit +//go:noescape +func wasmimport_UDPSocketSetUnicastHopLimit(self0 uint32, value0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.start-bind +//go:noescape +func wasmimport_UDPSocketStartBind(self0 uint32, network0 uint32, localAddress0 uint32, localAddress1 uint32, localAddress2 uint32, localAddress3 uint32, localAddress4 uint32, localAddress5 uint32, localAddress6 uint32, localAddress7 uint32, localAddress8 uint32, localAddress9 uint32, localAddress10 uint32, localAddress11 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.stream +//go:noescape +func wasmimport_UDPSocketStream(self0 uint32, remoteAddress0 uint32, remoteAddress1 uint32, remoteAddress2 uint32, remoteAddress3 uint32, remoteAddress4 uint32, remoteAddress5 uint32, remoteAddress6 uint32, remoteAddress7 uint32, remoteAddress8 uint32, remoteAddress9 uint32, remoteAddress10 uint32, remoteAddress11 uint32, remoteAddress12 uint32, result *cm.Result[TupleIncomingDatagramStreamOutgoingDatagramStreamShape, cm.Tuple[IncomingDatagramStream, OutgoingDatagramStream], ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.subscribe +//go:noescape +func wasmimport_UDPSocketSubscribe(self0 uint32) (result0 uint32) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.unicast-hop-limit +//go:noescape +func wasmimport_UDPSocketUnicastHopLimit(self0 uint32, result *cm.Result[uint8, uint8, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [resource-drop]incoming-datagram-stream +//go:noescape +func wasmimport_IncomingDatagramStreamResourceDrop(self0 uint32) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]incoming-datagram-stream.receive +//go:noescape +func wasmimport_IncomingDatagramStreamReceive(self0 uint32, maxResults0 uint64, result *cm.Result[cm.List[IncomingDatagram], cm.List[IncomingDatagram], ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]incoming-datagram-stream.subscribe +//go:noescape +func wasmimport_IncomingDatagramStreamSubscribe(self0 uint32) (result0 uint32) + +//go:wasmimport wasi:sockets/udp@0.2.0 [resource-drop]outgoing-datagram-stream +//go:noescape +func wasmimport_OutgoingDatagramStreamResourceDrop(self0 uint32) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]outgoing-datagram-stream.check-send +//go:noescape +func wasmimport_OutgoingDatagramStreamCheckSend(self0 uint32, result *cm.Result[uint64, uint64, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]outgoing-datagram-stream.send +//go:noescape +func wasmimport_OutgoingDatagramStreamSend(self0 uint32, datagrams0 *OutgoingDatagram, datagrams1 uint32, result *cm.Result[uint64, uint64, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]outgoing-datagram-stream.subscribe +//go:noescape +func wasmimport_OutgoingDatagramStreamSubscribe(self0 uint32) (result0 uint32) diff --git a/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go b/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go index 268a8f6c89..f773fa48d0 100644 --- a/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go @@ -9,6 +9,31 @@ import ( "internal/wasi/sockets/v0.2.0/network" ) +// Pollable represents the imported type alias "wasi:sockets/udp@0.2.0#pollable". +// +// See [poll.Pollable] for more information. +type Pollable = poll.Pollable + +// Network represents the imported type alias "wasi:sockets/udp@0.2.0#network". +// +// See [network.Network] for more information. +type Network = network.Network + +// ErrorCode represents the type alias "wasi:sockets/udp@0.2.0#error-code". +// +// See [network.ErrorCode] for more information. +type ErrorCode = network.ErrorCode + +// IPSocketAddress represents the type alias "wasi:sockets/udp@0.2.0#ip-socket-address". +// +// See [network.IPSocketAddress] for more information. +type IPSocketAddress = network.IPSocketAddress + +// IPAddressFamily represents the type alias "wasi:sockets/udp@0.2.0#ip-address-family". +// +// See [network.IPAddressFamily] for more information. +type IPAddressFamily = network.IPAddressFamily + // IncomingDatagram represents the record "wasi:sockets/udp@0.2.0#incoming-datagram". // // A received datagram. @@ -18,6 +43,7 @@ import ( // remote-address: ip-socket-address, // } type IncomingDatagram struct { + _ cm.HostLayout // The payload. // // Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. @@ -29,7 +55,7 @@ type IncomingDatagram struct { // with, if any. // // Equivalent to the `src_addr` out parameter of `recvfrom`. - RemoteAddress network.IPSocketAddress + RemoteAddress IPSocketAddress } // OutgoingDatagram represents the record "wasi:sockets/udp@0.2.0#outgoing-datagram". @@ -41,6 +67,7 @@ type IncomingDatagram struct { // remote-address: option, // } type OutgoingDatagram struct { + _ cm.HostLayout // The payload. Data cm.List[uint8] @@ -53,7 +80,7 @@ type OutgoingDatagram struct { // // If this value is None, the send operation is equivalent to `send` in POSIX. Otherwise // it is equivalent to `sendto`. - RemoteAddress cm.Option[network.IPSocketAddress] + RemoteAddress cm.Option[IPSocketAddress] } // UDPSocket represents the imported resource "wasi:sockets/udp@0.2.0#udp-socket". @@ -74,10 +101,6 @@ func (self UDPSocket) ResourceDrop() { return } -//go:wasmimport wasi:sockets/udp@0.2.0 [resource-drop]udp-socket -//go:noescape -func wasmimport_UDPSocketResourceDrop(self0 uint32) - // AddressFamily represents the imported method "address-family". // // Whether this is a IPv4 or IPv6 socket. @@ -87,32 +110,24 @@ func wasmimport_UDPSocketResourceDrop(self0 uint32) // address-family: func() -> ip-address-family // //go:nosplit -func (self UDPSocket) AddressFamily() (result network.IPAddressFamily) { +func (self UDPSocket) AddressFamily() (result IPAddressFamily) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_UDPSocketAddressFamily((uint32)(self0)) result = (network.IPAddressFamily)((uint32)(result0)) return } -//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.address-family -//go:noescape -func wasmimport_UDPSocketAddressFamily(self0 uint32) (result0 uint32) - // FinishBind represents the imported method "finish-bind". // // finish-bind: func() -> result<_, error-code> // //go:nosplit -func (self UDPSocket) FinishBind() (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { +func (self UDPSocket) FinishBind() (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_UDPSocketFinishBind((uint32)(self0), &result) return } -//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.finish-bind -//go:noescape -func wasmimport_UDPSocketFinishBind(self0 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) - // LocalAddress represents the imported method "local-address". // // Get the current bound address. @@ -136,16 +151,12 @@ func wasmimport_UDPSocketFinishBind(self0 uint32, result *cm.Result[network.Erro // local-address: func() -> result // //go:nosplit -func (self UDPSocket) LocalAddress() (result cm.Result[IPSocketAddressShape, network.IPSocketAddress, network.ErrorCode]) { +func (self UDPSocket) LocalAddress() (result cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_UDPSocketLocalAddress((uint32)(self0), &result) return } -//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.local-address -//go:noescape -func wasmimport_UDPSocketLocalAddress(self0 uint32, result *cm.Result[IPSocketAddressShape, network.IPSocketAddress, network.ErrorCode]) - // ReceiveBufferSize represents the imported method "receive-buffer-size". // // The kernel buffer space reserved for sends/receives on this socket. @@ -164,16 +175,12 @@ func wasmimport_UDPSocketLocalAddress(self0 uint32, result *cm.Result[IPSocketAd // receive-buffer-size: func() -> result // //go:nosplit -func (self UDPSocket) ReceiveBufferSize() (result cm.Result[uint64, uint64, network.ErrorCode]) { +func (self UDPSocket) ReceiveBufferSize() (result cm.Result[uint64, uint64, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_UDPSocketReceiveBufferSize((uint32)(self0), &result) return } -//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.receive-buffer-size -//go:noescape -func wasmimport_UDPSocketReceiveBufferSize(self0 uint32, result *cm.Result[uint64, uint64, network.ErrorCode]) - // RemoteAddress represents the imported method "remote-address". // // Get the address the socket is currently streaming to. @@ -190,79 +197,59 @@ func wasmimport_UDPSocketReceiveBufferSize(self0 uint32, result *cm.Result[uint6 // remote-address: func() -> result // //go:nosplit -func (self UDPSocket) RemoteAddress() (result cm.Result[IPSocketAddressShape, network.IPSocketAddress, network.ErrorCode]) { +func (self UDPSocket) RemoteAddress() (result cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_UDPSocketRemoteAddress((uint32)(self0), &result) return } -//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.remote-address -//go:noescape -func wasmimport_UDPSocketRemoteAddress(self0 uint32, result *cm.Result[IPSocketAddressShape, network.IPSocketAddress, network.ErrorCode]) - // SendBufferSize represents the imported method "send-buffer-size". // // send-buffer-size: func() -> result // //go:nosplit -func (self UDPSocket) SendBufferSize() (result cm.Result[uint64, uint64, network.ErrorCode]) { +func (self UDPSocket) SendBufferSize() (result cm.Result[uint64, uint64, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_UDPSocketSendBufferSize((uint32)(self0), &result) return } -//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.send-buffer-size -//go:noescape -func wasmimport_UDPSocketSendBufferSize(self0 uint32, result *cm.Result[uint64, uint64, network.ErrorCode]) - // SetReceiveBufferSize represents the imported method "set-receive-buffer-size". // // set-receive-buffer-size: func(value: u64) -> result<_, error-code> // //go:nosplit -func (self UDPSocket) SetReceiveBufferSize(value uint64) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { +func (self UDPSocket) SetReceiveBufferSize(value uint64) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint64)(value) wasmimport_UDPSocketSetReceiveBufferSize((uint32)(self0), (uint64)(value0), &result) return } -//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.set-receive-buffer-size -//go:noescape -func wasmimport_UDPSocketSetReceiveBufferSize(self0 uint32, value0 uint64, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) - // SetSendBufferSize represents the imported method "set-send-buffer-size". // // set-send-buffer-size: func(value: u64) -> result<_, error-code> // //go:nosplit -func (self UDPSocket) SetSendBufferSize(value uint64) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { +func (self UDPSocket) SetSendBufferSize(value uint64) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint64)(value) wasmimport_UDPSocketSetSendBufferSize((uint32)(self0), (uint64)(value0), &result) return } -//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.set-send-buffer-size -//go:noescape -func wasmimport_UDPSocketSetSendBufferSize(self0 uint32, value0 uint64, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) - // SetUnicastHopLimit represents the imported method "set-unicast-hop-limit". // // set-unicast-hop-limit: func(value: u8) -> result<_, error-code> // //go:nosplit -func (self UDPSocket) SetUnicastHopLimit(value uint8) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { +func (self UDPSocket) SetUnicastHopLimit(value uint8) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) value0 := (uint32)(value) wasmimport_UDPSocketSetUnicastHopLimit((uint32)(self0), (uint32)(value0), &result) return } -//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.set-unicast-hop-limit -//go:noescape -func wasmimport_UDPSocketSetUnicastHopLimit(self0 uint32, value0 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) - // StartBind represents the imported method "start-bind". // // Bind the socket to a specific network on the provided IP address and port. @@ -301,7 +288,7 @@ func wasmimport_UDPSocketSetUnicastHopLimit(self0 uint32, value0 uint32, result // result<_, error-code> // //go:nosplit -func (self UDPSocket) StartBind(network_ network.Network, localAddress network.IPSocketAddress) (result cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) { +func (self UDPSocket) StartBind(network_ Network, localAddress IPSocketAddress) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) network0 := cm.Reinterpret[uint32](network_) localAddress0, localAddress1, localAddress2, localAddress3, localAddress4, localAddress5, localAddress6, localAddress7, localAddress8, localAddress9, localAddress10, localAddress11 := lower_IPSocketAddress(localAddress) @@ -309,10 +296,6 @@ func (self UDPSocket) StartBind(network_ network.Network, localAddress network.I return } -//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.start-bind -//go:noescape -func wasmimport_UDPSocketStartBind(self0 uint32, network0 uint32, localAddress0 uint32, localAddress1 uint32, localAddress2 uint32, localAddress3 uint32, localAddress4 uint32, localAddress5 uint32, localAddress6 uint32, localAddress7 uint32, localAddress8 uint32, localAddress9 uint32, localAddress10 uint32, localAddress11 uint32, result *cm.Result[network.ErrorCode, struct{}, network.ErrorCode]) - // Stream represents the imported method "stream". // // Set up inbound & outbound communication channels, optionally to a specific peer. @@ -370,17 +353,13 @@ func wasmimport_UDPSocketStartBind(self0 uint32, network0 uint32, localAddress0 // outgoing-datagram-stream>, error-code> // //go:nosplit -func (self UDPSocket) Stream(remoteAddress cm.Option[network.IPSocketAddress]) (result cm.Result[TupleIncomingDatagramStreamOutgoingDatagramStreamShape, cm.Tuple[IncomingDatagramStream, OutgoingDatagramStream], network.ErrorCode]) { +func (self UDPSocket) Stream(remoteAddress cm.Option[IPSocketAddress]) (result cm.Result[TupleIncomingDatagramStreamOutgoingDatagramStreamShape, cm.Tuple[IncomingDatagramStream, OutgoingDatagramStream], ErrorCode]) { self0 := cm.Reinterpret[uint32](self) remoteAddress0, remoteAddress1, remoteAddress2, remoteAddress3, remoteAddress4, remoteAddress5, remoteAddress6, remoteAddress7, remoteAddress8, remoteAddress9, remoteAddress10, remoteAddress11, remoteAddress12 := lower_OptionIPSocketAddress(remoteAddress) wasmimport_UDPSocketStream((uint32)(self0), (uint32)(remoteAddress0), (uint32)(remoteAddress1), (uint32)(remoteAddress2), (uint32)(remoteAddress3), (uint32)(remoteAddress4), (uint32)(remoteAddress5), (uint32)(remoteAddress6), (uint32)(remoteAddress7), (uint32)(remoteAddress8), (uint32)(remoteAddress9), (uint32)(remoteAddress10), (uint32)(remoteAddress11), (uint32)(remoteAddress12), &result) return } -//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.stream -//go:noescape -func wasmimport_UDPSocketStream(self0 uint32, remoteAddress0 uint32, remoteAddress1 uint32, remoteAddress2 uint32, remoteAddress3 uint32, remoteAddress4 uint32, remoteAddress5 uint32, remoteAddress6 uint32, remoteAddress7 uint32, remoteAddress8 uint32, remoteAddress9 uint32, remoteAddress10 uint32, remoteAddress11 uint32, remoteAddress12 uint32, result *cm.Result[TupleIncomingDatagramStreamOutgoingDatagramStreamShape, cm.Tuple[IncomingDatagramStream, OutgoingDatagramStream], network.ErrorCode]) - // Subscribe represents the imported method "subscribe". // // Create a `pollable` which will resolve once the socket is ready for I/O. @@ -391,17 +370,13 @@ func wasmimport_UDPSocketStream(self0 uint32, remoteAddress0 uint32, remoteAddre // subscribe: func() -> pollable // //go:nosplit -func (self UDPSocket) Subscribe() (result poll.Pollable) { +func (self UDPSocket) Subscribe() (result Pollable) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_UDPSocketSubscribe((uint32)(self0)) - result = cm.Reinterpret[poll.Pollable]((uint32)(result0)) + result = cm.Reinterpret[Pollable]((uint32)(result0)) return } -//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.subscribe -//go:noescape -func wasmimport_UDPSocketSubscribe(self0 uint32) (result0 uint32) - // UnicastHopLimit represents the imported method "unicast-hop-limit". // // Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. @@ -414,16 +389,12 @@ func wasmimport_UDPSocketSubscribe(self0 uint32) (result0 uint32) // unicast-hop-limit: func() -> result // //go:nosplit -func (self UDPSocket) UnicastHopLimit() (result cm.Result[uint8, uint8, network.ErrorCode]) { +func (self UDPSocket) UnicastHopLimit() (result cm.Result[uint8, uint8, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_UDPSocketUnicastHopLimit((uint32)(self0), &result) return } -//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.unicast-hop-limit -//go:noescape -func wasmimport_UDPSocketUnicastHopLimit(self0 uint32, result *cm.Result[uint8, uint8, network.ErrorCode]) - // IncomingDatagramStream represents the imported resource "wasi:sockets/udp@0.2.0#incoming-datagram-stream". // // resource incoming-datagram-stream @@ -440,10 +411,6 @@ func (self IncomingDatagramStream) ResourceDrop() { return } -//go:wasmimport wasi:sockets/udp@0.2.0 [resource-drop]incoming-datagram-stream -//go:noescape -func wasmimport_IncomingDatagramStreamResourceDrop(self0 uint32) - // Receive represents the imported method "receive". // // Receive messages on the socket. @@ -475,17 +442,13 @@ func wasmimport_IncomingDatagramStreamResourceDrop(self0 uint32) // receive: func(max-results: u64) -> result, error-code> // //go:nosplit -func (self IncomingDatagramStream) Receive(maxResults uint64) (result cm.Result[cm.List[IncomingDatagram], cm.List[IncomingDatagram], network.ErrorCode]) { +func (self IncomingDatagramStream) Receive(maxResults uint64) (result cm.Result[cm.List[IncomingDatagram], cm.List[IncomingDatagram], ErrorCode]) { self0 := cm.Reinterpret[uint32](self) maxResults0 := (uint64)(maxResults) wasmimport_IncomingDatagramStreamReceive((uint32)(self0), (uint64)(maxResults0), &result) return } -//go:wasmimport wasi:sockets/udp@0.2.0 [method]incoming-datagram-stream.receive -//go:noescape -func wasmimport_IncomingDatagramStreamReceive(self0 uint32, maxResults0 uint64, result *cm.Result[cm.List[IncomingDatagram], cm.List[IncomingDatagram], network.ErrorCode]) - // Subscribe represents the imported method "subscribe". // // Create a `pollable` which will resolve once the stream is ready to receive again. @@ -496,17 +459,13 @@ func wasmimport_IncomingDatagramStreamReceive(self0 uint32, maxResults0 uint64, // subscribe: func() -> pollable // //go:nosplit -func (self IncomingDatagramStream) Subscribe() (result poll.Pollable) { +func (self IncomingDatagramStream) Subscribe() (result Pollable) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_IncomingDatagramStreamSubscribe((uint32)(self0)) - result = cm.Reinterpret[poll.Pollable]((uint32)(result0)) + result = cm.Reinterpret[Pollable]((uint32)(result0)) return } -//go:wasmimport wasi:sockets/udp@0.2.0 [method]incoming-datagram-stream.subscribe -//go:noescape -func wasmimport_IncomingDatagramStreamSubscribe(self0 uint32) (result0 uint32) - // OutgoingDatagramStream represents the imported resource "wasi:sockets/udp@0.2.0#outgoing-datagram-stream". // // resource outgoing-datagram-stream @@ -523,10 +482,6 @@ func (self OutgoingDatagramStream) ResourceDrop() { return } -//go:wasmimport wasi:sockets/udp@0.2.0 [resource-drop]outgoing-datagram-stream -//go:noescape -func wasmimport_OutgoingDatagramStreamResourceDrop(self0 uint32) - // CheckSend represents the imported method "check-send". // // Check readiness for sending. This function never blocks. @@ -544,16 +499,12 @@ func wasmimport_OutgoingDatagramStreamResourceDrop(self0 uint32) // check-send: func() -> result // //go:nosplit -func (self OutgoingDatagramStream) CheckSend() (result cm.Result[uint64, uint64, network.ErrorCode]) { +func (self OutgoingDatagramStream) CheckSend() (result cm.Result[uint64, uint64, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_OutgoingDatagramStreamCheckSend((uint32)(self0), &result) return } -//go:wasmimport wasi:sockets/udp@0.2.0 [method]outgoing-datagram-stream.check-send -//go:noescape -func wasmimport_OutgoingDatagramStreamCheckSend(self0 uint32, result *cm.Result[uint64, uint64, network.ErrorCode]) - // Send represents the imported method "send". // // Send messages on the socket. @@ -608,17 +559,13 @@ func wasmimport_OutgoingDatagramStreamCheckSend(self0 uint32, result *cm.Result[ // send: func(datagrams: list) -> result // //go:nosplit -func (self OutgoingDatagramStream) Send(datagrams cm.List[OutgoingDatagram]) (result cm.Result[uint64, uint64, network.ErrorCode]) { +func (self OutgoingDatagramStream) Send(datagrams cm.List[OutgoingDatagram]) (result cm.Result[uint64, uint64, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) datagrams0, datagrams1 := cm.LowerList(datagrams) wasmimport_OutgoingDatagramStreamSend((uint32)(self0), (*OutgoingDatagram)(datagrams0), (uint32)(datagrams1), &result) return } -//go:wasmimport wasi:sockets/udp@0.2.0 [method]outgoing-datagram-stream.send -//go:noescape -func wasmimport_OutgoingDatagramStreamSend(self0 uint32, datagrams0 *OutgoingDatagram, datagrams1 uint32, result *cm.Result[uint64, uint64, network.ErrorCode]) - // Subscribe represents the imported method "subscribe". // // Create a `pollable` which will resolve once the stream is ready to send again. @@ -629,13 +576,9 @@ func wasmimport_OutgoingDatagramStreamSend(self0 uint32, datagrams0 *OutgoingDat // subscribe: func() -> pollable // //go:nosplit -func (self OutgoingDatagramStream) Subscribe() (result poll.Pollable) { +func (self OutgoingDatagramStream) Subscribe() (result Pollable) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_OutgoingDatagramStreamSubscribe((uint32)(self0)) - result = cm.Reinterpret[poll.Pollable]((uint32)(result0)) + result = cm.Reinterpret[Pollable]((uint32)(result0)) return } - -//go:wasmimport wasi:sockets/udp@0.2.0 [method]outgoing-datagram-stream.subscribe -//go:noescape -func wasmimport_OutgoingDatagramStreamSubscribe(self0 uint32) (result0 uint32) From 07d23c9d8338c877a7bc68299000307a539cf7bc Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sun, 6 Oct 2024 17:47:48 +0200 Subject: [PATCH 217/444] runtime: implement newcoro, coroswitch to support package iter --- src/runtime/coro.go | 31 +++++++++++++++++++++++++++++++ testdata/go1.23/main.go | 21 ++++++++++++++++++--- testdata/go1.23/out.txt | 10 ++++++++++ 3 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 src/runtime/coro.go diff --git a/src/runtime/coro.go b/src/runtime/coro.go new file mode 100644 index 0000000000..204a5c2bed --- /dev/null +++ b/src/runtime/coro.go @@ -0,0 +1,31 @@ +package runtime + +// A naive implementation of coroutines that supports +// package iter. + +type coro struct { + f func(*coro) + ch chan struct{} +} + +//go:linkname newcoro + +func newcoro(f func(*coro)) *coro { + c := &coro{ + ch: make(chan struct{}), + f: f, + } + go func() { + defer close(c.ch) + <-c.ch + f(c) + }() + return c +} + +//go:linkname coroswitch + +func coroswitch(c *coro) { + c.ch <- struct{}{} + <-c.ch +} diff --git a/testdata/go1.23/main.go b/testdata/go1.23/main.go index 01782d235e..2737f68be6 100644 --- a/testdata/go1.23/main.go +++ b/testdata/go1.23/main.go @@ -1,14 +1,29 @@ package main +import "iter" + func main() { testFuncRange(counter) + testIterPull(counter) + println("go1.23 has lift-off!") } -func testFuncRange(f func(yield func(int) bool)) { - for i := range f { +func testFuncRange(it iter.Seq[int]) { + for i := range it { + println(i) + } +} + +func testIterPull(it iter.Seq[int]) { + next, stop := iter.Pull(it) + defer stop() + for { + i, ok := next() + if !ok { + break + } println(i) } - println("go1.23 has lift-off!") } func counter(yield func(int) bool) { diff --git a/testdata/go1.23/out.txt b/testdata/go1.23/out.txt index aeeb7d40e1..78ac56642a 100644 --- a/testdata/go1.23/out.txt +++ b/testdata/go1.23/out.txt @@ -8,4 +8,14 @@ 3 2 1 +10 +9 +8 +7 +6 +5 +4 +3 +2 +1 go1.23 has lift-off! From 6016d0c73936026ad9f21a40e7deb04436d99ab9 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 3 Oct 2024 17:55:03 +0200 Subject: [PATCH 218/444] main_test: refactor output comparison into separate function This shouldn't affect anything, just make the code a bit better (especially for the next commit). --- main_test.go | 50 ++++++++++++++++++++++---------------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/main_test.go b/main_test.go index 3bbb31da3d..c471f76208 100644 --- a/main_test.go +++ b/main_test.go @@ -396,17 +396,13 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c // of the path. path := TESTDATA + "/" + name // Get the expected output for this test. - txtpath := path[:len(path)-3] + ".txt" + expectedOutputPath := path[:len(path)-3] + ".txt" pkgName := "./" + path if path[len(path)-1] == '/' { - txtpath = path + "out.txt" + expectedOutputPath = path + "out.txt" options.Directory = path pkgName = "." } - expected, err := os.ReadFile(txtpath) - if err != nil { - t.Fatal("could not read expected output file:", err) - } config, err := builder.NewConfig(&options) if err != nil { @@ -428,10 +424,7 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c return } - // putchar() prints CRLF, convert it to LF. - actual := bytes.Replace(stdout.Bytes(), []byte{'\r', '\n'}, []byte{'\n'}, -1) - expected = bytes.Replace(expected, []byte{'\r', '\n'}, []byte{'\n'}, -1) // for Windows - + actual := stdout.Bytes() if config.EmulatorName() == "simavr" { // Strip simavr log formatting. actual = bytes.Replace(actual, []byte{0x1b, '[', '3', '2', 'm'}, nil, -1) @@ -446,17 +439,12 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c } // Check whether the command ran successfully. - fail := false if err != nil { - t.Log("failed to run:", err) - fail = true - } else if !bytes.Equal(expected, actual) { - t.Logf("output did not match (expected %d bytes, got %d bytes):", len(expected), len(actual)) - t.Logf(string(Diff("expected", expected, "actual", actual))) - fail = true + t.Error("failed to run:", err) } + checkOutput(t, expectedOutputPath, actual) - if fail { + if t.Failed() { r := bufio.NewReader(bytes.NewReader(actual)) for { line, err := r.ReadString('\n') @@ -696,21 +684,27 @@ func TestWasmExport(t *testing.T) { // Check that the output matches the expected output. // (Skip this for wasm-unknown because it can't produce output). if !tc.noOutput { - expectedOutput, err := os.ReadFile("testdata/wasmexport.txt") - if err != nil { - t.Fatal("could not read output file:", err) - } - actual := output.Bytes() - expectedOutput = bytes.ReplaceAll(expectedOutput, []byte("\r\n"), []byte("\n")) - actual = bytes.ReplaceAll(actual, []byte("\r\n"), []byte("\n")) - if !bytes.Equal(actual, expectedOutput) { - t.Error(string(Diff("expected", expectedOutput, "actual", actual))) - } + checkOutput(t, "testdata/wasmexport.txt", output.Bytes()) } }) } } +// Check whether the output of a test equals the expected output. +func checkOutput(t *testing.T, filename string, actual []byte) { + expectedOutput, err := os.ReadFile(filename) + if err != nil { + t.Fatal("could not read output file:", err) + } + expectedOutput = bytes.ReplaceAll(expectedOutput, []byte("\r\n"), []byte("\n")) + actual = bytes.ReplaceAll(actual, []byte("\r\n"), []byte("\n")) + + if !bytes.Equal(actual, expectedOutput) { + t.Errorf("output did not match (expected %d bytes, got %d bytes):", len(expectedOutput), len(actual)) + t.Error(string(Diff("expected", expectedOutput, "actual", actual))) + } +} + func TestTest(t *testing.T) { t.Parallel() From 4ef5109a07afb895c28a5703b839f0c681062410 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 3 Oct 2024 17:58:48 +0200 Subject: [PATCH 219/444] wasm: add //go:wasmexport support to js/wasm This adds support for //go:wasmexport with `-target=wasm` (in the browser). This follows the //go:wasmexport proposal, meaning that blocking functions are not allowed. Both `-buildmode=default` and `-buildmode=c-shared` are supported. The latter allows calling exported functions after `go.run()` has returned. --- main_test.go | 40 ++++++++++++++++++++++++++++++++ src/runtime/runtime_wasm_js.go | 21 ++++++----------- src/runtime/runtime_wasmentry.go | 2 +- targets/wasm_exec.js | 19 +++++---------- testdata/wasmexport.js | 40 ++++++++++++++++++++++++++++++++ 5 files changed, 94 insertions(+), 28 deletions(-) create mode 100644 testdata/wasmexport.js diff --git a/main_test.go b/main_test.go index c471f76208..95fad9442b 100644 --- a/main_test.go +++ b/main_test.go @@ -690,6 +690,46 @@ func TestWasmExport(t *testing.T) { } } +// Test //go:wasmexport in JavaScript (using NodeJS). +func TestWasmExportJS(t *testing.T) { + type testCase struct { + name string + buildMode string + } + + tests := []testCase{ + {name: "default"}, + {name: "c-shared", buildMode: "c-shared"}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Build the wasm binary. + tmpdir := t.TempDir() + options := optionsFromTarget("wasm", sema) + options.BuildMode = tc.buildMode + buildConfig, err := builder.NewConfig(&options) + if err != nil { + t.Fatal(err) + } + result, err := builder.Build("testdata/wasmexport-noscheduler.go", ".wasm", tmpdir, buildConfig) + if err != nil { + t.Fatal("failed to build binary:", err) + } + + // Test the resulting binary using NodeJS. + output := &bytes.Buffer{} + cmd := exec.Command("node", "testdata/wasmexport.js", result.Binary, buildConfig.BuildMode()) + cmd.Stdout = output + cmd.Stderr = output + err = cmd.Run() + if err != nil { + t.Error("failed to run node:", err) + } + checkOutput(t, "testdata/wasmexport.txt", output.Bytes()) + }) + } +} + // Check whether the output of a test equals the expected output. func checkOutput(t *testing.T, filename string, actual []byte) { expectedOutput, err := os.ReadFile(filename) diff --git a/src/runtime/runtime_wasm_js.go b/src/runtime/runtime_wasm_js.go index 0b1aa5bc41..89898b554e 100644 --- a/src/runtime/runtime_wasm_js.go +++ b/src/runtime/runtime_wasm_js.go @@ -2,26 +2,15 @@ package runtime -import "unsafe" - type timeUnit float64 // time in milliseconds, just like Date.now() in JavaScript // wasmNested is used to detect scheduler nesting (WASM calls into JS calls back into WASM). // When this happens, we need to use a reduced version of the scheduler. +// +// TODO: this variable can probably be removed once //go:wasmexport is the only +// allowed way to export a wasm function (currently, //export also works). var wasmNested bool -//export _start -func _start() { - // These need to be initialized early so that the heap can be initialized. - heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) - heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) - - wasmNested = true - run() - __stdio_exit() - wasmNested = false -} - var handleEvent func() //go:linkname setEventHandler syscall/js.setEventHandler @@ -50,3 +39,7 @@ func sleepTicks(d timeUnit) //go:wasmimport gojs runtime.ticks func ticks() timeUnit + +func beforeExit() { + __stdio_exit() +} diff --git a/src/runtime/runtime_wasmentry.go b/src/runtime/runtime_wasmentry.go index 7bb1e1b44e..ff7b0c1198 100644 --- a/src/runtime/runtime_wasmentry.go +++ b/src/runtime/runtime_wasmentry.go @@ -1,4 +1,4 @@ -//go:build tinygo.wasm && !js +//go:build tinygo.wasm package runtime diff --git a/targets/wasm_exec.js b/targets/wasm_exec.js index 6902454409..c430cc2b23 100644 --- a/targets/wasm_exec.js +++ b/targets/wasm_exec.js @@ -466,20 +466,13 @@ this._idPool = []; // unused ids that have been garbage collected this.exited = false; // whether the Go program has exited - while (true) { - const callbackPromise = new Promise((resolve) => { - this._resolveCallbackPromise = () => { - if (this.exited) { - throw new Error("bad callback: Go program has already exited"); - } - setTimeout(resolve, 0); // make sure it is asynchronous - }; - }); + if (this._inst.exports._start) { this._inst.exports._start(); - if (this.exited) { - break; - } - await callbackPromise; + + // TODO: wait until the program exists. + await new Promise(() => {}); + } else { + this._inst.exports._initialize(); } } diff --git a/testdata/wasmexport.js b/testdata/wasmexport.js new file mode 100644 index 0000000000..c4a065125a --- /dev/null +++ b/testdata/wasmexport.js @@ -0,0 +1,40 @@ +require('../targets/wasm_exec.js'); + +function runTests() { + let testCall = (name, params, expected) => { + let result = go._inst.exports[name].apply(null, params); + if (result !== expected) { + console.error(`${name}(...${params}): expected result ${expected}, got ${result}`); + } + } + + // These are the same tests as in TestWasmExport. + testCall('hello', [], undefined); + testCall('add', [3, 5], 8); + testCall('add', [7, 9], 16); + testCall('add', [6, 1], 7); + testCall('reentrantCall', [2, 3], 5); + testCall('reentrantCall', [1, 8], 9); +} + +let go = new Go(); +go.importObject.tester = { + callOutside: (a, b) => { + return go._inst.exports.add(a, b); + }, + callTestMain: () => { + runTests(); + }, +}; +WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { + let buildMode = process.argv[3]; + if (buildMode === 'default') { + go.run(result.instance); + } else if (buildMode === 'c-shared') { + go.run(result.instance); + runTests(); + } +}).catch((err) => { + console.error(err); + process.exit(1); +}); From c6acaa981df06785d0b244da891c0440ce4dd9e0 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 10 Oct 2024 14:04:19 +0200 Subject: [PATCH 220/444] builder: remove environment variables when invoking Clang This is a problem on Guix, which sets C_INCLUDE_PATH that is not affected by `-nostdlibinc`. And therefore it affects the build in unintended ways. Removing all environmental variables fixes this issue, and perhaps also other issues caused by Clang being affected by environment variables. --- builder/commands.go | 12 ------------ builder/tools.go | 25 +++++++++++++++++++------ 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/builder/commands.go b/builder/commands.go index 932e9aceaf..d804ee1476 100644 --- a/builder/commands.go +++ b/builder/commands.go @@ -3,7 +3,6 @@ package builder import ( "errors" "fmt" - "os" "os/exec" "runtime" "strings" @@ -76,14 +75,3 @@ func LookupCommand(name string) (string, error) { } return "", errors.New("none of these commands were found in your $PATH: " + strings.Join(commands[name], " ")) } - -func execCommand(name string, args ...string) error { - name, err := LookupCommand(name) - if err != nil { - return err - } - cmd := exec.Command(name, args...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() -} diff --git a/builder/tools.go b/builder/tools.go index bd18aa09d2..b4087828f1 100644 --- a/builder/tools.go +++ b/builder/tools.go @@ -14,16 +14,29 @@ import ( // runCCompiler invokes a C compiler with the given arguments. func runCCompiler(flags ...string) error { + // Find the right command to run Clang. + var cmd *exec.Cmd if hasBuiltinTools { // Compile this with the internal Clang compiler. - cmd := exec.Command(os.Args[0], append([]string{"clang"}, flags...)...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() + cmd = exec.Command(os.Args[0], append([]string{"clang"}, flags...)...) + } else { + // Compile this with an external invocation of the Clang compiler. + name, err := LookupCommand("clang") + if err != nil { + return err + } + cmd = exec.Command(name, flags...) } - // Compile this with an external invocation of the Clang compiler. - return execCommand("clang", flags...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + // Make sure the command doesn't use any environmental variables. + // Most importantly, it should not use C_INCLUDE_PATH and the like. But + // removing all environmental variables also works. + cmd.Env = []string{} + + return cmd.Run() } // link invokes a linker with the given name and flags. From 45cc5b58cd8fa164c60ef72c934e9fe16f2ba2de Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 14 Oct 2024 11:22:29 +0200 Subject: [PATCH 221/444] runtime: disallow defer in interrupts This often doesn't work because there might not be a current task to push the defer frame to. It will instead show an unhelpful nil pointer dereference panic. We could make this work with a separate defer stack for interrupts (as if they were newly started goroutines) but that is difficult with multiple interrupts happening at the same time (we shouldn't jump to a previous interrupt in `panic()`!). So instead, disable defer altogether in interrupts and adjust panic/recover accordingly. --- src/runtime/panic.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/runtime/panic.go b/src/runtime/panic.go index 8cc62aeebe..abe4e7d279 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -2,6 +2,7 @@ package runtime import ( "internal/task" + "runtime/interrupt" "unsafe" ) @@ -50,7 +51,9 @@ func _panic(message interface{}) { if panicStrategy() == panicStrategyTrap { trap() } - if supportsRecover() { + // Note: recover is not supported inside interrupts. + // (This could be supported, like defer, but we currently don't). + if supportsRecover() && !interrupt.In() { frame := (*deferFrame)(task.Current().DeferFrame) if frame != nil { frame.PanicValue = message @@ -95,6 +98,12 @@ func runtimePanicAt(addr unsafe.Pointer, msg string) { //go:inline //go:nobounds func setupDeferFrame(frame *deferFrame, jumpSP unsafe.Pointer) { + if interrupt.In() { + // Defer is not currently allowed in interrupts. + // We could add support for this, but since defer might also allocate + // (especially in loops) it might not be a good idea anyway. + runtimePanicAt(returnAddress(0), "defer in interrupt") + } currentTask := task.Current() frame.Previous = (*deferFrame)(currentTask.DeferFrame) frame.JumpSP = jumpSP @@ -122,8 +131,11 @@ func destroyDeferFrame(frame *deferFrame) { // useParentFrame is set when the caller of runtime._recover has a defer frame // itself. In that case, recover() shouldn't check that frame but one frame up. func _recover(useParentFrame bool) interface{} { - if !supportsRecover() { - // Compiling without stack unwinding support, so make this a no-op. + if !supportsRecover() || interrupt.In() { + // Either we're compiling without stack unwinding support, or we're + // inside an interrupt where panic/recover is not supported. Either way, + // make this a no-op since panic() won't do any long jumps to a deferred + // function. return nil } // TODO: somehow check that recover() is called directly by a deferred From 539cc5c47bb1bc46bcaf663d8f963670a7fbb111 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Mon, 14 Oct 2024 18:59:07 +0200 Subject: [PATCH 222/444] transform: optimize range over []byte(string) Fixes #2700 --- transform/testdata/stringtobytes.ll | 8 ++++++++ transform/testdata/stringtobytes.out.ll | 5 +++++ transform/util.go | 9 ++++++--- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/transform/testdata/stringtobytes.ll b/transform/testdata/stringtobytes.ll index 06373a51ba..6d008d023b 100644 --- a/transform/testdata/stringtobytes.ll +++ b/transform/testdata/stringtobytes.ll @@ -5,6 +5,8 @@ target triple = "x86_64--linux" declare { ptr, i64, i64 } @runtime.stringToBytes(ptr, i64) +declare void @printByte(i8) + declare void @printSlice(ptr nocapture readonly, i64, i64) declare void @writeToSlice(ptr nocapture, i64, i64) @@ -17,6 +19,12 @@ entry: %2 = extractvalue { ptr, i64, i64 } %0, 1 %3 = extractvalue { ptr, i64, i64 } %0, 2 call fastcc void @printSlice(ptr %1, i64 %2, i64 %3) + + ; print(slice[0]) + %indexaddr.ptr1 = extractvalue { ptr, i64, i64 } %0, 0 + %4 = getelementptr inbounds i8, ptr %indexaddr.ptr1, i64 0 + %5 = load i8, ptr %4, align 1 + call fastcc void @printByte(i8 %5) ret void } diff --git a/transform/testdata/stringtobytes.out.ll b/transform/testdata/stringtobytes.out.ll index b33a17553b..b8909755aa 100644 --- a/transform/testdata/stringtobytes.out.ll +++ b/transform/testdata/stringtobytes.out.ll @@ -5,6 +5,8 @@ target triple = "x86_64--linux" declare { ptr, i64, i64 } @runtime.stringToBytes(ptr, i64) +declare void @printByte(i8) + declare void @printSlice(ptr nocapture readonly, i64, i64) declare void @writeToSlice(ptr nocapture, i64, i64) @@ -12,6 +14,9 @@ declare void @writeToSlice(ptr nocapture, i64, i64) define void @testReadOnly() { entry: call fastcc void @printSlice(ptr @str, i64 6, i64 6) + %0 = getelementptr inbounds i8, ptr @str, i64 0 + %1 = load i8, ptr %0, align 1 + call fastcc void @printByte(i8 %1) ret void } diff --git a/transform/util.go b/transform/util.go index 9923669d17..7a574d2fcb 100644 --- a/transform/util.go +++ b/transform/util.go @@ -38,15 +38,18 @@ func hasFlag(call, param llvm.Value, kind string) bool { func isReadOnly(value llvm.Value) bool { uses := getUses(value) for _, use := range uses { - if !use.IsAGetElementPtrInst().IsNil() { + switch { + case !use.IsAGetElementPtrInst().IsNil(): if !isReadOnly(use) { return false } - } else if !use.IsACallInst().IsNil() { + case !use.IsACallInst().IsNil(): if !hasFlag(use, value, "readonly") { return false } - } else { + case !use.IsALoadInst().IsNil(): + // Loads are read-only. + default: // Unknown instruction, might not be readonly. return false } From df724f5827e2b96d53a708d642ee52798506915c Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 12 Oct 2024 10:33:24 +0200 Subject: [PATCH 223/444] loader: don't panic when main package is not named 'main' This can in fact happen in practice, so return an actual error message instead. --- errors_test.go | 1 + loader/loader.go | 8 ++++++-- testdata/errors/invalidmain.go | 6 ++++++ 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 testdata/errors/invalidmain.go diff --git a/errors_test.go b/errors_test.go index 62d5af2cbb..5045840ff5 100644 --- a/errors_test.go +++ b/errors_test.go @@ -24,6 +24,7 @@ func TestErrors(t *testing.T) { {name: "cgo"}, {name: "compiler"}, {name: "interp"}, + {name: "invalidmain"}, {name: "linker-flashoverflow", target: "cortex-m-qemu"}, {name: "linker-ramoverflow", target: "cortex-m-qemu"}, {name: "linker-undefined", target: "darwin/arm64"}, diff --git a/loader/loader.go b/loader/loader.go index d10485707f..6786ee5335 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -418,8 +418,12 @@ func (p *Package) Check() error { packageName := p.ImportPath if p == p.program.MainPkg() { if p.Name != "main" { - // Sanity check. Should not ever trigger. - panic("expected main package to have name 'main'") + return Errors{p, []error{ + scanner.Error{ + Pos: p.program.fset.Position(p.Files[0].Name.Pos()), + Msg: fmt.Sprintf("expected main package to have name \"main\", not %#v", p.Name), + }, + }} } packageName = "main" } diff --git a/testdata/errors/invalidmain.go b/testdata/errors/invalidmain.go new file mode 100644 index 0000000000..a86e32c8dd --- /dev/null +++ b/testdata/errors/invalidmain.go @@ -0,0 +1,6 @@ +// some comment to move the first line + +package foobar + +// ERROR: # command-line-arguments +// ERROR: invalidmain.go:3:9: expected main package to have name "main", not "foobar" From 2690b243ea0a47a52fd2fbaea95cdbe4e6f01bb8 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 12 Oct 2024 10:50:34 +0200 Subject: [PATCH 224/444] main: make sure typecheck errors are correctly reported --- errors_test.go | 1 + testdata/errors/invalidname.go | 6 ++++++ testdata/errors/invalidname/invalidname.go | 3 +++ 3 files changed, 10 insertions(+) create mode 100644 testdata/errors/invalidname.go create mode 100644 testdata/errors/invalidname/invalidname.go diff --git a/errors_test.go b/errors_test.go index 5045840ff5..fe118616e3 100644 --- a/errors_test.go +++ b/errors_test.go @@ -25,6 +25,7 @@ func TestErrors(t *testing.T) { {name: "compiler"}, {name: "interp"}, {name: "invalidmain"}, + {name: "invalidname"}, {name: "linker-flashoverflow", target: "cortex-m-qemu"}, {name: "linker-ramoverflow", target: "cortex-m-qemu"}, {name: "linker-undefined", target: "darwin/arm64"}, diff --git a/testdata/errors/invalidname.go b/testdata/errors/invalidname.go new file mode 100644 index 0000000000..9a470b0d8d --- /dev/null +++ b/testdata/errors/invalidname.go @@ -0,0 +1,6 @@ +package main + +import _ "github.com/tinygo-org/tinygo/testdata/errors/invalidname" + +// ERROR: # github.com/tinygo-org/tinygo/testdata/errors/invalidname +// ERROR: invalidname{{[\\/]}}invalidname.go:3:9: invalid package name _ diff --git a/testdata/errors/invalidname/invalidname.go b/testdata/errors/invalidname/invalidname.go new file mode 100644 index 0000000000..c75242244e --- /dev/null +++ b/testdata/errors/invalidname/invalidname.go @@ -0,0 +1,3 @@ +// some comment to move the 'package' line + +package _ From 9583439be46fed7e5a782b384f65bb27788ba487 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 12 Oct 2024 10:54:22 +0200 Subject: [PATCH 225/444] loader: make sure we always return an error even without type errors This issue was originally reported here: https://github.com/NixOS/nixpkgs/pull/341170#issuecomment-2359237471 The fix here isn't a great fix, it turns the error message from this: # runtime/interrupt into this: # runtime/interrupt package requires newer Go version go1.23 ...so not great, because it doesn't show the real error message (which is that TinyGo wasn't compiled with the right Go version). But at least it gives a hint in the right direction. It's difficult to test for this specific case, so I've left out testing in this case (boo!) --- loader/loader.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/loader/loader.go b/loader/loader.go index 6786ee5335..f3ffa86144 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -432,7 +432,15 @@ func (p *Package) Check() error { if err, ok := err.(Errors); ok { return err } - return Errors{p, typeErrors} + if len(typeErrors) != 0 { + // Got type errors, so return them. + return Errors{p, typeErrors} + } + // This can happen in some weird cases. + // The only case I know is when compiling a Go 1.23 program, with a + // TinyGo version that supports Go 1.23 but is compiled using Go 1.22. + // So this should be pretty rare. + return Errors{p, []error{err}} } p.Pkg = typesPkg From ac5f84e3d7b837643507fbd2b5af44b81a61af2e Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 12 Oct 2024 11:10:21 +0200 Subject: [PATCH 226/444] builder: check for Go toolchain version used to compile TinyGo This shows a much better error message for issues like this one: https://github.com/NixOS/nixpkgs/pull/341170#issuecomment-2359237471 The new error message would be: cannot compile with Go toolchain version go1.23 (TinyGo was built using toolchain version go1.21.4) --- builder/config.go | 26 ++++++++++++++++++++++---- goenv/version.go | 12 +++++++++--- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/builder/config.go b/builder/config.go index 40c6f0c9a7..8db8ff8fe7 100644 --- a/builder/config.go +++ b/builder/config.go @@ -2,6 +2,7 @@ package builder import ( "fmt" + "runtime" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/goenv" @@ -23,20 +24,37 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) { spec.OpenOCDCommands = options.OpenOCDCommands } - major, minor, err := goenv.GetGorootVersion() + // Version range supported by TinyGo. + const minorMin = 19 + const minorMax = 23 + + // Check that we support this Go toolchain version. + gorootMajor, gorootMinor, err := goenv.GetGorootVersion() if err != nil { return nil, err } - if major != 1 || minor < 19 || minor > 23 { + if gorootMajor != 1 || gorootMinor < minorMin || gorootMinor > minorMax { // 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.19 through 1.23, got go%d.%d", major, minor) + return nil, fmt.Errorf("requires go version 1.19 through 1.23, got go%d.%d", gorootMajor, gorootMinor) + } + + // Check that the Go toolchain version isn't too new, if we haven't been + // compiled with the latest Go version. + // This may be a bit too aggressive: if the newer version doesn't change the + // Go language we will most likely be able to compile it. + buildMajor, buildMinor, err := goenv.Parse(runtime.Version()) + if err != nil { + return nil, err + } + if buildMajor != 1 || buildMinor < gorootMinor { + return nil, fmt.Errorf("cannot compile with Go toolchain version go%d.%d (TinyGo was built using toolchain version %s)", gorootMajor, gorootMinor, runtime.Version()) } return &compileopts.Config{ Options: options, Target: spec, - GoMinorVersion: minor, + GoMinorVersion: gorootMinor, TestConfig: options.TestConfig, }, nil } diff --git a/goenv/version.go b/goenv/version.go index 1db5a630af..cdfa278bd5 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -34,19 +34,25 @@ func GetGorootVersion() (major, minor int, err error) { if err != nil { return 0, 0, err } + return Parse(s) +} - if s == "" || s[:2] != "go" { +// Parse parses the Go version (like "go1.3.2") in the parameter and return the +// major and minor version: 1 and 3 in this example. If there is an error, (0, +// 0) and an error will be returned. +func Parse(version string) (major, minor int, err error) { + if version == "" || version[:2] != "go" { return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix") } - parts := strings.Split(s[2:], ".") + parts := strings.Split(version[2:], ".") if len(parts) < 2 { return 0, 0, errors.New("could not parse Go version: version has less than two parts") } // Ignore the errors, we don't really handle errors here anyway. var trailing string - n, err := fmt.Sscanf(s, "go%d.%d%s", &major, &minor, &trailing) + n, err := fmt.Sscanf(version, "go%d.%d%s", &major, &minor, &trailing) if n == 2 && err == io.EOF { // Means there were no trailing characters (i.e., not an alpha/beta) err = nil From 01dac8ba8e3efb801f560987bfe11b7882c342ca Mon Sep 17 00:00:00 2001 From: leongross Date: Fri, 11 Oct 2024 15:16:42 +0200 Subject: [PATCH 227/444] os/file_unix: add runtime function net.NewFile stub Signed-off-by: leongross --- src/os/file_unix.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/os/file_unix.go b/src/os/file_unix.go index efbd8ef672..76d35a62cc 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -12,6 +12,7 @@ package os import ( "io" "syscall" + _ "unsafe" ) const DevNull = "/dev/null" @@ -223,3 +224,17 @@ func newUnixDirent(parent, name string, typ FileMode) (DirEntry, error) { ude.info = info return ude, nil } + +// Since internal/poll is not available, we need to stub this out. +// Big go requires the option to add the fd to the polling system. +// +//go:linkname net_newUnixFile net.newUnixFile +func net_newUnixFile(fd int, name string) *File { + if fd < 0 { + panic("invalid FD") + } + + // see src/os/file_unix.go:162 newFile for the original implementation. + // return newFile(fd, name, kindSock, true) + return NewFile(uintptr(fd), name) +} From 40c9c66c1d6d758157fbc8163cc48e8cdecd85b9 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Fri, 18 Oct 2024 08:00:42 -0700 Subject: [PATCH 228/444] compiler: mark stringFromBytes as nocapture/readonly to help escape analysis Fixes #4525 --- compiler/symbol.go | 3 +++ transform/testdata/allocs2.go | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/compiler/symbol.go b/compiler/symbol.go index c2007cfd41..5447e0dba8 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -172,6 +172,9 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)) llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) + case "runtime.stringFromBytes": + llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) + llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)) case "runtime.trackPointer": // This function is necessary for tracking pointers on the stack in a // portable way (see gc_stack_portable.go). Indicate to the optimizer diff --git a/transform/testdata/allocs2.go b/transform/testdata/allocs2.go index 3b08fbc9e4..13c460231d 100644 --- a/transform/testdata/allocs2.go +++ b/transform/testdata/allocs2.go @@ -49,6 +49,11 @@ func main() { n4 = n5 }() println(n4, n5) + + // This shouldn't escape. + var buf [32]byte + s := string(buf[:]) + println(len(s)) } func derefInt(x *int) int { From 951e50c06fc617e9a45caddc62b76b88f86200a2 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Fri, 18 Oct 2024 12:17:38 -0700 Subject: [PATCH 229/444] compiler: mark stringFromRunes as nocapture/readonly --- compiler/symbol.go | 3 +++ transform/testdata/allocs2.go | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/compiler/symbol.go b/compiler/symbol.go index 5447e0dba8..e175bdd78d 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -175,6 +175,9 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) case "runtime.stringFromBytes": llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)) + case "runtime.stringFromRunes": + llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) + llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)) case "runtime.trackPointer": // This function is necessary for tracking pointers on the stack in a // portable way (see gc_stack_portable.go). Indicate to the optimizer diff --git a/transform/testdata/allocs2.go b/transform/testdata/allocs2.go index 13c460231d..299df5b213 100644 --- a/transform/testdata/allocs2.go +++ b/transform/testdata/allocs2.go @@ -54,6 +54,10 @@ func main() { var buf [32]byte s := string(buf[:]) println(len(s)) + + var rbuf [5]rune + s = string(rbuf[:]) + println(s) } func derefInt(x *int) int { From 5e3c816373b831897a152e60ed858d811ead4bbf Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 18 Oct 2024 12:43:26 +0200 Subject: [PATCH 230/444] ci: use macos-13 instead of macos-12 for amd64 builds The macos-12 runner is being deprecated, so we have to switch to a new runner: https://github.com/actions/runner-images/issues/10721 The next one is macos-13, which is still amd64. --- .github/workflows/build-macos.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 5fef339dd7..7fcf46f379 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -16,11 +16,11 @@ jobs: name: build-macos strategy: matrix: - # macos-12: amd64 (oldest supported version as of 05-02-2024) + # macos-13: amd64 (oldest supported version as of 18-10-2024) # macos-14: arm64 (oldest arm64 version) - os: [macos-12, macos-14] + os: [macos-13, macos-14] include: - - os: macos-12 + - os: macos-13 goarch: amd64 - os: macos-14 goarch: arm64 From cd2bb8333d859a311d8ff9e94cb4e0fade8f65b3 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 18 Oct 2024 12:29:28 +0200 Subject: [PATCH 231/444] wasm: add test for js.FuncOf While there are some browser tests, Node.js is just a lot better for testing this kind of stuff because it's much faster and we don't need a browser for this. --- main_test.go | 27 +++++++++++++++++++++++++++ testdata/wasmfunc.go | 17 +++++++++++++++++ testdata/wasmfunc.js | 21 +++++++++++++++++++++ testdata/wasmfunc.txt | 7 +++++++ 4 files changed, 72 insertions(+) create mode 100644 testdata/wasmfunc.go create mode 100644 testdata/wasmfunc.js create mode 100644 testdata/wasmfunc.txt diff --git a/main_test.go b/main_test.go index 95fad9442b..93da107366 100644 --- a/main_test.go +++ b/main_test.go @@ -690,6 +690,33 @@ func TestWasmExport(t *testing.T) { } } +// Test js.FuncOf (for syscall/js). +// This test might be extended in the future to cover more cases in syscall/js. +func TestWasmFuncOf(t *testing.T) { + // Build the wasm binary. + tmpdir := t.TempDir() + options := optionsFromTarget("wasm", sema) + buildConfig, err := builder.NewConfig(&options) + if err != nil { + t.Fatal(err) + } + result, err := builder.Build("testdata/wasmfunc.go", ".wasm", tmpdir, buildConfig) + if err != nil { + t.Fatal("failed to build binary:", err) + } + + // Test the resulting binary using NodeJS. + output := &bytes.Buffer{} + cmd := exec.Command("node", "testdata/wasmfunc.js", result.Binary, buildConfig.BuildMode()) + cmd.Stdout = output + cmd.Stderr = output + err = cmd.Run() + if err != nil { + t.Error("failed to run node:", err) + } + checkOutput(t, "testdata/wasmfunc.txt", output.Bytes()) +} + // Test //go:wasmexport in JavaScript (using NodeJS). func TestWasmExportJS(t *testing.T) { type testCase struct { diff --git a/testdata/wasmfunc.go b/testdata/wasmfunc.go new file mode 100644 index 0000000000..9d0d690e4f --- /dev/null +++ b/testdata/wasmfunc.go @@ -0,0 +1,17 @@ +package main + +import "syscall/js" + +func main() { + js.Global().Call("setCallback", js.FuncOf(func(this js.Value, args []js.Value) any { + println("inside callback! parameters:") + sum := 0 + for _, value := range args { + n := value.Int() + println(" parameter:", n) + sum += n + } + return sum + })) + js.Global().Call("callCallback") +} diff --git a/testdata/wasmfunc.js b/testdata/wasmfunc.js new file mode 100644 index 0000000000..3b1831ee4c --- /dev/null +++ b/testdata/wasmfunc.js @@ -0,0 +1,21 @@ +require('../targets/wasm_exec.js'); + +var callback; + +global.setCallback = (cb) => { + callback = cb; +}; + +global.callCallback = () => { + console.log('calling callback!'); + let result = callback(1, 2, 3, 4); + console.log('result from callback:', result); +}; + +let go = new Go(); +WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { + go.run(result.instance); +}).catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/testdata/wasmfunc.txt b/testdata/wasmfunc.txt new file mode 100644 index 0000000000..be41eba3c6 --- /dev/null +++ b/testdata/wasmfunc.txt @@ -0,0 +1,7 @@ +calling callback! +inside callback! parameters: + parameter: 1 + parameter: 2 + parameter: 3 + parameter: 4 +result from callback: 10 From 2f9e39e21c8d3ff2f1ee991bd9492da2e37162c3 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 18 Oct 2024 12:31:11 +0200 Subject: [PATCH 232/444] runtime: remove minSched hack for wasm I am not entirely sure what it's doing (it seems related to js.FuncOf), but tests still seem to pass when this code is removed. So let's remove it. --- src/runtime/runtime_wasm_js.go | 7 ------- src/runtime/runtime_wasm_js_scheduler.go | 14 -------------- src/runtime/scheduler.go | 18 ------------------ 3 files changed, 39 deletions(-) diff --git a/src/runtime/runtime_wasm_js.go b/src/runtime/runtime_wasm_js.go index 89898b554e..b49ffd15d6 100644 --- a/src/runtime/runtime_wasm_js.go +++ b/src/runtime/runtime_wasm_js.go @@ -4,13 +4,6 @@ package runtime type timeUnit float64 // time in milliseconds, just like Date.now() in JavaScript -// wasmNested is used to detect scheduler nesting (WASM calls into JS calls back into WASM). -// When this happens, we need to use a reduced version of the scheduler. -// -// TODO: this variable can probably be removed once //go:wasmexport is the only -// allowed way to export a wasm function (currently, //export also works). -var wasmNested bool - var handleEvent func() //go:linkname setEventHandler syscall/js.setEventHandler diff --git a/src/runtime/runtime_wasm_js_scheduler.go b/src/runtime/runtime_wasm_js_scheduler.go index 94018336e4..9fd8c45541 100644 --- a/src/runtime/runtime_wasm_js_scheduler.go +++ b/src/runtime/runtime_wasm_js_scheduler.go @@ -8,24 +8,10 @@ func resume() { handleEvent() }() - if wasmNested { - minSched() - return - } - - wasmNested = true scheduler(false) - wasmNested = false } //export go_scheduler func go_scheduler() { - if wasmNested { - minSched() - return - } - - wasmNested = true scheduler(false) - wasmNested = false } diff --git a/src/runtime/scheduler.go b/src/runtime/scheduler.go index 2f22876527..3f726a0641 100644 --- a/src/runtime/scheduler.go +++ b/src/runtime/scheduler.go @@ -247,24 +247,6 @@ func scheduler(returnAtDeadlock bool) { } } -// This horrible hack exists to make WASM work properly. -// When a WASM program calls into JS which calls back into WASM, the event with which we called back in needs to be handled before returning. -// Thus there are two copies of the scheduler running at once. -// This is a reduced version of the scheduler which does not deal with the timer queue (that is a problem for the outer scheduler). -func minSched() { - scheduleLog("start nested scheduler") - for !schedulerDone { - t := runqueue.Pop() - if t == nil { - break - } - - scheduleLogTask(" run:", t) - t.Resume() - } - scheduleLog("stop nested scheduler") -} - func Gosched() { runqueue.Push(task.Current()) task.Pause() From 0dfa57ea042f935183746f98dc6751d8e38d88d4 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Fri, 18 Oct 2024 10:25:04 -0700 Subject: [PATCH 233/444] runtime: use unsafe.Slice in tsip code --- src/runtime/memhash_tsip.go | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/src/runtime/memhash_tsip.go b/src/runtime/memhash_tsip.go index a8829e0527..607055bd50 100644 --- a/src/runtime/memhash_tsip.go +++ b/src/runtime/memhash_tsip.go @@ -15,23 +15,6 @@ import ( "unsafe" ) -func ptrToSlice(ptr unsafe.Pointer, n uintptr) []byte { - var p []byte - - type _bslice struct { - ptr *byte - len uintptr - cap uintptr - } - - pslice := (*_bslice)(unsafe.Pointer(&p)) - pslice.ptr = (*byte)(ptr) - pslice.cap = n - pslice.len = n - - return p -} - type sip struct { v0, v1 uint64 } @@ -45,8 +28,7 @@ func (s *sip) round() { } func hash64(ptr unsafe.Pointer, n uintptr, seed uintptr) uint64 { - - p := ptrToSlice(ptr, n) + p := unsafe.Slice((*byte)(ptr), n) k0 := uint64(seed) k1 := uint64(0) @@ -117,9 +99,7 @@ func (s *sip32) round() { } func hash32(ptr unsafe.Pointer, n uintptr, seed uintptr) uint32 { - // TODO(dgryski): replace this messiness with unsafe.Slice when we can use 1.17 features - - p := ptrToSlice(ptr, n) + p := unsafe.Slice((*byte)(ptr), n) k0 := uint32(seed) k1 := uint32(seed >> 32) From 23d3a31107b16fddf16e8cea623caea35e05404c Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Fri, 18 Oct 2024 10:26:26 -0700 Subject: [PATCH 234/444] runtime: use unsafe.Slice for leveldb code --- src/runtime/memhash_leveldb.go | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/runtime/memhash_leveldb.go b/src/runtime/memhash_leveldb.go index f57c9cf93e..2e7557bb3f 100644 --- a/src/runtime/memhash_leveldb.go +++ b/src/runtime/memhash_leveldb.go @@ -16,23 +16,6 @@ import ( "unsafe" ) -func ptrToSlice(ptr unsafe.Pointer, n uintptr) []byte { - var p []byte - - type _bslice struct { - ptr *byte - len uintptr - cap uintptr - } - - pslice := (*_bslice)(unsafe.Pointer(&p)) - pslice.ptr = (*byte)(ptr) - pslice.cap = n - pslice.len = n - - return p -} - // leveldb hash func hash32(ptr unsafe.Pointer, n, seed uintptr) uint32 { @@ -41,7 +24,7 @@ func hash32(ptr unsafe.Pointer, n, seed uintptr) uint32 { m = 0xc6a4a793 ) - b := ptrToSlice(ptr, n) + b := unsafe.Slice((*byte)(ptr), n) h := uint32(lseed^seed) ^ uint32(uint(len(b))*uint(m)) From a191326ea8c2fd054d46e278731e1ec99a098ad2 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Mon, 21 Oct 2024 16:01:59 +0200 Subject: [PATCH 235/444] goenv: parse patch version, add func Compare to compare two Go version strings (#4536) goenv: parse patch version, add func Compare to compare two Go version strings * Parse tests * add Compare function to compare two Go version strings * goenv, builder: parse patch version in Go version string --- builder/config.go | 2 +- goenv/version.go | 51 +++++++++++++++++++++++++------ goenv/version_test.go | 71 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 11 deletions(-) create mode 100644 goenv/version_test.go diff --git a/builder/config.go b/builder/config.go index 8db8ff8fe7..d1d0a2713b 100644 --- a/builder/config.go +++ b/builder/config.go @@ -43,7 +43,7 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) { // compiled with the latest Go version. // This may be a bit too aggressive: if the newer version doesn't change the // Go language we will most likely be able to compile it. - buildMajor, buildMinor, err := goenv.Parse(runtime.Version()) + buildMajor, buildMinor, _, err := goenv.Parse(runtime.Version()) if err != nil { return nil, err } diff --git a/goenv/version.go b/goenv/version.go index cdfa278bd5..9cc463402e 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -34,33 +34,64 @@ func GetGorootVersion() (major, minor int, err error) { if err != nil { return 0, 0, err } - return Parse(s) + major, minor, _, err = Parse(s) + return major, minor, err } // Parse parses the Go version (like "go1.3.2") in the parameter and return the -// major and minor version: 1 and 3 in this example. If there is an error, (0, -// 0) and an error will be returned. -func Parse(version string) (major, minor int, err error) { +// major, minor, and patch version: 1, 3, and 2 in this example. +// If there is an error, (0, 0, 0) and an error will be returned. +func Parse(version string) (major, minor, patch int, err error) { if version == "" || version[:2] != "go" { - return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix") + return 0, 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix") } parts := strings.Split(version[2:], ".") if len(parts) < 2 { - return 0, 0, errors.New("could not parse Go version: version has less than two parts") + return 0, 0, 0, errors.New("could not parse Go version: version has less than two parts") } // Ignore the errors, we don't really handle errors here anyway. var trailing string - n, err := fmt.Sscanf(version, "go%d.%d%s", &major, &minor, &trailing) - if n == 2 && err == io.EOF { + n, err := fmt.Sscanf(version, "go%d.%d.%d%s", &major, &minor, &patch, &trailing) + if n == 2 { + n, err = fmt.Sscanf(version, "go%d.%d%s", &major, &minor, &trailing) + } + if n >= 2 && err == io.EOF { // Means there were no trailing characters (i.e., not an alpha/beta) err = nil } if err != nil { - return 0, 0, fmt.Errorf("failed to parse version: %s", err) + return 0, 0, 0, fmt.Errorf("failed to parse version: %s", err) + } + + return major, minor, patch, nil +} + +// Compare compares two Go version strings. +// The result will be 0 if a == b, -1 if a < b, and +1 if a > b. +// If either a or b is not a valid Go version, it is treated as "go0.0" +// and compared lexicographically. +// See [Parse] for more information. +func Compare(a, b string) int { + aMajor, aMinor, aPatch, _ := Parse(a) + bMajor, bMinor, bPatch, _ := Parse(b) + switch { + case aMajor < bMajor: + return -1 + case aMajor > bMajor: + return +1 + case aMinor < bMinor: + return -1 + case aMinor > bMinor: + return +1 + case aPatch < bPatch: + return -1 + case aPatch > bPatch: + return +1 + default: + return strings.Compare(a, b) } - return } // GorootVersionString returns the version string as reported by the Go diff --git a/goenv/version_test.go b/goenv/version_test.go new file mode 100644 index 0000000000..1744d2b22f --- /dev/null +++ b/goenv/version_test.go @@ -0,0 +1,71 @@ +package goenv + +import "testing" + +func TestParse(t *testing.T) { + tests := []struct { + v string + major int + minor int + patch int + wantErr bool + }{ + {"", 0, 0, 0, true}, + {"go", 0, 0, 0, true}, + {"go1", 0, 0, 0, true}, + {"go.0", 0, 0, 0, true}, + {"go1.0", 1, 0, 0, false}, + {"go1.1", 1, 1, 0, false}, + {"go1.23", 1, 23, 0, false}, + {"go1.23.5", 1, 23, 5, false}, + {"go1.23.5-rc6", 1, 23, 5, false}, + {"go2.0", 2, 0, 0, false}, + {"go2.0.15", 2, 0, 15, false}, + } + for _, tt := range tests { + t.Run(tt.v, func(t *testing.T) { + major, minor, patch, err := Parse(tt.v) + if err == nil && tt.wantErr { + t.Errorf("Parse(%q): expected err != nil", tt.v) + } + if err != nil && !tt.wantErr { + t.Errorf("Parse(%q): expected err == nil", tt.v) + } + if major != tt.major || minor != tt.minor || patch != tt.patch { + t.Errorf("Parse(%q): expected %d, %d, %d, nil; got %d, %d, %d, %v", + tt.v, tt.major, tt.minor, tt.patch, major, minor, patch, err) + } + }) + } +} + +func TestCompare(t *testing.T) { + tests := []struct { + a string + b string + want int + }{ + {"", "", 0}, + {"go0", "go0", 0}, + {"go0", "go1", -1}, + {"go1", "go0", 1}, + {"go1", "go2", -1}, + {"go2", "go1", 1}, + {"go1.1", "go1.2", -1}, + {"go1.2", "go1.1", 1}, + {"go1.1.0", "go1.2.0", -1}, + {"go1.2.0", "go1.1.0", 1}, + {"go1.2.0", "go2.3.0", -1}, + {"go1.23.2", "go1.23.10", -1}, + {"go0.1.22", "go1.23.101", -1}, + } + for _, tt := range tests { + t.Run(tt.a+" "+tt.b, func(t *testing.T) { + got := Compare(tt.a, tt.b) + if got != tt.want { + t.Errorf("Compare(%q, %q): expected %d; got %d", + tt.a, tt.b, tt.want, got) + } + }) + } +} From bcfe751f62302cbb17f11236dc03775122ab6974 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 21 Oct 2024 12:01:04 +0200 Subject: [PATCH 236/444] fe310: support GPIO PinInput This is needed to support switching between input and output. --- src/machine/machine_fe310.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/machine/machine_fe310.go b/src/machine/machine_fe310.go index 37f3ee07e8..4a15ad76e1 100644 --- a/src/machine/machine_fe310.go +++ b/src/machine/machine_fe310.go @@ -26,6 +26,8 @@ const ( func (p Pin) Configure(config PinConfig) { sifive.GPIO0.INPUT_EN.SetBits(1 << uint8(p)) switch config.Mode { + case PinInput: + sifive.GPIO0.OUTPUT_EN.ClearBits(1 << uint8(p)) case PinOutput: sifive.GPIO0.OUTPUT_EN.SetBits(1 << uint8(p)) case PinPWM: From b2fbbeb771fab6eeafec00515940fe6d1d0614a7 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 21 Oct 2024 10:59:34 +0200 Subject: [PATCH 237/444] esp32c3: add smoke tests for a few boards These boards probably haven't been working since the addition of I2C, because they were missing some constants in the machine package. --- GNUmakefile | 6 ++++++ src/machine/board_esp32-c3-devkit-rust-1.go | 4 ++-- src/machine/board_esp32c3-12f.go | 6 ++++++ src/machine/board_makerfabs-esp32c3spi35.go | 8 ++++---- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 110ab5463f..75725d08c2 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -841,6 +841,12 @@ endif @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=xiao-esp32c3 examples/machinetest @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp32-c3-devkit-rust-1 examples/blinky1 + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp32c3-12f examples/blinky1 + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=makerfabs-esp32c3spi35 examples/machinetest + @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=maixbit examples/blinky1 diff --git a/src/machine/board_esp32-c3-devkit-rust-1.go b/src/machine/board_esp32-c3-devkit-rust-1.go index 3cba69d1bb..8e47269f95 100644 --- a/src/machine/board_esp32-c3-devkit-rust-1.go +++ b/src/machine/board_esp32-c3-devkit-rust-1.go @@ -64,8 +64,8 @@ const ( // I2C pins const ( - I2C_SCL_PIN = D8 - I2C_SDA_PIN = D10 + SCL_PIN = D8 + SDA_PIN = D10 ) // USBCDC pins diff --git a/src/machine/board_esp32c3-12f.go b/src/machine/board_esp32c3-12f.go index df28915a4f..f023bb9d61 100644 --- a/src/machine/board_esp32c3-12f.go +++ b/src/machine/board_esp32c3-12f.go @@ -46,3 +46,9 @@ const ( UART_TX_PIN = TXD UART_RX_PIN = RXD ) + +// I2C pins +const ( + SCL_PIN = NoPin + SDA_PIN = NoPin +) diff --git a/src/machine/board_makerfabs-esp32c3spi35.go b/src/machine/board_makerfabs-esp32c3spi35.go index 8e4401f5f8..6e4e6f4bef 100644 --- a/src/machine/board_makerfabs-esp32c3spi35.go +++ b/src/machine/board_makerfabs-esp32c3spi35.go @@ -64,8 +64,8 @@ const ( // Touchscreen pins const ( TS_CS_PIN = D0 - TS_SDA_PIN = I2C_SDA_PIN - TS_SCL_PIN = I2C_SCL_PIN + TS_SDA_PIN = SDA_PIN + TS_SCL_PIN = SCL_PIN ) // MicroSD pins @@ -90,8 +90,8 @@ const ( // I2C pins const ( - I2C_SDA_PIN = D2 - I2C_SCL_PIN = D3 + SDA_PIN = D2 + SCL_PIN = D3 ) // SPI pins From e615c253194aa6d64289ed4dffab9b1be8887e31 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 21 Oct 2024 11:59:09 +0200 Subject: [PATCH 238/444] targets: add WaveShare ESP-C3-32S-Kit I've had this board for a while now, but never added proper TinyGo support. So here is a PR to do just that. --- GNUmakefile | 2 ++ src/machine/board_esp-c3-32s-kit.go | 40 +++++++++++++++++++++++++++++ targets/esp-c3-32s-kit.json | 5 ++++ 3 files changed, 47 insertions(+) create mode 100644 src/machine/board_esp-c3-32s-kit.go create mode 100644 targets/esp-c3-32s-kit.json diff --git a/GNUmakefile b/GNUmakefile index 75725d08c2..7bace1f7c1 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -835,6 +835,8 @@ ifneq ($(XTENSA), 0) $(TINYGO) build -size short -o test.bin -target mch2022 examples/machinetest @$(MD5SUM) test.bin endif + $(TINYGO) build -size short -o test.bin -target=esp-c3-32s-kit examples/blinky1 + @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=qtpy-esp32c3 examples/machinetest @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=m5stamp-c3 examples/machinetest diff --git a/src/machine/board_esp-c3-32s-kit.go b/src/machine/board_esp-c3-32s-kit.go new file mode 100644 index 0000000000..09385aa3d8 --- /dev/null +++ b/src/machine/board_esp-c3-32s-kit.go @@ -0,0 +1,40 @@ +//go:build esp_c3_32s_kit + +package machine + +// See: +// * https://www.waveshare.com/w/upload/8/8f/Esp32-c3s_specification.pdf +// * https://www.waveshare.com/w/upload/4/46/Nodemcu-esp-c3-32s-kit-schematics.pdf + +// Digital Pins +const ( + IO0 = GPIO0 + IO1 = GPIO1 + IO2 = GPIO2 + IO3 = GPIO3 + IO4 = GPIO4 + IO5 = GPIO5 + IO6 = GPIO6 + IO7 = GPIO7 + IO8 = GPIO8 + IO9 = GPIO9 + IO18 = GPIO18 + IO19 = GPIO19 +) + +const ( + LED_RED = IO3 + LED_GREEN = IO4 + LED_BLUE = IO5 + + LED = LED_RED + + LED1 = LED_RED + LED2 = LED_GREEN +) + +// I2C pins +const ( + SDA_PIN = NoPin + SCL_PIN = NoPin +) diff --git a/targets/esp-c3-32s-kit.json b/targets/esp-c3-32s-kit.json new file mode 100644 index 0000000000..6f787e7dbb --- /dev/null +++ b/targets/esp-c3-32s-kit.json @@ -0,0 +1,5 @@ +{ + "inherits": ["esp32c3"], + "build-tags": ["esp_c3_32s_kit"], + "serial-port": ["1a86:7523"] +} From 3dcac3b5396dfd71f52f398d9951a17d992fa6ba Mon Sep 17 00:00:00 2001 From: Daniel Esteban Date: Mon, 14 Oct 2024 11:31:18 +0200 Subject: [PATCH 239/444] Add sponsor button to key repositories --- .github/FUNDING.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..869e436c3c --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +open_collective: tinygo From 24c11d4ba5f722ef646ae0e1ee06b90b2411590c Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Tue, 22 Oct 2024 18:05:04 +0200 Subject: [PATCH 240/444] compiler: conform to latest iteration of wasm types proposal (#4501) compiler: align with current wasm types proposal https://github.com/golang/go/issues/66984 - Remove int and uint as allowed types in params, results, pointers, or struct fields - Only allow small integers in pointers, arrays, or struct fields - enforce structs.HostLayout usage per wasm types proposal https://github.com/golang/go/issues/66984 - require go1.23 for structs.HostLayout - use an interface to check if GoVersion() exists This permits TinyGo to compile with Go 1.21. - use goenv.Compare instead of WantGoVersion - testdata/wasmexport: use int32 instead of int - compiler/testdata: add structs.HostLayout - compiler/testdata: improve tests for structs.HostLayout --- compiler/symbol.go | 35 ++++++++++++++++++------ compiler/testdata/errors.go | 44 +++++++++++++++++++++++++----- testdata/wasmexport-noscheduler.go | 6 ++-- testdata/wasmexport.go | 10 +++---- 4 files changed, 72 insertions(+), 23 deletions(-) diff --git a/compiler/symbol.go b/compiler/symbol.go index e175bdd78d..9b9b1d10e8 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/tinygo-org/tinygo/compiler/llvmutil" + "github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/loader" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" @@ -422,14 +423,14 @@ func (c *compilerContext) checkWasmImportExport(f *ssa.Function, pragma string) c.addError(f.Signature.Results().At(1).Pos(), fmt.Sprintf("%s: too many return values", pragma)) } else if f.Signature.Results().Len() == 1 { result := f.Signature.Results().At(0) - if !isValidWasmType(result.Type(), siteResult) { + if !c.isValidWasmType(result.Type(), siteResult) { c.addError(result.Pos(), fmt.Sprintf("%s: unsupported result type %s", pragma, result.Type().String())) } } for _, param := range f.Params { // Check whether the type is allowed. // Only a very limited number of types can be mapped to WebAssembly. - if !isValidWasmType(param.Type(), siteParam) { + if !c.isValidWasmType(param.Type(), siteParam) { c.addError(param.Pos(), fmt.Sprintf("%s: unsupported parameter type %s", pragma, param.Type().String())) } } @@ -442,13 +443,15 @@ func (c *compilerContext) checkWasmImportExport(f *ssa.Function, pragma string) // // This previously reflected the additional restrictions documented here: // https://github.com/golang/go/issues/59149 -func isValidWasmType(typ types.Type, site wasmSite) bool { +func (c *compilerContext) isValidWasmType(typ types.Type, site wasmSite) bool { switch typ := typ.Underlying().(type) { case *types.Basic: switch typ.Kind() { case types.Bool: return true - case types.Int, types.Uint, types.Int8, types.Uint8, types.Int16, types.Uint16, types.Int32, types.Uint32, types.Int64, types.Uint64: + case types.Int8, types.Uint8, types.Int16, types.Uint16: + return site == siteIndirect + case types.Int32, types.Uint32, types.Int64, types.Uint64: return true case types.Float32, types.Float64: return true @@ -459,19 +462,35 @@ func isValidWasmType(typ types.Type, site wasmSite) bool { return site == siteParam || site == siteIndirect } case *types.Array: - return site == siteIndirect && isValidWasmType(typ.Elem(), siteIndirect) + return site == siteIndirect && c.isValidWasmType(typ.Elem(), siteIndirect) case *types.Struct: if site != siteIndirect { return false } + // Structs with no fields do not need structs.HostLayout + if typ.NumFields() == 0 { + return true + } + hasHostLayout := true // default to true before detecting Go version + // (*types.Package).GoVersion added in go1.21 + if gv, ok := any(c.pkg).(interface{ GoVersion() string }); ok { + if goenv.Compare(gv.GoVersion(), "go1.23") >= 0 { + hasHostLayout = false // package structs added in go1.23 + } + } for i := 0; i < typ.NumFields(); i++ { - if !isValidWasmType(typ.Field(i).Type(), siteIndirect) { + ftyp := typ.Field(i).Type() + if ftyp.String() == "structs.HostLayout" { + hasHostLayout = true + continue + } + if !c.isValidWasmType(ftyp, siteIndirect) { return false } } - return true + return hasHostLayout case *types.Pointer: - return isValidWasmType(typ.Elem(), siteIndirect) + return c.isValidWasmType(typ.Elem(), siteIndirect) } return false } diff --git a/compiler/testdata/errors.go b/compiler/testdata/errors.go index 81e8a76a18..ae95b75234 100644 --- a/compiler/testdata/errors.go +++ b/compiler/testdata/errors.go @@ -1,6 +1,9 @@ package main -import "unsafe" +import ( + "structs" + "unsafe" +) //go:wasmimport modulename empty func empty() @@ -14,31 +17,37 @@ func implementation() { type Uint uint32 type S struct { + _ structs.HostLayout a [4]uint32 b uintptr - c int d float32 e float64 } //go:wasmimport modulename validparam -func validparam(a int32, b uint64, c float64, d unsafe.Pointer, e Uint, f uintptr, g string, h *int32, i *S) +func validparam(a int32, b uint64, c float64, d unsafe.Pointer, e Uint, f uintptr, g string, h *int32, i *S, j *struct{}, k *[8]uint8) // ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type [4]uint32 // ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type []byte // ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type struct{a int} // ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type chan struct{} // ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type func() +// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type int +// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type uint +// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type [8]int // //go:wasmimport modulename invalidparam -func invalidparam(a [4]uint32, b []byte, c struct{ a int }, d chan struct{}, e func()) +func invalidparam(a [4]uint32, b []byte, c struct{ a int }, d chan struct{}, e func(), f int, g uint, h [8]int) + +// ERROR: //go:wasmimport modulename invalidparam_no_hostlayout: unsupported parameter type *struct{int} +// ERROR: //go:wasmimport modulename invalidparam_no_hostlayout: unsupported parameter type *struct{string} +// +//go:wasmimport modulename invalidparam_no_hostlayout +func invalidparam_no_hostlayout(a *struct{ int }, b *struct{ string }) //go:wasmimport modulename validreturn_int32 func validreturn_int32() int32 -//go:wasmimport modulename validreturn_int -func validreturn_int() int - //go:wasmimport modulename validreturn_ptr_int32 func validreturn_ptr_int32() *int32 @@ -48,6 +57,12 @@ func validreturn_ptr_string() *string //go:wasmimport modulename validreturn_ptr_struct func validreturn_ptr_struct() *S +//go:wasmimport modulename validreturn_ptr_struct +func validreturn_ptr_empty_struct() *struct{} + +//go:wasmimport modulename validreturn_ptr_array +func validreturn_ptr_array() *[8]uint8 + //go:wasmimport modulename validreturn_unsafe_pointer func validreturn_unsafe_pointer() unsafe.Pointer @@ -56,11 +71,26 @@ func validreturn_unsafe_pointer() unsafe.Pointer //go:wasmimport modulename manyreturns func manyreturns() (int32, int32) +// ERROR: //go:wasmimport modulename invalidreturn_int: unsupported result type int +// +//go:wasmimport modulename invalidreturn_int +func invalidreturn_int() int + +// ERROR: //go:wasmimport modulename invalidreturn_int: unsupported result type uint +// +//go:wasmimport modulename invalidreturn_int +func invalidreturn_uint() uint + // ERROR: //go:wasmimport modulename invalidreturn_func: unsupported result type func() // //go:wasmimport modulename invalidreturn_func func invalidreturn_func() func() +// ERROR: //go:wasmimport modulename invalidreturn_pointer_array_int: unsupported result type *[8]int +// +//go:wasmimport modulename invalidreturn_pointer_array_int +func invalidreturn_pointer_array_int() *[8]int + // ERROR: //go:wasmimport modulename invalidreturn_slice_byte: unsupported result type []byte // //go:wasmimport modulename invalidreturn_slice_byte diff --git a/testdata/wasmexport-noscheduler.go b/testdata/wasmexport-noscheduler.go index 844c8d1588..b996b2faa0 100644 --- a/testdata/wasmexport-noscheduler.go +++ b/testdata/wasmexport-noscheduler.go @@ -18,16 +18,16 @@ func hello() { } //go:wasmexport add -func add(a, b int) int { +func add(a, b int32) int32 { println("called add:", a, b) return a + b } //go:wasmimport tester callOutside -func callOutside(a, b int) int +func callOutside(a, b int32) int32 //go:wasmexport reentrantCall -func reentrantCall(a, b int) int { +func reentrantCall(a, b int32) int32 { println("reentrantCall:", a, b) result := callOutside(a, b) println("reentrantCall result:", result) diff --git a/testdata/wasmexport.go b/testdata/wasmexport.go index 9065d6e926..a215761273 100644 --- a/testdata/wasmexport.go +++ b/testdata/wasmexport.go @@ -21,15 +21,15 @@ func hello() { } //go:wasmexport add -func add(a, b int) int { +func add(a, b int32) int32 { println("called add:", a, b) addInputs <- a addInputs <- b return <-addOutput } -var addInputs = make(chan int) -var addOutput = make(chan int) +var addInputs = make(chan int32) +var addOutput = make(chan int32) func adder() { for { @@ -41,10 +41,10 @@ func adder() { } //go:wasmimport tester callOutside -func callOutside(a, b int) int +func callOutside(a, b int32) int32 //go:wasmexport reentrantCall -func reentrantCall(a, b int) int { +func reentrantCall(a, b int32) int32 { println("reentrantCall:", a, b) result := callOutside(a, b) println("reentrantCall result:", result) From 0f95b4102d83b6977277ea7d5e87ad538eddc5ef Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 22 Oct 2024 15:53:57 +0200 Subject: [PATCH 241/444] wasm: use precise GC for WebAssembly (including WASI) With a few small modifications, all the problems with `-gc=precise` in WebAssembly seem to have been fixed. I didn't do any performance measurements, but this is supposed to improve GC performance. --- src/internal/task/task.go | 3 +++ src/internal/task/task_asyncify.go | 30 +++++++++++++++++++----------- src/internal/task/task_stack.go | 3 --- targets/wasip1.json | 1 + targets/wasip2.json | 1 + targets/wasm.json | 1 + 6 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/internal/task/task.go b/src/internal/task/task.go index c457390977..c1ee57ddae 100644 --- a/src/internal/task/task.go +++ b/src/internal/task/task.go @@ -30,3 +30,6 @@ type Task struct { // the given function and falls back to the default stack size. It is replaced // with a load from a special section just before codegen. func getGoroutineStackSize(fn uintptr) uintptr + +//go:linkname runtime_alloc runtime.alloc +func runtime_alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer diff --git a/src/internal/task/task_asyncify.go b/src/internal/task/task_asyncify.go index b16ddd7d83..55a1044e4a 100644 --- a/src/internal/task/task_asyncify.go +++ b/src/internal/task/task_asyncify.go @@ -35,11 +35,16 @@ type state struct { type stackState struct { // asyncify is the stack pointer of the asyncify stack. // This starts from the bottom and grows upwards. - asyncifysp uintptr + asyncifysp unsafe.Pointer // asyncify is stack pointer of the C stack. // This starts from the top and grows downwards. - csp uintptr + csp unsafe.Pointer + + // Pointer to the first (lowest address) of the stack. It must never be + // overwritten. It can be checked from time to time to see whether a stack + // overflow happened in the past. + canaryPtr *uintptr } // start creates and starts a new goroutine with the given function and arguments. @@ -63,12 +68,18 @@ func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) { s.args = args // Create a stack. - stack := make([]uintptr, stackSize/unsafe.Sizeof(uintptr(0))) + stack := runtime_alloc(stackSize, nil) + + // Set up the stack canary, a random number that should be checked when + // switching from the task back to the scheduler. The stack canary pointer + // points to the first word of the stack. If it has changed between now and + // the next stack switch, there was a stack overflow. + s.canaryPtr = (*uintptr)(stack) + *s.canaryPtr = stackCanary // Calculate stack base addresses. - s.asyncifysp = uintptr(unsafe.Pointer(&stack[0])) - s.csp = uintptr(unsafe.Pointer(&stack[0])) + uintptr(len(stack))*unsafe.Sizeof(uintptr(0)) - stack[0] = stackCanary + s.asyncifysp = unsafe.Add(stack, unsafe.Sizeof(uintptr(0))) + s.csp = unsafe.Add(stack, stackSize) } //go:linkname runqueuePushBack runtime.runqueuePushBack @@ -85,14 +96,11 @@ func Current() *Task { // Pause suspends the current task and returns to the scheduler. // This function may only be called when running on a goroutine stack, not when running on the system stack. func Pause() { - // This is mildly unsafe but this is also the only place we can do this. - if *(*uintptr)(unsafe.Pointer(currentTask.state.asyncifysp)) != stackCanary { + if *currentTask.state.canaryPtr != stackCanary { runtimePanic("stack overflow") } currentTask.state.unwind() - - *(*uintptr)(unsafe.Pointer(currentTask.state.asyncifysp)) = stackCanary } //export tinygo_unwind @@ -113,7 +121,7 @@ func (t *Task) Resume() { } currentTask = prevTask t.gcData.swap() - if t.state.asyncifysp > t.state.csp { + if uintptr(t.state.asyncifysp) > uintptr(t.state.csp) { runtimePanic("stack overflow") } } diff --git a/src/internal/task/task_stack.go b/src/internal/task/task_stack.go index f566de041b..551612425f 100644 --- a/src/internal/task/task_stack.go +++ b/src/internal/task/task_stack.go @@ -104,9 +104,6 @@ var startTask [0]uint8 //go:linkname runqueuePushBack runtime.runqueuePushBack func runqueuePushBack(*Task) -//go:linkname runtime_alloc runtime.alloc -func runtime_alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer - // start creates and starts a new goroutine with the given function and arguments. // The new goroutine is scheduled to run later. func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) { diff --git a/targets/wasip1.json b/targets/wasip1.json index 409be9ec93..8d1966e789 100644 --- a/targets/wasip1.json +++ b/targets/wasip1.json @@ -8,6 +8,7 @@ "linker": "wasm-ld", "libc": "wasi-libc", "rtlib": "compiler-rt", + "gc": "precise", "scheduler": "asyncify", "default-stack-size": 65536, "cflags": [ diff --git a/targets/wasip2.json b/targets/wasip2.json index 4b0e675914..7c8394c8ea 100644 --- a/targets/wasip2.json +++ b/targets/wasip2.json @@ -9,6 +9,7 @@ "linker": "wasm-ld", "libc": "wasmbuiltins", "rtlib": "compiler-rt", + "gc": "precise", "scheduler": "asyncify", "default-stack-size": 65536, "cflags": [ diff --git a/targets/wasm.json b/targets/wasm.json index 050ee105e0..48a6788910 100644 --- a/targets/wasm.json +++ b/targets/wasm.json @@ -8,6 +8,7 @@ "linker": "wasm-ld", "libc": "wasi-libc", "rtlib": "compiler-rt", + "gc": "precise", "scheduler": "asyncify", "default-stack-size": 65536, "cflags": [ From b8fe75a9dd4949a08b78b2f3dd06fa3c697573dd Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 2 Aug 2024 17:10:52 +0200 Subject: [PATCH 242/444] runtime: add support for os/signal This adds support for enabling and listening to signals on Linux and MacOS. --- builder/musl.go | 1 + compileopts/target.go | 6 +- main_test.go | 9 ++ src/os/signal/signal.go | 14 --- src/runtime/runtime_unix.go | 228 +++++++++++++++++++++++++++++++++++- src/runtime/signal.c | 89 ++++++++++++++ src/runtime/wait_other.go | 2 +- testdata/signal.go | 42 +++++++ testdata/signal.txt | 2 + 9 files changed, 374 insertions(+), 19 deletions(-) delete mode 100644 src/os/signal/signal.go create mode 100644 src/runtime/signal.c create mode 100644 testdata/signal.go create mode 100644 testdata/signal.txt diff --git a/builder/musl.go b/builder/musl.go index 8130981e6c..ecae118e47 100644 --- a/builder/musl.go +++ b/builder/musl.go @@ -128,6 +128,7 @@ var libMusl = Library{ "mman/*.c", "math/*.c", "multibyte/*.c", + "signal/" + arch + "/*.s", "signal/*.c", "stdio/*.c", "string/*.c", diff --git a/compileopts/target.go b/compileopts/target.go index ab9f871f34..b5df5b9115 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -391,7 +391,8 @@ func defaultTarget(options *Options) (*TargetSpec, error) { ) spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/os_darwin.c", - "src/runtime/runtime_unix.c") + "src/runtime/runtime_unix.c", + "src/runtime/signal.c") case "linux": spec.Linker = "ld.lld" spec.RTLib = "compiler-rt" @@ -412,7 +413,8 @@ func defaultTarget(options *Options) (*TargetSpec, error) { spec.CFlags = append(spec.CFlags, "-mno-outline-atomics") } spec.ExtraFiles = append(spec.ExtraFiles, - "src/runtime/runtime_unix.c") + "src/runtime/runtime_unix.c", + "src/runtime/signal.c") case "windows": spec.Linker = "ld.lld" spec.Libc = "mingw-w64" diff --git a/main_test.go b/main_test.go index 93da107366..136128d51c 100644 --- a/main_test.go +++ b/main_test.go @@ -79,6 +79,7 @@ func TestBuild(t *testing.T) { "oldgo/", "print.go", "reflect.go", + "signal.go", "slice.go", "sort.go", "stdlib.go", @@ -217,6 +218,7 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { // isWebAssembly := strings.HasPrefix(spec.Triple, "wasm") isWASI := strings.HasPrefix(options.Target, "wasi") isWebAssembly := isWASI || strings.HasPrefix(options.Target, "wasm") || (options.Target == "" && strings.HasPrefix(options.GOARCH, "wasm")) + isBaremetal := options.Target == "simavr" || options.Target == "cortex-m-qemu" || options.Target == "riscv-qemu" for _, name := range tests { if options.GOOS == "linux" && (options.GOARCH == "arm" || options.GOARCH == "386") { @@ -281,6 +283,13 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { continue } } + if isWebAssembly || isBaremetal || options.GOOS == "windows" { + switch name { + case "signal.go": + // Signals only work on POSIX-like systems. + continue + } + } name := name // redefine to avoid race condition t.Run(name, func(t *testing.T) { diff --git a/src/os/signal/signal.go b/src/os/signal/signal.go deleted file mode 100644 index 41ceaf4853..0000000000 --- a/src/os/signal/signal.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package signal - -import ( - "os" -) - -// Just stubbing the functions for now since signal handling is not yet implemented in tinygo -func Reset(sig ...os.Signal) {} -func Ignore(sig ...os.Signal) {} -func Notify(c chan<- os.Signal, sig ...os.Signal) {} diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index ba5d5a5938..c4fd3285b6 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -3,6 +3,8 @@ package runtime import ( + "math/bits" + "sync/atomic" "unsafe" ) @@ -12,6 +14,9 @@ func libc_write(fd int32, buf unsafe.Pointer, count uint) int //export usleep func usleep(usec uint) int +//export pause +func pause() int32 + // void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); // Note: off_t is defined as int64 because: // - musl (used on Linux) always defines it as int64 @@ -217,8 +222,47 @@ func nanosecondsToTicks(ns int64) timeUnit { } func sleepTicks(d timeUnit) { - // timeUnit is in nanoseconds, so need to convert to microseconds here. - usleep(uint(d) / 1000) + // When there are no signal handlers present, we can simply go to sleep. + if !hasSignals { + // timeUnit is in nanoseconds, so need to convert to microseconds here. + usleep(uint(d) / 1000) + return + } + + if GOOS == "darwin" { + // Check for incoming signals. + if checkSignals() { + // Received a signal, so there's probably at least one goroutine + // that's runnable again. + return + } + + // WARNING: there is a race condition here. If a signal arrives between + // checkSignals() and usleep(), the usleep() call will not exit early so + // the signal is delayed until usleep finishes or another signal + // arrives. + // There doesn't appear to be a simple way to fix this on MacOS. + + // timeUnit is in nanoseconds, so need to convert to microseconds here. + result := usleep(uint(d) / 1000) + if result != 0 { + checkSignals() + } + } else { + // Linux (and various other POSIX systems) implement sigtimedwait so we + // can do this in a non-racy way. + tinygo_wfi_mask(activeSignals) + if checkSignals() { + tinygo_wfi_unmask() + return + } + signal := tinygo_wfi_sleep(activeSignals, uint64(d)) + if signal >= 0 { + tinygo_signal_handler(signal) + checkSignals() + } + tinygo_wfi_unmask() + } } func getTime(clock int32) uint64 { @@ -307,3 +351,183 @@ func growHeap() bool { setHeapEnd(heapStart + heapSize) return true } + +func init() { + // Set up a channel to receive signals into. + signalChan = make(chan uint32, 1) +} + +var signalChan chan uint32 + +// Indicate whether signals have been registered. +var hasSignals bool + +// Mask of signals that have been received. The signal handler atomically ORs +// signals into this value. +var receivedSignals uint32 + +var activeSignals uint32 + +//go:linkname signal_enable os/signal.signal_enable +func signal_enable(s uint32) { + if s >= 32 { + // TODO: to support higher signal numbers, we need to turn + // receivedSignals into a uint32 array. + runtimePanicAt(returnAddress(0), "unsupported signal number") + } + hasSignals = true + activeSignals |= 1 << s + // It's easier to implement this function in C. + tinygo_signal_enable(s) +} + +//go:linkname signal_ignore os/signal.signal_ignore +func signal_ignore(s uint32) { + if s >= 32 { + // TODO: to support higher signal numbers, we need to turn + // receivedSignals into a uint32 array. + runtimePanicAt(returnAddress(0), "unsupported signal number") + } + activeSignals &^= 1 << s + tinygo_signal_ignore(s) +} + +//go:linkname signal_disable os/signal.signal_disable +func signal_disable(s uint32) { + if s >= 32 { + // TODO: to support higher signal numbers, we need to turn + // receivedSignals into a uint32 array. + runtimePanicAt(returnAddress(0), "unsupported signal number") + } + activeSignals &^= 1 << s + tinygo_signal_disable(s) +} + +//go:linkname signal_waitUntilIdle os/signal.signalWaitUntilIdle +func signal_waitUntilIdle() { + // Make sure all signals are sent on the channel. + for atomic.LoadUint32(&receivedSignals) != 0 { + checkSignals() + Gosched() + } + + // Make sure all signals are processed. + for len(signalChan) != 0 { + Gosched() + } +} + +//export tinygo_signal_enable +func tinygo_signal_enable(s uint32) + +//export tinygo_signal_ignore +func tinygo_signal_ignore(s uint32) + +//export tinygo_signal_disable +func tinygo_signal_disable(s uint32) + +// void tinygo_signal_handler(int sig); +// +//export tinygo_signal_handler +func tinygo_signal_handler(s int32) { + // This loop is essentially the atomic equivalent of the following: + // + // receivedSignals |= 1 << s + // + // TODO: use atomic.Uint32.And once we drop support for Go 1.22 instead of + // this loop. + for { + mask := uint32(1) << uint32(s) + val := atomic.LoadUint32(&receivedSignals) + swapped := atomic.CompareAndSwapUint32(&receivedSignals, val, val|mask) + if swapped { + break + } + } +} + +//go:linkname signal_recv os/signal.signal_recv +func signal_recv() uint32 { + // Function called from os/signal to get the next received signal. + val := <-signalChan + checkSignals() + return val +} + +// Atomically find a signal that previously occured and send it into the +// signalChan channel. Return true if at least one signal was delivered this +// way, false otherwise. +func checkSignals() bool { + gotSignals := false + for { + // Extract the lowest numbered signal number from receivedSignals. + val := atomic.LoadUint32(&receivedSignals) + if val == 0 { + // There is no signal ready to be received by the program (common + // case). + return gotSignals + } + num := uint32(bits.TrailingZeros32(val)) + + // Do a non-blocking send on signalChan. + select { + case signalChan <- num: + // There was room free in the channel, so remove the signal number + // from the receivedSignals mask. + gotSignals = true + default: + // Could not send the signal number on the channel. This means + // there's still a signal pending. In that case, let it be received + // at which point checkSignals is called again to put the next one + // in the channel buffer. + return gotSignals + } + + // Atomically clear the signal number from receivedSignals. + // TODO: use atomic.Uint32.Or once we drop support for Go 1.22 instead + // of this loop. + for { + newVal := val &^ (1 << num) + swapped := atomic.CompareAndSwapUint32(&receivedSignals, val, newVal) + if swapped { + break + } + val = atomic.LoadUint32(&receivedSignals) + } + } +} + +//export tinygo_wfi_mask +func tinygo_wfi_mask(active uint32) + +//export tinygo_wfi_sleep +func tinygo_wfi_sleep(active uint32, timeout uint64) int32 + +//export tinygo_wfi_wait +func tinygo_wfi_wait(active uint32) int32 + +//export tinygo_wfi_unmask +func tinygo_wfi_unmask() + +func waitForEvents() { + if hasSignals { + // We could have used pause() here, but that function is impossible to + // use in a race-free way: + // https://www.cipht.net/2023/11/30/perils-of-pause.html + // Therefore we need something better. + // Note: this is unsafe with multithreading, because sigprocmask is only + // defined for single-threaded applictions. + tinygo_wfi_mask(activeSignals) + if checkSignals() { + tinygo_wfi_unmask() + return + } + signal := tinygo_wfi_wait(activeSignals) + tinygo_signal_handler(signal) + checkSignals() + tinygo_wfi_unmask() + } else { + // The program doesn't use signals, so this is a deadlock. + runtimePanic("deadlocked: no event source") + } +} diff --git a/src/runtime/signal.c b/src/runtime/signal.c new file mode 100644 index 0000000000..a462518c13 --- /dev/null +++ b/src/runtime/signal.c @@ -0,0 +1,89 @@ +//go:build none + +// Ignore the //go:build above. This file is manually included on Linux and +// MacOS to provide os/signal support. + +#include +#include +#include +#include + +// Signal handler in the runtime. +void tinygo_signal_handler(int sig); + +// Enable a signal from the runtime. +void tinygo_signal_enable(uint32_t sig) { + struct sigaction act = { 0 }; + act.sa_handler = &tinygo_signal_handler; + sigaction(sig, &act, NULL); +} + +void tinygo_signal_ignore(uint32_t sig) { + struct sigaction act = { 0 }; + act.sa_handler = SIG_IGN; + sigaction(sig, &act, NULL); +} + +void tinygo_signal_disable(uint32_t sig) { + struct sigaction act = { 0 }; + act.sa_handler = SIG_DFL; + sigaction(sig, &act, NULL); +} + +// Implement waitForEvents and sleep with signals. +// Warning: sigprocmask is not defined in a multithreaded program so will need +// to be replaced with something else once we implement threading on POSIX. + +// Signals active before a call to tinygo_wfi_mask. +static sigset_t active_signals; + +static void tinygo_set_signals(sigset_t *mask, uint32_t signals) { + sigemptyset(mask); + for (int i=0; i<32; i++) { + if ((signals & (1< Date: Thu, 3 Oct 2024 12:03:32 -0700 Subject: [PATCH 243/444] runtime: add gc layout info for some basic types --- loader/goroot.go | 1 + src/internal/gclayout/gclayout.go | 33 +++++++++++++++++++++++++++++++ src/reflect/type.go | 21 ++++++++++++++++++++ src/reflect/value.go | 7 +++++-- src/runtime/slice.go | 9 ++++++++- src/runtime/string.go | 9 +++++---- 6 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 src/internal/gclayout/gclayout.go diff --git a/loader/goroot.go b/loader/goroot.go index 05eeeda190..20fee016bf 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -245,6 +245,7 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { "internal/cm/": false, "internal/fuzz/": false, "internal/reflectlite/": false, + "internal/gclayout": false, "internal/task/": false, "internal/wasi/": false, "machine/": false, diff --git a/src/internal/gclayout/gclayout.go b/src/internal/gclayout/gclayout.go new file mode 100644 index 0000000000..aa841d8048 --- /dev/null +++ b/src/internal/gclayout/gclayout.go @@ -0,0 +1,33 @@ +package gclayout + +import "unsafe" + +// Internal constants for gc layout +// See runtime/gc_precise.go + +var ( + NoPtrs unsafe.Pointer + Pointer unsafe.Pointer + String unsafe.Pointer + Slice unsafe.Pointer +) + +func init() { + var sizeBits uintptr + + switch unsafe.Sizeof(uintptr(0)) { + case 8: + sizeBits = 6 + case 4: + sizeBits = 5 + case 2: + sizeBits = 4 + } + + var sizeShift = sizeBits + 1 + + NoPtrs = unsafe.Pointer(uintptr(0b0< 1 { maxSize /= uintptr(elementSize) } @@ -1493,7 +1494,9 @@ func MakeSlice(typ Type, len, cap int) Value { var slice sliceHeader slice.cap = uintptr(ucap) slice.len = uintptr(ulen) - slice.data = alloc(size, nil) + layout := elem.gcLayout() + + slice.data = alloc(size, layout) return Value{ typecode: rtype, diff --git a/src/runtime/slice.go b/src/runtime/slice.go index 7d804b11be..c9603643a7 100644 --- a/src/runtime/slice.go +++ b/src/runtime/slice.go @@ -3,6 +3,7 @@ package runtime // This file implements compiler builtins for slices: append() and copy(). import ( + "internal/gclayout" "math/bits" "unsafe" ) @@ -47,7 +48,13 @@ func sliceGrow(oldBuf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr) // memory allocators, this causes some difficult to debug issues. newCap = 1 << bits.Len(uint(newCap)) - buf := alloc(newCap*elemSize, nil) + var layout unsafe.Pointer + // less type info here; can only go off element size + if elemSize < unsafe.Sizeof(uintptr(0)) { + layout = gclayout.NoPtrs + } + + buf := alloc(newCap*elemSize, layout) if oldLen > 0 { // copy any data to new slice memmove(buf, oldBuf, oldLen*elemSize) diff --git a/src/runtime/string.go b/src/runtime/string.go index aeefe1d4fa..54485dfc29 100644 --- a/src/runtime/string.go +++ b/src/runtime/string.go @@ -3,6 +3,7 @@ package runtime // This file implements functions related to Go strings. import ( + "internal/gclayout" "unsafe" ) @@ -59,7 +60,7 @@ func stringConcat(x, y _string) _string { return x } else { length := x.length + y.length - buf := alloc(length, nil) + buf := alloc(length, gclayout.NoPtrs) memcpy(buf, unsafe.Pointer(x.ptr), x.length) memcpy(unsafe.Add(buf, x.length), unsafe.Pointer(y.ptr), y.length) return _string{ptr: (*byte)(buf), length: length} @@ -72,7 +73,7 @@ func stringFromBytes(x struct { len uintptr cap uintptr }) _string { - buf := alloc(x.len, nil) + buf := alloc(x.len, gclayout.NoPtrs) memcpy(buf, unsafe.Pointer(x.ptr), x.len) return _string{ptr: (*byte)(buf), length: x.len} } @@ -83,7 +84,7 @@ func stringToBytes(x _string) (slice struct { len uintptr cap uintptr }) { - buf := alloc(x.length, nil) + buf := alloc(x.length, gclayout.NoPtrs) memcpy(buf, unsafe.Pointer(x.ptr), x.length) slice.ptr = (*byte)(buf) slice.len = x.length @@ -100,7 +101,7 @@ func stringFromRunes(runeSlice []rune) (s _string) { } // Allocate memory for the string. - s.ptr = (*byte)(alloc(s.length, nil)) + s.ptr = (*byte)(alloc(s.length, gclayout.NoPtrs)) // Encode runes to UTF-8 and store the resulting bytes in the string. index := uintptr(0) From f0d523f7782f9088c3841e955a37fcddb9d2da56 Mon Sep 17 00:00:00 2001 From: sago35 Date: Fri, 25 Oct 2024 08:43:13 +0900 Subject: [PATCH 244/444] machine/usb/adc/midi: clarify operator precedence --- src/machine/usb/adc/midi/messages.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/machine/usb/adc/midi/messages.go b/src/machine/usb/adc/midi/messages.go index 7a681f9bf6..baeb0c409b 100644 --- a/src/machine/usb/adc/midi/messages.go +++ b/src/machine/usb/adc/midi/messages.go @@ -97,7 +97,7 @@ func (m *midi) NoteOn(cable, channel uint8, note Note, velocity uint8) error { return errInvalidMIDIVelocity } - m.msg[0], m.msg[1], m.msg[2], m.msg[3] = (cable&0xf<<4)|CINNoteOn, MsgNoteOn|(channel-1&0xf), byte(note)&0x7f, velocity&0x7f + m.msg[0], m.msg[1], m.msg[2], m.msg[3] = ((cable&0xf)<<4)|CINNoteOn, MsgNoteOn|((channel-1)&0xf), byte(note)&0x7f, velocity&0x7f _, err := m.Write(m.msg[:]) return err } @@ -115,7 +115,7 @@ func (m *midi) NoteOff(cable, channel uint8, note Note, velocity uint8) error { return errInvalidMIDIVelocity } - m.msg[0], m.msg[1], m.msg[2], m.msg[3] = (cable&0xf<<4)|CINNoteOff, MsgNoteOff|(channel-1&0xf), byte(note)&0x7f, velocity&0x7f + m.msg[0], m.msg[1], m.msg[2], m.msg[3] = ((cable&0xf)<<4)|CINNoteOff, MsgNoteOff|((channel-1)&0xf), byte(note)&0x7f, velocity&0x7f _, err := m.Write(m.msg[:]) return err } @@ -137,7 +137,7 @@ func (m *midi) ControlChange(cable, channel, control, value uint8) error { return errInvalidMIDIControlValue } - m.msg[0], m.msg[1], m.msg[2], m.msg[3] = (cable&0xf<<4)|CINControlChange, MsgControlChange|(channel-1&0xf), control&0x7f, value&0x7f + m.msg[0], m.msg[1], m.msg[2], m.msg[3] = ((cable&0xf)<<4)|CINControlChange, MsgControlChange|((channel-1)&0xf), control&0x7f, value&0x7f _, err := m.Write(m.msg[:]) return err } @@ -156,7 +156,7 @@ func (m *midi) ProgramChange(cable, channel uint8, patch uint8) error { return errInvalidMIDIPatch } - m.msg[0], m.msg[1], m.msg[2] = (cable&0xf<<4)|CINProgramChange, MsgProgramChange|(channel-1&0xf), patch&0x7f + m.msg[0], m.msg[1], m.msg[2] = ((cable&0xf)<<4)|CINProgramChange, MsgProgramChange|((channel-1)&0xf), patch&0x7f _, err := m.Write(m.msg[:3]) return err } @@ -177,7 +177,7 @@ func (m *midi) PitchBend(cable, channel uint8, bend uint16) error { return errInvalidMIDIPitchBend } - m.msg[0], m.msg[1], m.msg[2], m.msg[3] = (cable&0xf<<4)|CINPitchBendChange, MsgPitchBend|(channel-1&0xf), byte(bend&0x7f), byte(bend>>8)&0x7f + m.msg[0], m.msg[1], m.msg[2], m.msg[3] = ((cable&0xf)<<4)|CINPitchBendChange, MsgPitchBend|((channel-1)&0xf), byte(bend&0x7f), byte(bend>>8)&0x7f _, err := m.Write(m.msg[:]) return err } @@ -198,7 +198,7 @@ func (m *midi) SysEx(cable uint8, data []byte) error { } // write start - m.msg[0], m.msg[1] = (cable&0xf<<4)|CINSysExStart, MsgSysExStart + m.msg[0], m.msg[1] = ((cable&0xf)<<4)|CINSysExStart, MsgSysExStart m.msg[2], m.msg[3] = data[0], data[1] if _, err := m.Write(m.msg[:]); err != nil { return err @@ -207,7 +207,7 @@ func (m *midi) SysEx(cable uint8, data []byte) error { // write middle i := 2 for ; i < len(data)-2; i += 3 { - m.msg[0], m.msg[1] = (cable&0xf<<4)|CINSysExStart, data[i] + m.msg[0], m.msg[1] = ((cable&0xf)<<4)|CINSysExStart, data[i] m.msg[2], m.msg[3] = data[i+1], data[i+2] if _, err := m.Write(m.msg[:]); err != nil { return err @@ -216,13 +216,13 @@ func (m *midi) SysEx(cable uint8, data []byte) error { // write end switch len(data) - i { case 2: - m.msg[0], m.msg[1] = (cable&0xf<<4)|CINSysExEnd3, data[i] + m.msg[0], m.msg[1] = ((cable&0xf)<<4)|CINSysExEnd3, data[i] m.msg[2], m.msg[3] = data[i+1], MsgSysExEnd case 1: - m.msg[0], m.msg[1] = (cable&0xf<<4)|CINSysExEnd2, data[i] + m.msg[0], m.msg[1] = ((cable&0xf)<<4)|CINSysExEnd2, data[i] m.msg[2], m.msg[3] = MsgSysExEnd, 0 case 0: - m.msg[0], m.msg[1] = (cable&0xf<<4)|CINSysExEnd1, MsgSysExEnd + m.msg[0], m.msg[1] = ((cable&0xf)<<4)|CINSysExEnd1, MsgSysExEnd m.msg[2], m.msg[3] = 0, 0 } if _, err := m.Write(m.msg[:]); err != nil { From b8420e78bb3fe9db73898f2168a91a27e313f1c8 Mon Sep 17 00:00:00 2001 From: sago35 Date: Fri, 25 Oct 2024 08:44:09 +0900 Subject: [PATCH 245/444] machine/usb/adc/midi: fix PitchBend --- src/machine/usb/adc/midi/messages.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/machine/usb/adc/midi/messages.go b/src/machine/usb/adc/midi/messages.go index baeb0c409b..c123acb737 100644 --- a/src/machine/usb/adc/midi/messages.go +++ b/src/machine/usb/adc/midi/messages.go @@ -177,7 +177,7 @@ func (m *midi) PitchBend(cable, channel uint8, bend uint16) error { return errInvalidMIDIPitchBend } - m.msg[0], m.msg[1], m.msg[2], m.msg[3] = ((cable&0xf)<<4)|CINPitchBendChange, MsgPitchBend|((channel-1)&0xf), byte(bend&0x7f), byte(bend>>8)&0x7f + m.msg[0], m.msg[1], m.msg[2], m.msg[3] = ((cable&0xf)<<4)|CINPitchBendChange, MsgPitchBend|((channel-1)&0xf), byte(bend&0x7f), byte(bend>>7)&0x7f _, err := m.Write(m.msg[:]) return err } From 9a6397b3252aa3f3a55953ab56b93e981b88426d Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Fri, 25 Oct 2024 07:24:01 -0700 Subject: [PATCH 246/444] runtime: bump markStackSize Every time we overflow the stack, we have to do a full rescan of the heap. Making this larger means fewer overflows and thus fewer secondary+ heap scans. --- builder/sizes_test.go | 2 +- src/runtime/gc_blocks.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/sizes_test.go b/builder/sizes_test.go index 628ddee896..8b669462a5 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -41,7 +41,7 @@ func TestBinarySize(t *testing.T) { // This is a small number of very diverse targets that we want to test. tests := []sizeTest{ // microcontrollers - {"hifive1b", "examples/echo", 4560, 280, 0, 2268}, + {"hifive1b", "examples/echo", 4568, 280, 0, 2268}, {"microbit", "examples/serial", 2868, 388, 8, 2272}, {"wioterminal", "examples/pininterrupt", 6104, 1484, 116, 6832}, diff --git a/src/runtime/gc_blocks.go b/src/runtime/gc_blocks.go index 1cc2384948..3c3862dbba 100644 --- a/src/runtime/gc_blocks.go +++ b/src/runtime/gc_blocks.go @@ -45,7 +45,7 @@ const ( bytesPerBlock = wordsPerBlock * unsafe.Sizeof(heapStart) stateBits = 2 // how many bits a block state takes (see blockState type) blocksPerStateByte = 8 / stateBits - markStackSize = 4 * unsafe.Sizeof((*int)(nil)) // number of to-be-marked blocks to queue before forcing a rescan + markStackSize = 8 * unsafe.Sizeof((*int)(nil)) // number of to-be-marked blocks to queue before forcing a rescan ) var ( From 69263e73199029aa4c1d778103952d4b3e7ea23e Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 25 Oct 2024 15:17:59 +0200 Subject: [PATCH 247/444] GNUmakefile: do not use the -v flag in `go test` This makes it easier to find what actually went wrong in CI. This flag was added in #4431, I think it was unintentional. --- GNUmakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GNUmakefile b/GNUmakefile index 7bace1f7c1..fd2d282c4b 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -293,7 +293,7 @@ tinygo: ## Build the TinyGo compiler @if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " $(MAKE) llvm-source"; echo " $(MAKE) $(LLVM_BUILDDIR)"; exit 1; fi CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" -ldflags="-X github.com/tinygo-org/tinygo/goenv.GitSha1=`git rev-parse --short HEAD`" . test: wasi-libc check-nodejs-version - CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -v -timeout=1h -buildmode exe -tags "byollvm osusergo" $(GOTESTPKGS) + CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags "byollvm osusergo" $(GOTESTPKGS) # Standard library packages that pass tests on darwin, linux, wasi, and windows, but take over a minute in wasi TEST_PACKAGES_SLOW = \ From 2a76ceb7dd5ea5a834ec470b724882564d9681b3 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 25 Oct 2024 15:15:21 +0200 Subject: [PATCH 248/444] all: version v0.34.0 --- CHANGELOG.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ goenv/version.go | 2 +- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 551951dfa5..41c5d05932 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,53 @@ +0.34.0 +--- +* **general** + - fix `GOOS=wasip1` for `tinygo test` + - add `-C DIR` flag + - add initial documentation for project governance + - add `-ldflags='-extldflags=...'` support + - improve usage message with `tinygo help` and when passing invalid parameters +* **compiler** + - `builder`: remove environment variables when invoking Clang, to avoid the environment changing the behavior + - `builder`: check for the Go toolchain version used to compile TinyGo + - `cgo`: add `C.CBytes` implementation + - `compiler`: fix passing weirdly-padded structs as parameters to new goroutines + - `compiler`: support pragmas on generic functions + - `compiler`: do not let the slice buffer escape when casting a `[]byte` or `[]rune` to a string, to help escape analysis + - `compiler`: conform to the latest iteration of the wasm types proposal + - `loader`: don't panic when main package is not named 'main' + - `loader`: make sure we always return type checker errors even without type errors + - `transform`: optimize range over `[]byte(string)` +* **standard library** + - `crypto/x509`: add package stub to build crypto/x509 on macOS + - `machine/usb/adc/midi`: fix `PitchBend` + - `os`: add `Truncate` stub for baremetal + - `os`: add stubs for `os.File` deadlines + - `os`: add internal `net.newUnixFile` for the net package + - `runtime`: stub runtime_{Before,After}Exec for linkage + - `runtime`: randomize map accesses + - `runtime`: support `maps.Clone` + - `runtime`: add more fields to `MemStats` + - `runtime`: implement newcoro, coroswitch to support package iter + - `runtime`: disallow defer in interrupts + - `runtime`: add support for os/signal on Linux and MacOS + - `runtime`: add gc layout info for some basic types to help the precise GC + - `runtime`: bump GC mark stack size to avoid excessive heap rescans +* **targets** + - `darwin`: use Go standard library syscall package instead of a custom one + - `fe310`: support GPIO `PinInput` + - `mips`: fix compiler crash with GOMIPS=softfloat and defer + - `mips`: add big-endian (GOARCH=mips) support + - `mips`: use MIPS32 (instead of MIPS32R2) as the instruction set for wider compatibility + - `wasi`: add relative and absolute --dir options to wasmtime args + - `wasip2`: add wasmtime -S args to support network interfaces + - `wasm`: add `//go:wasmexport` support (for all WebAssembly targets) + - `wasm`: use precise instead of conservative GC for WebAssembly (including WASI) + - `wasm-unknown`: add bulk memory flags since basically every runtime has it now +* **boards** + - add RAKwireless RAK4631 + - add WaveShare ESP-C3-32S-Kit + + 0.33.0 --- diff --git a/goenv/version.go b/goenv/version.go index 9cc463402e..10b2647bdc 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -9,7 +9,7 @@ import ( // Version of TinyGo. // Update this value before release of new version of software. -const version = "0.34.0-dev" +const version = "0.34.0" var ( // This variable is set at build time using -ldflags parameters. From 915132645eb29eed4de3fbf78d0e1077a8c6a8cf Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 26 Oct 2024 10:58:27 +0200 Subject: [PATCH 249/444] ci: remove 'shell: bash' lines from MacOS build Unlike Windows, we can just use the default shell here. --- .github/workflows/build-macos.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 7fcf46f379..7b0c177ed5 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -27,7 +27,6 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Install Dependencies - shell: bash run: | HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu binaryen - name: Checkout @@ -72,7 +71,6 @@ jobs: path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' - shell: bash run: | # fetch LLVM source rm -rf llvm-project @@ -100,14 +98,12 @@ jobs: - name: make gen-device run: make -j3 gen-device - name: Test TinyGo - shell: bash run: make test GOTESTFLAGS="-short" - name: Build TinyGo release tarball run: make release -j3 - name: Test stdlib packages run: make tinygo-test - name: Make release artifact - shell: bash run: cp -p build/release.tar.gz build/tinygo.darwin-${{ matrix.goarch }}.tar.gz - name: Publish release artifact # Note: this release artifact is double-zipped, see: @@ -121,7 +117,6 @@ jobs: name: darwin-${{ matrix.goarch }}-double-zipped path: build/tinygo.darwin-${{ matrix.goarch }}.tar.gz - name: Smoke tests - shell: bash run: make smoketest TINYGO=$(PWD)/build/tinygo test-macos-homebrew: name: homebrew-install From 76d5b3d786034b5d58167a4b47ec36539bbdf55a Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 28 Oct 2024 14:43:04 +0100 Subject: [PATCH 250/444] sync: don't use `volatile` in Mutex Volatile loads/stors are only useful for communication with interrupts or for memory-mapped I/O. They do not provide any sort of safety for sync.Mutex, while making it *appear* as if it is more safe. * `sync.Mutex` cannot be used safely inside interrupts, because any blocking calls (including `Lock`) will cause a runtime panic. * For multithreading, `volatile` is also the wrong choice. Atomic operations should be used instead, and the current code would not work for multithreaded programs anyway. --- src/sync/mutex.go | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/src/sync/mutex.go b/src/sync/mutex.go index 59f320d5d7..3db705af0c 100644 --- a/src/sync/mutex.go +++ b/src/sync/mutex.go @@ -3,12 +3,10 @@ package sync import ( "internal/task" _ "unsafe" - - "runtime/volatile" ) type Mutex struct { - state uint8 // Set to non-zero if locked. + locked bool blocked task.Stack } @@ -16,18 +14,18 @@ type Mutex struct { func scheduleTask(*task.Task) func (m *Mutex) Lock() { - if m.islocked() { + if m.locked { // Push self onto stack of blocked tasks, and wait to be resumed. m.blocked.Push(task.Current()) task.Pause() return } - m.setlock(true) + m.locked = true } func (m *Mutex) Unlock() { - if !m.islocked() { + if !m.locked { panic("sync: unlock of unlocked Mutex") } @@ -35,7 +33,7 @@ func (m *Mutex) Unlock() { if t := m.blocked.Pop(); t != nil { scheduleTask(t) } else { - m.setlock(false) + m.locked = false } } @@ -45,28 +43,13 @@ func (m *Mutex) Unlock() { // and use of TryLock is often a sign of a deeper problem // in a particular use of mutexes. func (m *Mutex) TryLock() bool { - if m.islocked() { + if m.locked { return false } m.Lock() return true } -func (m *Mutex) islocked() bool { - return volatile.LoadUint8(&m.state) != 0 -} - -func (m *Mutex) setlock(b bool) { - volatile.StoreUint8(&m.state, boolToU8(b)) -} - -func boolToU8(b bool) uint8 { - if b { - return 1 - } - return 0 -} - type RWMutex struct { // waitingWriters are all of the tasks waiting for write locks. waitingWriters task.Stack From 0edeaf657f4d1024f60ca438084c61c8139dd566 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Mon, 28 Oct 2024 17:57:24 +0100 Subject: [PATCH 251/444] tinygo: revise and simplify wasmtime argument handling (#4555) --- compileopts/target.go | 2 +- main.go | 58 ++++++++++++++++++++------------------- targets/wasip1.json | 2 +- targets/wasip2.json | 2 +- targets/wasm-unknown.json | 2 +- 5 files changed, 34 insertions(+), 32 deletions(-) diff --git a/compileopts/target.go b/compileopts/target.go index b5df5b9115..3dc8af02f6 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -452,7 +452,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) { "--stack-first", "--no-demangle", ) - spec.Emulator = "wasmtime --dir={tmpDir}::/tmp {}" + spec.Emulator = "wasmtime run --dir={tmpDir}::/tmp {}" spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/asm_tinygowasm.S", "src/internal/task/task_asyncify_wasm.S", diff --git a/main.go b/main.go index fe8a3fb15a..254e140cf2 100644 --- a/main.go +++ b/main.go @@ -769,9 +769,6 @@ func Run(pkgName string, options *compileopts.Options, cmdArgs []string) error { // passes command line arguments and environment variables in a way appropriate // for the given emulator. func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, cmdArgs, environmentVars []string, timeout time.Duration, run func(cmd *exec.Cmd, result builder.BuildResult) error) (builder.BuildResult, error) { - - isSingleFile := strings.HasSuffix(pkgName, ".go") - // Determine whether we're on a system that supports environment variables // and command line parameters (operating systems, WASI) or not (baremetal, // WebAssembly in the browser). If we're on a system without an environment, @@ -784,7 +781,7 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c needsEnvInVars = true } } - var args, emuArgs, env []string + var args, env []string var extraCmdEnv []string if needsEnvInVars { runtimeGlobals := make(map[string]string) @@ -804,20 +801,6 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c "runtime": runtimeGlobals, } } - } else if config.EmulatorName() == "wasmtime" { - for _, v := range environmentVars { - emuArgs = append(emuArgs, "--env", v) - } - - // Use of '--' argument no longer necessary as of Wasmtime v14: - // https://github.com/bytecodealliance/wasmtime/pull/6946 - // args = append(args, "--") - args = append(args, cmdArgs...) - - // Set this for nicer backtraces during tests, but don't override the user. - if _, ok := os.LookupEnv("WASMTIME_BACKTRACE_DETAILS"); !ok { - extraCmdEnv = append(extraCmdEnv, "WASMTIME_BACKTRACE_DETAILS=1") - } } else { // Pass environment variables and command line parameters as usual. // This also works on qemu-aarch64 etc. @@ -860,7 +843,7 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c return result, err } - name = emulator[0] + name, emulator = emulator[0], emulator[1:] // wasmtime is a WebAssembly runtime CLI with WASI enabled by default. // By default, only stdio is allowed. For example, while STDOUT routes @@ -869,11 +852,24 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c // outside the package directory. Other tests require temporary // writeable directories. We allow this by adding wasmtime flags below. if name == "wasmtime" { + var emuArgs []string + + // Extract the wasmtime subcommand (e.g. "run" or "serve") + if len(emulator) > 1 { + emuArgs = append(emuArgs, emulator[0]) + emulator = emulator[1:] + } + + wd, _ := os.Getwd() + // Below adds additional wasmtime flags in case a test reads files // outside its directory, like "../testdata/e.txt". This allows any // relative directory up to the module root, even if the test never // reads any files. if config.TestConfig.CompileTestBinary { + // Set working directory to package dir + wd = result.MainDir + // Add relative dirs (../, ../..) up to module root (for wasip1) dirs := dirsToModuleRootRel(result.MainDir, result.ModuleRoot) @@ -883,19 +879,25 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c for _, d := range dirs { emuArgs = append(emuArgs, "--dir="+d) } + } else { + emuArgs = append(emuArgs, "--dir=.") } - dir := result.MainDir - if isSingleFile { - dir, _ = os.Getwd() + emuArgs = append(emuArgs, "--dir="+wd) + emuArgs = append(emuArgs, "--env=PWD="+wd) + for _, v := range environmentVars { + emuArgs = append(emuArgs, "--env", v) } - emuArgs = append(emuArgs, "--dir=.") - emuArgs = append(emuArgs, "--dir="+dir) - emuArgs = append(emuArgs, "--env=PWD="+dir) + + // Set this for nicer backtraces during tests, but don't override the user. + if _, ok := os.LookupEnv("WASMTIME_BACKTRACE_DETAILS"); !ok { + extraCmdEnv = append(extraCmdEnv, "WASMTIME_BACKTRACE_DETAILS=1") + } + + emulator = append(emuArgs, emulator...) } - emuArgs = append(emuArgs, emulator[1:]...) - args = append(emuArgs, args...) + args = append(emulator, args...) } var cmd *exec.Cmd if ctx != nil { @@ -925,7 +927,7 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c // Run binary. if config.Options.PrintCommands != nil { - config.Options.PrintCommands(cmd.Path, cmd.Args...) + config.Options.PrintCommands(cmd.Path, cmd.Args[1:]...) } err = run(cmd, result) if err != nil { diff --git a/targets/wasip1.json b/targets/wasip1.json index 8d1966e789..4181f16ee9 100644 --- a/targets/wasip1.json +++ b/targets/wasip1.json @@ -23,5 +23,5 @@ "extra-files": [ "src/runtime/asm_tinygowasm.S" ], - "emulator": "wasmtime --dir={tmpDir}::/tmp {}" + "emulator": "wasmtime run --dir={tmpDir}::/tmp {}" } diff --git a/targets/wasip2.json b/targets/wasip2.json index 7c8394c8ea..786536f2b0 100644 --- a/targets/wasip2.json +++ b/targets/wasip2.json @@ -25,7 +25,7 @@ "extra-files": [ "src/runtime/asm_tinygowasm.S" ], - "emulator": "wasmtime --wasm component-model -Sinherit-network -Sallow-ip-name-lookup --dir={tmpDir}::/tmp {}", + "emulator": "wasmtime run --wasm component-model -Sinherit-network -Sallow-ip-name-lookup --dir={tmpDir}::/tmp {}", "wit-package": "{root}/lib/wasi-cli/wit/", "wit-world": "wasi:cli/command" } diff --git a/targets/wasm-unknown.json b/targets/wasm-unknown.json index 59cd94db48..f07a2406ed 100644 --- a/targets/wasm-unknown.json +++ b/targets/wasm-unknown.json @@ -24,5 +24,5 @@ "extra-files": [ "src/runtime/asm_tinygowasm.S" ], - "emulator": "wasmtime --dir={tmpDir}::/tmp {}" + "emulator": "wasmtime run --dir={tmpDir}::/tmp {}" } From 1ac26d3d2ffb201e820a024ccbe47b71430de96e Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 30 Oct 2024 12:17:39 +0100 Subject: [PATCH 252/444] test: run TestWasmExportJS tests in parallel --- main_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main_test.go b/main_test.go index 136128d51c..14b3d18091 100644 --- a/main_test.go +++ b/main_test.go @@ -728,6 +728,7 @@ func TestWasmFuncOf(t *testing.T) { // Test //go:wasmexport in JavaScript (using NodeJS). func TestWasmExportJS(t *testing.T) { + t.Parallel() type testCase struct { name string buildMode string @@ -738,7 +739,9 @@ func TestWasmExportJS(t *testing.T) { {name: "c-shared", buildMode: "c-shared"}, } for _, tc := range tests { + tc := tc t.Run(tc.name, func(t *testing.T) { + t.Parallel() // Build the wasm binary. tmpdir := t.TempDir() options := optionsFromTarget("wasm", sema) From 4b706ae25c2ab52f0aabdb78baeaef4cdcf3578c Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 30 Oct 2024 09:50:57 +0100 Subject: [PATCH 253/444] test: show output even when a test binary didn't exit cleanly This was a problem on wasm, where node would exit with a non-zero exit code when there was a panic. --- main_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main_test.go b/main_test.go index 14b3d18091..723a522670 100644 --- a/main_test.go +++ b/main_test.go @@ -429,6 +429,9 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c for _, line := range strings.Split(strings.TrimRight(w.String(), "\n"), "\n") { t.Log(line) } + if stdout.Len() != 0 { + t.Logf("output:\n%s", stdout.String()) + } t.Fail() return } From 4e49ba597dc20e0bfd13fc39e0a1c89959576ec5 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 31 Oct 2024 10:44:21 +0100 Subject: [PATCH 254/444] interrupt: fix bug in interrupt lowering The alignment wasn't set, so defaulted to 4 (for a 32-bit int). LLVM saw this, and therefore assumed that a ptrtoint of the pointer would have had the lowest bits unset. That's an entirely valid optimization, except that we are using these globals for arbitrary values (and aren't actually using these globals). Fixed by setting alignment to 1. It works, though long-term we should maybe find a different solution for this. --- compiler/interrupt.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/interrupt.go b/compiler/interrupt.go index 6ba031819a..68c8a36058 100644 --- a/compiler/interrupt.go +++ b/compiler/interrupt.go @@ -41,6 +41,8 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro // Create a new global of type runtime/interrupt.handle. Globals of this // type are lowered in the interrupt lowering pass. + // It must have an alignment of 1, otherwise LLVM thinks a ptrtoint of the + // global has the lower bits unset. globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type() globalLLVMType := b.getLLVMType(globalType) globalName := b.fn.Package().Pkg.Path() + "$interrupt" + strconv.FormatInt(id.Int64(), 10) @@ -48,6 +50,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro global.SetVisibility(llvm.HiddenVisibility) global.SetGlobalConstant(true) global.SetUnnamedAddr(true) + global.SetAlignment(1) initializer := llvm.ConstNull(globalLLVMType) initializer = b.CreateInsertValue(initializer, funcContext, 0, "") initializer = b.CreateInsertValue(initializer, funcPtr, 1, "") From 058f62ac08ace927f01b005e7b0a21b1a41590a7 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 25 Oct 2024 15:32:20 +0200 Subject: [PATCH 255/444] main: parse extldflags early so we can report the error message This avoids some weird behavior when the -extldflags flag cannot be parsed by TinyGo. --- compileopts/config.go | 10 +--------- compileopts/options.go | 2 +- main.go | 9 ++++++++- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/compileopts/config.go b/compileopts/config.go index 44d3b005cc..76215b1817 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -406,15 +406,7 @@ func (c *Config) LDFlags() []string { if c.Target.LinkerScript != "" { ldflags = append(ldflags, "-T", c.Target.LinkerScript) } - - if c.Options.ExtLDFlags != "" { - ext, err := shlex.Split(c.Options.ExtLDFlags) - if err != nil { - // if shlex can't split it, pass it as-is and let the external linker complain - ext = []string{c.Options.ExtLDFlags} - } - ldflags = append(ldflags, ext...) - } + ldflags = append(ldflags, c.Options.ExtLDFlags...) return ldflags } diff --git a/compileopts/options.go b/compileopts/options.go index b83f6f63ba..bc462b29bd 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -58,7 +58,7 @@ type Options struct { Timeout time.Duration WITPackage string // pass through to wasm-tools component embed invocation WITWorld string // pass through to wasm-tools component embed -w option - ExtLDFlags string + ExtLDFlags []string } // Verify performs a validation on the given options, raising an error if options are not valid. diff --git a/main.go b/main.go index 254e140cf2..8ae5ce316a 100644 --- a/main.go +++ b/main.go @@ -1641,12 +1641,19 @@ func main() { Timeout: *timeout, WITPackage: witPackage, WITWorld: witWorld, - ExtLDFlags: extLDFlags, } if *printCommands { options.PrintCommands = printCommand } + if extLDFlags != "" { + options.ExtLDFlags, err = shlex.Split(extLDFlags) + if err != nil { + fmt.Fprintln(os.Stderr, "could not parse -extldflags:", err) + os.Exit(1) + } + } + err = options.Verify() if err != nil { fmt.Fprintln(os.Stderr, err.Error()) From 449eefdb19c58df9303ee5b9fe62fe18876eb2f9 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 31 Oct 2024 09:18:32 +0100 Subject: [PATCH 256/444] compiler: allow deferred panic This is rare, but apparently some programs do this: defer panic("...") This is emitted in the IR as a builtin function. --- compiler/compiler.go | 4 ++++ testdata/recover.go | 15 +++++++++++++++ testdata/recover.txt | 4 ++++ 3 files changed, 23 insertions(+) diff --git a/compiler/compiler.go b/compiler/compiler.go index 752e4a5c62..981993fe76 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1680,6 +1680,10 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c result = b.CreateSelect(cmp, result, arg, "") } return result, nil + case "panic": + // This is rare, but happens in "defer panic()". + b.createRuntimeInvoke("_panic", argValues, "") + return llvm.Value{}, nil case "print", "println": for i, value := range argValues { if i >= 1 && callName == "println" { diff --git a/testdata/recover.go b/testdata/recover.go index ced90cfaee..c7c02c94a4 100644 --- a/testdata/recover.go +++ b/testdata/recover.go @@ -19,6 +19,9 @@ func main() { println("\n# panic replace") panicReplace() + + println("\n# defer panic") + deferPanic() } func recoverSimple() { @@ -89,6 +92,18 @@ func panicReplace() { panic("panic 1") } +func deferPanic() { + defer func() { + printitf("recovered from deferred call:", recover()) + }() + + // This recover should not do anything. + defer recover() + + defer panic("deferred panic") + println("defer panic") +} + func printitf(msg string, itf interface{}) { switch itf := itf.(type) { case string: diff --git a/testdata/recover.txt b/testdata/recover.txt index d276498550..3575058812 100644 --- a/testdata/recover.txt +++ b/testdata/recover.txt @@ -23,3 +23,7 @@ recovered: panic panic 1 panic 2 recovered: panic 2 + +# defer panic +defer panic +recovered from deferred call: deferred panic From 1648fe8ef2cfc7c41e9d6e191565b089b571812e Mon Sep 17 00:00:00 2001 From: Joonas Bergius Date: Wed, 30 Oct 2024 22:52:39 -0500 Subject: [PATCH 257/444] runtime/trace: stub all public methods Signed-off-by: Joonas Bergius --- src/runtime/trace/trace.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/runtime/trace/trace.go b/src/runtime/trace/trace.go index 31cdbc33cb..9d5edad866 100644 --- a/src/runtime/trace/trace.go +++ b/src/runtime/trace/trace.go @@ -2,6 +2,7 @@ package trace import ( + "context" "errors" "io" ) @@ -11,3 +12,31 @@ func Start(w io.Writer) error { } func Stop() {} + +func NewTask(pctx context.Context, taskType string) (ctx context.Context, task *Task) { + return context.TODO(), nil +} + +type Task struct{} + +func (t *Task) End() {} + +func Log(ctx context.Context, category, message string) {} + +func Logf(ctx context.Context, category, format string, args ...any) {} + +func WithRegion(ctx context.Context, regionType string, fn func()) { + fn() +} + +func StartRegion(ctx context.Context, regionType string) *Region { + return nil +} + +type Region struct{} + +func (r *Region) End() {} + +func IsEnabled() bool { + return false +} From 5862b481a4e1801cecbc1daa891de2289ac734f7 Mon Sep 17 00:00:00 2001 From: sago35 Date: Fri, 1 Nov 2024 09:05:39 +0900 Subject: [PATCH 258/444] goenv: update to new v0.35.0 development version --- goenv/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/goenv/version.go b/goenv/version.go index 10b2647bdc..5b71820f2d 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -9,7 +9,7 @@ import ( // Version of TinyGo. // Update this value before release of new version of software. -const version = "0.34.0" +const version = "0.35.0-dev" var ( // This variable is set at build time using -ldflags parameters. From c02a8141c7974e3e3a3deebd38c9ff64c6511c33 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Mon, 4 Nov 2024 23:32:17 +0100 Subject: [PATCH 259/444] internal/wasm-tools, syscall: update to wasm-tools-go@v0.3.1 (#4577) * internal/wasm-tools, internal/cm: update wasm-tools-go to v0.3.1 and regenerate bindings * syscall: use new (cm.Result).Result() method instead of OK() and Err() --- internal/wasm-tools/go.mod | 5 +- internal/wasm-tools/go.sum | 14 ++-- src/internal/cm/result.go | 10 +++ src/syscall/libc_wasip2.go | 147 ++++++++++++++++++------------------- 4 files changed, 90 insertions(+), 86 deletions(-) diff --git a/internal/wasm-tools/go.mod b/internal/wasm-tools/go.mod index fbc3b20f07..3404ab78c7 100644 --- a/internal/wasm-tools/go.mod +++ b/internal/wasm-tools/go.mod @@ -2,7 +2,7 @@ module github.com/tinygo-org/tinygo/internal/tools go 1.22.4 -require github.com/bytecodealliance/wasm-tools-go v0.3.0 +require github.com/bytecodealliance/wasm-tools-go v0.3.1 require ( github.com/coreos/go-semver v0.3.1 // indirect @@ -12,8 +12,7 @@ require ( github.com/regclient/regclient v0.7.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/ulikunitz/xz v0.5.12 // indirect - github.com/urfave/cli/v3 v3.0.0-alpha9 // indirect - github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + github.com/urfave/cli/v3 v3.0.0-alpha9.2 // indirect golang.org/x/mod v0.21.0 // indirect golang.org/x/sys v0.26.0 // indirect ) diff --git a/internal/wasm-tools/go.sum b/internal/wasm-tools/go.sum index 72f90a316d..2f4bef21b6 100644 --- a/internal/wasm-tools/go.sum +++ b/internal/wasm-tools/go.sum @@ -1,5 +1,5 @@ -github.com/bytecodealliance/wasm-tools-go v0.3.0 h1:9aeDFYpbi3gtIW/nJCH+P+LhFMqezGoOfzqbUZLadho= -github.com/bytecodealliance/wasm-tools-go v0.3.0/go.mod h1:VY+9FlpLi6jnhCrZLkyJjF9rjU4aEekgaRTk28MS2JE= +github.com/bytecodealliance/wasm-tools-go v0.3.1 h1:9Q9PjSzkbiVmkUvZ7nYCfJ02mcQDBalxycA3s8g7kR4= +github.com/bytecodealliance/wasm-tools-go v0.3.1/go.mod h1:vNAQ8DAEp6xvvk+TUHah5DslLEa76f4H6e737OeaxuY= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -23,14 +23,12 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/urfave/cli/v3 v3.0.0-alpha9 h1:P0RMy5fQm1AslQS+XCmy9UknDXctOmG/q/FZkUFnJSo= -github.com/urfave/cli/v3 v3.0.0-alpha9/go.mod h1:0kK/RUFHyh+yIKSfWxwheGndfnrvYSmYFVeKCh03ZUc= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/urfave/cli/v3 v3.0.0-alpha9.2 h1:CL8llQj3dGRLVQQzHxS+ZYRLanOuhyK1fXgLKD+qV+Y= +github.com/urfave/cli/v3 v3.0.0-alpha9.2/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= diff --git a/src/internal/cm/result.go b/src/internal/cm/result.go index 82200e2782..781dccc1a1 100644 --- a/src/internal/cm/result.go +++ b/src/internal/cm/result.go @@ -71,6 +71,16 @@ func (r *result[Shape, OK, Err]) Err() *Err { return (*Err)(unsafe.Pointer(&r.data)) } +// Result returns (OK, zero value of Err, false) if r represents the OK case, +// or (zero value of OK, Err, true) if r represents the error case. +// This does not have a pointer receiver, so it can be chained. +func (r result[Shape, OK, Err]) Result() (ok OK, err Err, isErr bool) { + if r.isErr { + return ok, *(*Err)(unsafe.Pointer(&r.data)), true + } + return *(*OK)(unsafe.Pointer(&r.data)), err, false +} + // This function is sized so it can be inlined and optimized away. func (r *result[Shape, OK, Err]) validate() { var shape Shape diff --git a/src/syscall/libc_wasip2.go b/src/syscall/libc_wasip2.go index 7123f2db20..a89e64a80b 100644 --- a/src/syscall/libc_wasip2.go +++ b/src/syscall/libc_wasip2.go @@ -162,8 +162,8 @@ func readStream(stream *wasiStream, buf *byte, count uint, offset int64) int { } libcErrno = 0 - result := stream.in.BlockingRead(uint64(count)) - if err := result.Err(); err != nil { + list, err, isErr := stream.in.BlockingRead(uint64(count)).Result() + if isErr { if err.Closed() { libcErrno = 0 return 0 @@ -174,9 +174,7 @@ func readStream(stream *wasiStream, buf *byte, count uint, offset int64) int { return -1 } - dst := unsafe.Slice(buf, count) - list := result.OK() - copy(dst, list.Slice()) + copy(unsafe.Slice(buf, count), list.Slice()) return int(list.Len()) } @@ -202,8 +200,8 @@ func writeStream(stream *wasiStream, buf *byte, count uint, offset int64) int { if len > remaining { len = remaining } - result := stream.out.BlockingWriteAndFlush(cm.ToList(src[:len])) - if err := result.Err(); err != nil { + _, err, isErr := stream.out.BlockingWriteAndFlush(cm.ToList(src[:len])).Result() + if isErr { if err.Closed() { libcErrno = 0 return 0 @@ -248,13 +246,13 @@ func pread(fd int32, buf *byte, count uint, offset int64) int { return -1 } - result := streams.d.Read(types.FileSize(count), types.FileSize(offset)) - if err := result.Err(); err != nil { - libcErrno = errorCodeToErrno(*err) + listEOF, err, isErr := streams.d.Read(types.FileSize(count), types.FileSize(offset)).Result() + if isErr { + libcErrno = errorCodeToErrno(err) return -1 } - list := result.OK().F0 + list := listEOF.F0 copy(unsafe.Slice(buf, count), list.Slice()) // TODO(dgryski): EOF bool is ignored? @@ -285,14 +283,14 @@ func pwrite(fd int32, buf *byte, count uint, offset int64) int { return -1 } - result := streams.d.Write(cm.NewList(buf, count), types.FileSize(offset)) - if err := result.Err(); err != nil { + n, err, isErr := streams.d.Write(cm.NewList(buf, count), types.FileSize(offset)).Result() + if isErr { // TODO(dgryski): - libcErrno = errorCodeToErrno(*err) + libcErrno = errorCodeToErrno(err) return -1 } - return int(*result.OK()) + return int(n) } // ssize_t lseek(int fd, off_t offset, int whence); @@ -321,12 +319,12 @@ func lseek(fd int32, offset int64, whence int) int64 { case 1: // SEEK_CUR stream.offset += offset case 2: // SEEK_END - result := stream.d.Stat() - if err := result.Err(); err != nil { - libcErrno = errorCodeToErrno(*err) + stat, err, isErr := stream.d.Stat().Result() + if isErr { + libcErrno = errorCodeToErrno(err) return -1 } - stream.offset = int64(result.OK().Size) + offset + stream.offset = int64(stat.Size) + offset } return int64(stream.offset) @@ -439,9 +437,9 @@ func mkdir(pathname *byte, mode uint32) int32 { return -1 } - result := dir.d.CreateDirectoryAt(relPath) - if err := result.Err(); err != nil { - libcErrno = errorCodeToErrno(*err) + _, err, isErr := dir.d.CreateDirectoryAt(relPath).Result() + if isErr { + libcErrno = errorCodeToErrno(err) return -1 } @@ -459,9 +457,9 @@ func rmdir(pathname *byte) int32 { return -1 } - result := dir.d.RemoveDirectoryAt(relPath) - if err := result.Err(); err != nil { - libcErrno = errorCodeToErrno(*err) + _, err, isErr := dir.d.RemoveDirectoryAt(relPath).Result() + if isErr { + libcErrno = errorCodeToErrno(err) return -1 } @@ -486,9 +484,9 @@ func rename(from, to *byte) int32 { return -1 } - result := fromDir.d.RenameAt(fromRelPath, toDir.d, toRelPath) - if err := result.Err(); err != nil { - libcErrno = errorCodeToErrno(*err) + _, err, isErr := fromDir.d.RenameAt(fromRelPath, toDir.d, toRelPath).Result() + if isErr { + libcErrno = errorCodeToErrno(err) return -1 } @@ -520,9 +518,9 @@ func symlink(from, to *byte) int32 { // TODO(dgryski): check fromDir == toDir? - result := fromDir.d.SymlinkAt(fromRelPath, toRelPath) - if err := result.Err(); err != nil { - libcErrno = errorCodeToErrno(*err) + _, err, isErr := fromDir.d.SymlinkAt(fromRelPath, toRelPath).Result() + if isErr { + libcErrno = errorCodeToErrno(err) return -1 } @@ -554,9 +552,9 @@ func link(from, to *byte) int32 { // TODO(dgryski): check fromDir == toDir? - result := fromDir.d.LinkAt(0, fromRelPath, toDir.d, toRelPath) - if err := result.Err(); err != nil { - libcErrno = errorCodeToErrno(*err) + _, err, isErr := fromDir.d.LinkAt(0, fromRelPath, toDir.d, toRelPath).Result() + if isErr { + libcErrno = errorCodeToErrno(err) return -1 } @@ -587,9 +585,9 @@ func fsync(fd int32) int32 { return -1 } - result := streams.d.SyncData() - if err := result.Err(); err != nil { - libcErrno = errorCodeToErrno(*err) + _, err, isErr := streams.d.SyncData().Result() + if isErr { + libcErrno = errorCodeToErrno(err) return -1 } @@ -607,13 +605,12 @@ func readlink(pathname *byte, buf *byte, count uint) int { return -1 } - result := dir.d.ReadLinkAt(relPath) - if err := result.Err(); err != nil { - libcErrno = errorCodeToErrno(*err) + s, err, isErr := dir.d.ReadLinkAt(relPath).Result() + if isErr { + libcErrno = errorCodeToErrno(err) return -1 } - s := *result.OK() size := uintptr(count) if size > uintptr(len(s)) { size = uintptr(len(s)) @@ -634,9 +631,9 @@ func unlink(pathname *byte) int32 { return -1 } - result := dir.d.UnlinkFileAt(relPath) - if err := result.Err(); err != nil { - libcErrno = errorCodeToErrno(*err) + _, err, isErr := dir.d.UnlinkFileAt(relPath).Result() + if isErr { + libcErrno = errorCodeToErrno(err) return -1 } @@ -661,13 +658,13 @@ func stat(pathname *byte, dst *Stat_t) int32 { return -1 } - result := dir.d.StatAt(0, relPath) - if err := result.Err(); err != nil { - libcErrno = errorCodeToErrno(*err) + stat, err, isErr := dir.d.StatAt(0, relPath).Result() + if isErr { + libcErrno = errorCodeToErrno(err) return -1 } - setStatFromWASIStat(dst, result.OK()) + setStatFromWASIStat(dst, &stat) return 0 } @@ -690,13 +687,13 @@ func fstat(fd int32, dst *Stat_t) int32 { libcErrno = EBADF return -1 } - result := stream.d.Stat() - if err := result.Err(); err != nil { - libcErrno = errorCodeToErrno(*err) + stat, err, isErr := stream.d.Stat().Result() + if isErr { + libcErrno = errorCodeToErrno(err) return -1 } - setStatFromWASIStat(dst, result.OK()) + setStatFromWASIStat(dst, &stat) return 0 } @@ -745,13 +742,13 @@ func lstat(pathname *byte, dst *Stat_t) int32 { return -1 } - result := dir.d.StatAt(0, relPath) - if err := result.Err(); err != nil { - libcErrno = errorCodeToErrno(*err) + stat, err, isErr := dir.d.StatAt(0, relPath).Result() + if isErr { + libcErrno = errorCodeToErrno(err) return -1 } - setStatFromWASIStat(dst, result.OK()) + setStatFromWASIStat(dst, &stat) return 0 } @@ -981,25 +978,25 @@ func open(pathname *byte, flags int32, mode uint32) int32 { pflags &^= types.PathFlagsSymlinkFollow } - result := dir.d.OpenAt(pflags, relPath, oflags, dflags) - if err := result.Err(); err != nil { - libcErrno = errorCodeToErrno(*err) + descriptor, err, isErr := dir.d.OpenAt(pflags, relPath, oflags, dflags).Result() + if isErr { + libcErrno = errorCodeToErrno(err) return -1 } stream := wasiFile{ - d: *result.OK(), + d: descriptor, oflag: flags, refs: 1, } if flags&(O_WRONLY|O_APPEND) == (O_WRONLY | O_APPEND) { - result := stream.d.Stat() - if err := result.Err(); err != nil { - libcErrno = errorCodeToErrno(*err) + stat, err, isErr := stream.d.Stat().Result() + if isErr { + libcErrno = errorCodeToErrno(err) return -1 } - stream.offset = int64(result.OK().Size) + stream.offset = int64(stat.Size) } libcfd := findFreeFD() @@ -1112,13 +1109,13 @@ func fdopendir(fd int32) unsafe.Pointer { return nil } - result := stream.d.ReadDirectory() - if err := result.Err(); err != nil { - libcErrno = errorCodeToErrno(*err) + dir, err, isErr := stream.d.ReadDirectory().Result() + if isErr { + libcErrno = errorCodeToErrno(err) return nil } - return unsafe.Pointer(&libc_DIR{d: *result.OK()}) + return unsafe.Pointer(&libc_DIR{d: dir}) } // int fdclosedir(DIR *); @@ -1153,13 +1150,13 @@ func readdir(dirp unsafe.Pointer) *Dirent { return nil } - result := dir.d.ReadDirectoryEntry() - if err := result.Err(); err != nil { - libcErrno = errorCodeToErrno(*err) + someEntry, err, isErr := dir.d.ReadDirectoryEntry().Result() + if isErr { + libcErrno = errorCodeToErrno(err) return nil } - entry := result.OK().Some() + entry := someEntry.Some() if entry == nil { libcErrno = 0 return nil @@ -1311,9 +1308,9 @@ func chdir(name *byte) int { return -1 } - result := dir.d.OpenAt(types.PathFlagsSymlinkFollow, rel, types.OpenFlagsDirectory, types.DescriptorFlagsRead) - if err := result.Err(); err != nil { - libcErrno = errorCodeToErrno(*err) + _, err, isErr := dir.d.OpenAt(types.PathFlagsSymlinkFollow, rel, types.OpenFlagsDirectory, types.DescriptorFlagsRead).Result() + if isErr { + libcErrno = errorCodeToErrno(err) return -1 } From f9f439ad49ef8a21e373568277aff8e448806cf2 Mon Sep 17 00:00:00 2001 From: leongross Date: Thu, 7 Nov 2024 09:45:47 +0100 Subject: [PATCH 260/444] os: implement StartProcess Signed-off-by: leongross --- GNUmakefile | 1 + builder/musl.go | 8 +++ src/os/exec.go | 14 ++++- src/os/exec_linux.go | 103 ++++++++++++++++++++++++++++++++++++ src/os/exec_linux_test.go | 78 +++++++++++++++++++++++++++ src/os/exec_other.go | 27 ++++++++++ src/os/exec_posix.go | 35 ------------ src/os/osexec.go | 58 ++++++++++++++++++++ src/syscall/syscall_libc.go | 5 ++ src/syscall/syscall_unix.go | 2 + 10 files changed, 295 insertions(+), 36 deletions(-) create mode 100644 src/os/exec_linux.go create mode 100644 src/os/exec_linux_test.go create mode 100644 src/os/exec_other.go delete mode 100644 src/os/exec_posix.go create mode 100644 src/os/osexec.go diff --git a/GNUmakefile b/GNUmakefile index fd2d282c4b..155f5c5d57 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -941,6 +941,7 @@ endif @cp -rp lib/musl/src/thread build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/time build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/unistd build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/process build/release/tinygo/lib/musl/src @cp -rp lib/mingw-w64/mingw-w64-crt/def-include build/release/tinygo/lib/mingw-w64/mingw-w64-crt @cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/api-ms-win-crt-* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common @cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/kernel32.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common diff --git a/builder/musl.go b/builder/musl.go index ecae118e47..a6699ad8d1 100644 --- a/builder/musl.go +++ b/builder/musl.go @@ -136,12 +136,20 @@ var libMusl = Library{ "thread/*.c", "time/*.c", "unistd/*.c", + "process/*.c", } + if arch == "arm" { // These files need to be added to the start for some reason. globs = append([]string{"thread/arm/*.c"}, globs...) } + if arch != "aarch64" && arch != "mips" { + //aarch64 and mips have no architecture specific code, either they + // are not supported or don't need any? + globs = append([]string{"process/" + arch + "/*.s"}, globs...) + } + var sources []string seenSources := map[string]struct{}{} basepath := goenv.Get("TINYGOROOT") + "/lib/musl/src/" diff --git a/src/os/exec.go b/src/os/exec.go index 1ea9dcbd8c..28406f916b 100644 --- a/src/os/exec.go +++ b/src/os/exec.go @@ -5,6 +5,12 @@ import ( "syscall" ) +var ( + ErrNotImplementedDir = errors.New("directory setting not implemented") + ErrNotImplementedSys = errors.New("sys setting not implemented") + ErrNotImplementedFiles = errors.New("files setting not implemented") +) + type Signal interface { String() string Signal() // to distinguish from other Stringers @@ -47,6 +53,10 @@ func (p *ProcessState) Sys() interface{} { return nil // TODO } +func (p *ProcessState) Exited() bool { + return false // TODO +} + // ExitCode returns the exit code of the exited process, or -1 // if the process hasn't exited or was terminated by a signal. func (p *ProcessState) ExitCode() int { @@ -57,8 +67,10 @@ type Process struct { Pid int } +// StartProcess starts a new process with the program, arguments and attributes specified by name, argv and attr. +// Arguments to the process (os.Args) are passed via argv. func StartProcess(name string, argv []string, attr *ProcAttr) (*Process, error) { - return nil, &PathError{Op: "fork/exec", Path: name, Err: ErrNotImplemented} + return startProcess(name, argv, attr) } func (p *Process) Wait() (*ProcessState, error) { diff --git a/src/os/exec_linux.go b/src/os/exec_linux.go new file mode 100644 index 0000000000..58ee79cc81 --- /dev/null +++ b/src/os/exec_linux.go @@ -0,0 +1,103 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux && !baremetal && !tinygo.wasm + +package os + +import ( + "errors" + "runtime" + "syscall" +) + +// The only signal values guaranteed to be present in the os package on all +// systems are os.Interrupt (send the process an interrupt) and os.Kill (force +// the process to exit). On Windows, sending os.Interrupt to a process with +// os.Process.Signal is not implemented; it will return an error instead of +// sending a signal. +var ( + Interrupt Signal = syscall.SIGINT + Kill Signal = syscall.SIGKILL +) + +// Keep compatible with golang and always succeed and return new proc with pid on Linux. +func findProcess(pid int) (*Process, error) { + return &Process{Pid: pid}, nil +} + +func (p *Process) release() error { + // NOOP for unix. + p.Pid = -1 + // no need for a finalizer anymore + runtime.SetFinalizer(p, nil) + return nil +} + +// This function is a wrapper around the forkExec function, which is a wrapper around the fork and execve system calls. +// The StartProcess function creates a new process by forking the current process and then calling execve to replace the current process with the new process. +// It thereby replaces the newly created process with the specified command and arguments. +// Differences to upstream golang implementation (https://cs.opensource.google/go/go/+/master:src/syscall/exec_unix.go;l=143): +// * No setting of Process Attributes +// * Ignoring Ctty +// * No ForkLocking (might be introduced by #4273) +// * No parent-child communication via pipes (TODO) +// * No waiting for crashes child processes to prohibit zombie process accumulation / Wait status checking (TODO) +func forkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error) { + if argv == nil { + return 0, errors.New("exec: no argv") + } + + if len(argv) == 0 { + return 0, errors.New("exec: no argv") + } + + if attr == nil { + attr = new(ProcAttr) + } + + p, err := fork() + pid = int(p) + + if err != nil { + return 0, err + } + + // else code runs in child, which then should exec the new process + err = execve(argv0, argv, attr.Env) + if err != nil { + // exec failed + return 0, err + } + // 3. TODO: use pipes to communicate back child status + return pid, nil +} + +// In Golang, the idiomatic way to create a new process is to use the StartProcess function. +// Since the Model of operating system processes in tinygo differs from the one in Golang, we need to implement the StartProcess function differently. +// The startProcess function is a wrapper around the forkExec function, which is a wrapper around the fork and execve system calls. +// The StartProcess function creates a new process by forking the current process and then calling execve to replace the current process with the new process. +// It thereby replaces the newly created process with the specified command and arguments. +func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err error) { + if attr != nil { + if attr.Dir != "" { + return nil, ErrNotImplementedDir + } + + if attr.Sys != nil { + return nil, ErrNotImplementedSys + } + + if len(attr.Files) != 0 { + return nil, ErrNotImplementedFiles + } + } + + pid, err := forkExec(name, argv, attr) + if err != nil { + return nil, err + } + + return findProcess(pid) +} diff --git a/src/os/exec_linux_test.go b/src/os/exec_linux_test.go new file mode 100644 index 0000000000..34f1fef983 --- /dev/null +++ b/src/os/exec_linux_test.go @@ -0,0 +1,78 @@ +//go:build linux && !baremetal && !tinygo.wasm + +package os_test + +import ( + "errors" + . "os" + "runtime" + "syscall" + "testing" +) + +// Test the functionality of the forkExec function, which is used to fork and exec a new process. +// This test is not run on Windows, as forkExec is not supported on Windows. +// This test is not run on Plan 9, as forkExec is not supported on Plan 9. +func TestForkExec(t *testing.T) { + if runtime.GOOS != "linux" { + t.Logf("skipping test on %s", runtime.GOOS) + return + } + + proc, err := StartProcess("/bin/echo", []string{"hello", "world"}, &ProcAttr{}) + if !errors.Is(err, nil) { + t.Fatalf("forkExec failed: %v", err) + } + + if proc == nil { + t.Fatalf("proc is nil") + } + + if proc.Pid == 0 { + t.Fatalf("forkExec failed: new process has pid 0") + } +} + +func TestForkExecErrNotExist(t *testing.T) { + proc, err := StartProcess("invalid", []string{"invalid"}, &ProcAttr{}) + if !errors.Is(err, ErrNotExist) { + t.Fatalf("wanted ErrNotExist, got %s\n", err) + } + + if proc != nil { + t.Fatalf("wanted nil, got %v\n", proc) + } +} + +func TestForkExecProcDir(t *testing.T) { + proc, err := StartProcess("/bin/echo", []string{"hello", "world"}, &ProcAttr{Dir: "dir"}) + if !errors.Is(err, ErrNotImplementedDir) { + t.Fatalf("wanted ErrNotImplementedDir, got %v\n", err) + } + + if proc != nil { + t.Fatalf("wanted nil, got %v\n", proc) + } +} + +func TestForkExecProcSys(t *testing.T) { + proc, err := StartProcess("/bin/echo", []string{"hello", "world"}, &ProcAttr{Sys: &syscall.SysProcAttr{}}) + if !errors.Is(err, ErrNotImplementedSys) { + t.Fatalf("wanted ErrNotImplementedSys, got %v\n", err) + } + + if proc != nil { + t.Fatalf("wanted nil, got %v\n", proc) + } +} + +func TestForkExecProcFiles(t *testing.T) { + proc, err := StartProcess("/bin/echo", []string{"hello", "world"}, &ProcAttr{Files: []*File{}}) + if !errors.Is(err, ErrNotImplementedFiles) { + t.Fatalf("wanted ErrNotImplementedFiles, got %v\n", err) + } + + if proc != nil { + t.Fatalf("wanted nil, got %v\n", proc) + } +} diff --git a/src/os/exec_other.go b/src/os/exec_other.go new file mode 100644 index 0000000000..5494f08968 --- /dev/null +++ b/src/os/exec_other.go @@ -0,0 +1,27 @@ +//go:build (!aix && !android && !freebsd && !linux && !netbsd && !openbsd && !plan9 && !solaris) || baremetal || tinygo.wasm + +package os + +import "syscall" + +var ( + Interrupt Signal = syscall.SIGINT + Kill Signal = syscall.SIGKILL +) + +func findProcess(pid int) (*Process, error) { + return &Process{Pid: pid}, nil +} + +func (p *Process) release() error { + p.Pid = -1 + return nil +} + +func forkExec(_ string, _ []string, _ *ProcAttr) (pid int, err error) { + return 0, ErrNotImplemented +} + +func startProcess(_ string, _ []string, _ *ProcAttr) (proc *Process, err error) { + return &Process{Pid: 0}, ErrNotImplemented +} diff --git a/src/os/exec_posix.go b/src/os/exec_posix.go deleted file mode 100644 index 720368572b..0000000000 --- a/src/os/exec_posix.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build aix || darwin || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris || wasip1 || wasip2 || windows - -package os - -import ( - "runtime" - "syscall" -) - -// The only signal values guaranteed to be present in the os package on all -// systems are os.Interrupt (send the process an interrupt) and os.Kill (force -// the process to exit). On Windows, sending os.Interrupt to a process with -// os.Process.Signal is not implemented; it will return an error instead of -// sending a signal. -var ( - Interrupt Signal = syscall.SIGINT - Kill Signal = syscall.SIGKILL -) - -// Keep compatible with golang and always succeed and return new proc with pid on Linux. -func findProcess(pid int) (*Process, error) { - return &Process{Pid: pid}, nil -} - -func (p *Process) release() error { - // NOOP for unix. - p.Pid = -1 - // no need for a finalizer anymore - runtime.SetFinalizer(p, nil) - return nil -} diff --git a/src/os/osexec.go b/src/os/osexec.go new file mode 100644 index 0000000000..65e3ab6952 --- /dev/null +++ b/src/os/osexec.go @@ -0,0 +1,58 @@ +//go:build linux && !baremetal && !tinygo.wasm + +package os + +import ( + "syscall" + "unsafe" +) + +func fork() (pid int32, err error) { + pid = libc_fork() + if pid != 0 { + if errno := *libc_errno(); errno != 0 { + err = syscall.Errno(*libc_errno()) + } + } + return +} + +// the golang standard library does not expose interfaces for execve and fork, so we define them here the same way via the libc wrapper +func execve(pathname string, argv []string, envv []string) error { + argv0 := cstring(pathname) + + // transform argv and envv into the format expected by execve + argv1 := make([]*byte, len(argv)+1) + for i, arg := range argv { + argv1[i] = &cstring(arg)[0] + } + argv1[len(argv)] = nil + + env1 := make([]*byte, len(envv)+1) + for i, env := range envv { + env1[i] = &cstring(env)[0] + } + env1[len(envv)] = nil + + ret, _, err := syscall.Syscall(syscall.SYS_EXECVE, uintptr(unsafe.Pointer(&argv0[0])), uintptr(unsafe.Pointer(&argv1[0])), uintptr(unsafe.Pointer(&env1[0]))) + if int(ret) != 0 { + return err + } + + return nil +} + +func cstring(s string) []byte { + data := make([]byte, len(s)+1) + copy(data, s) + // final byte should be zero from the initial allocation + return data +} + +//export fork +func libc_fork() int32 + +// Internal musl function to get the C errno pointer. +// +//export __errno_location +func libc_errno() *int32 diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index 67cf6681f7..8b032d3831 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -233,6 +233,11 @@ func (w WaitStatus) Continued() bool { return false } func (w WaitStatus) StopSignal() Signal { return 0 } func (w WaitStatus) TrapCause() int { return 0 } +// since rusage is quite a big struct and we stub it out anyway no need to define it here +func Wait4(pid int, wstatus *WaitStatus, options int, rusage uintptr) (wpid int, err error) { + return 0, ENOSYS // TODO +} + func Getenv(key string) (value string, found bool) { data := cstring(key) raw := libc_getenv(&data[0]) diff --git a/src/syscall/syscall_unix.go b/src/syscall/syscall_unix.go index 23d81fb891..b5b8f4eb78 100644 --- a/src/syscall/syscall_unix.go +++ b/src/syscall/syscall_unix.go @@ -1,3 +1,5 @@ +//go:build linux || unix + package syscall func Exec(argv0 string, argv []string, envv []string) (err error) From a6c4287b4d2dc8c951227820a1dbc69d7e26b689 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 30 Oct 2024 19:24:47 +0100 Subject: [PATCH 261/444] runtime: don't call sleepTicks with a negative duration There are rare cases where this can happen, see for example https://github.com/tinygo-org/tinygo/issues/4568 --- builder/sizes_test.go | 6 +++--- src/runtime/scheduler.go | 16 +++++++++------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/builder/sizes_test.go b/builder/sizes_test.go index 8b669462a5..dd89609e71 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -41,9 +41,9 @@ func TestBinarySize(t *testing.T) { // This is a small number of very diverse targets that we want to test. tests := []sizeTest{ // microcontrollers - {"hifive1b", "examples/echo", 4568, 280, 0, 2268}, - {"microbit", "examples/serial", 2868, 388, 8, 2272}, - {"wioterminal", "examples/pininterrupt", 6104, 1484, 116, 6832}, + {"hifive1b", "examples/echo", 4580, 280, 0, 2268}, + {"microbit", "examples/serial", 2888, 388, 8, 2272}, + {"wioterminal", "examples/pininterrupt", 6124, 1484, 116, 6832}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the diff --git a/src/runtime/scheduler.go b/src/runtime/scheduler.go index 3f726a0641..203e954c36 100644 --- a/src/runtime/scheduler.go +++ b/src/runtime/scheduler.go @@ -230,13 +230,15 @@ func scheduler(returnAtDeadlock bool) { println("--- timer waiting:", tim, tim.whenTicks()) } } - sleepTicks(timeLeft) - if asyncScheduler { - // The sleepTicks function above only sets a timeout at which - // point the scheduler will be called again. It does not really - // sleep. So instead of sleeping, we return and expect to be - // called again. - break + if timeLeft > 0 { + sleepTicks(timeLeft) + if asyncScheduler { + // The sleepTicks function above only sets a timeout at + // which point the scheduler will be called again. It does + // not really sleep. So instead of sleeping, we return and + // expect to be called again. + break + } } continue } From 8ff97bdedd5dc420462943dffe662bae57b6d567 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 30 Oct 2024 19:29:12 +0100 Subject: [PATCH 262/444] runtime: remove unnecessary check for negative sleepTicks duration This is now fixed for every target in the previous commit. Also see: https://github.com/tinygo-org/tinygo/pull/4239 --- src/runtime/runtime_mimxrt1062_time.go | 22 ++++++++++------------ src/runtime/runtime_rp2040.go | 4 ---- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/runtime/runtime_mimxrt1062_time.go b/src/runtime/runtime_mimxrt1062_time.go index e3779052ff..feaf68dbd4 100644 --- a/src/runtime/runtime_mimxrt1062_time.go +++ b/src/runtime/runtime_mimxrt1062_time.go @@ -119,19 +119,17 @@ func ticks() timeUnit { } func sleepTicks(duration timeUnit) { - if duration >= 0 { - curr := ticks() - last := curr + duration // 64-bit overflow unlikely - for curr < last { - cycles := timeUnit((last - curr) / pitCyclesPerMicro) - if cycles > 0xFFFFFFFF { - cycles = 0xFFFFFFFF - } - if !timerSleep(uint32(cycles)) { - return // return early due to interrupt - } - curr = ticks() + curr := ticks() + last := curr + duration // 64-bit overflow unlikely + for curr < last { + cycles := timeUnit((last - curr) / pitCyclesPerMicro) + if cycles > 0xFFFFFFFF { + cycles = 0xFFFFFFFF } + if !timerSleep(uint32(cycles)) { + return // return early due to interrupt + } + curr = ticks() } } diff --git a/src/runtime/runtime_rp2040.go b/src/runtime/runtime_rp2040.go index fa048117b9..1d36a771e5 100644 --- a/src/runtime/runtime_rp2040.go +++ b/src/runtime/runtime_rp2040.go @@ -31,10 +31,6 @@ func nanosecondsToTicks(ns int64) timeUnit { } func sleepTicks(d timeUnit) { - if d <= 0 { - return - } - if hasScheduler { // With scheduler, sleepTicks may return early if an interrupt or // event fires - so scheduler can schedule any go routines now From 04a7baec3ede3d91243cfc73916b0b237a93e3fe Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 30 Oct 2024 12:12:10 +0100 Subject: [PATCH 263/444] wasm: support `//go:wasmexport` functions after a call to `time.Sleep` This fixes a bug where `//go:wasmexport` functions would not be allowed anymore after a call to `time.Sleep` (when using `-buildmode=default`). --- src/runtime/runtime_wasmentry.go | 34 ++++++++++-------------------- src/runtime/scheduler.go | 4 ++-- src/runtime/scheduler_any.go | 2 +- testdata/wasmexport-noscheduler.go | 6 ++++++ 4 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/runtime/runtime_wasmentry.go b/src/runtime/runtime_wasmentry.go index ff7b0c1198..756db50955 100644 --- a/src/runtime/runtime_wasmentry.go +++ b/src/runtime/runtime_wasmentry.go @@ -14,12 +14,13 @@ import ( // This is the _start entry point, when using -buildmode=default. func wasmEntryCommand() { // These need to be initialized early so that the heap can be initialized. + initializeCalled = true heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) - wasmExportState = wasmExportStateInMain run() - wasmExportState = wasmExportStateExited - beforeExit() + if mainExited { + beforeExit() + } } // This is the _initialize entry point, when using -buildmode=c-shared. @@ -27,6 +28,8 @@ func wasmEntryReactor() { // This function is called before any //go:wasmexport functions are called // to initialize everything. It must not block. + initializeCalled = true + // Initialize the heap. heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) @@ -38,38 +41,23 @@ func wasmEntryReactor() { // goroutine. go func() { initAll() - wasmExportState = wasmExportStateReactor }() scheduler(true) - if wasmExportState != wasmExportStateReactor { - // Unlikely, but if package initializers do something blocking (like - // time.Sleep()), that's a bug. - runtimePanic("package initializer blocks") - } } else { // There are no goroutines (except for the main one, if you can call it // that), so we can just run all the package initializers. initAll() - wasmExportState = wasmExportStateReactor } } -// Track which state we're in: before (or during) init, running inside -// main.main, after main.main returned, or reactor mode (after init). -var wasmExportState uint8 - -const ( - wasmExportStateInit = iota - wasmExportStateInMain - wasmExportStateExited - wasmExportStateReactor -) +// Whether the runtime was initialized by a call to _initialize or _start. +var initializeCalled bool func wasmExportCheckRun() { - switch wasmExportState { - case wasmExportStateInit: + switch { + case !initializeCalled: runtimePanic("//go:wasmexport function called before runtime initialization") - case wasmExportStateExited: + case mainExited: runtimePanic("//go:wasmexport function called after main.main returned") } } diff --git a/src/runtime/scheduler.go b/src/runtime/scheduler.go index 203e954c36..8ba461e4db 100644 --- a/src/runtime/scheduler.go +++ b/src/runtime/scheduler.go @@ -21,7 +21,7 @@ const schedulerDebug = false // queue a new scheduler invocation using setTimeout. const asyncScheduler = GOOS == "js" -var schedulerDone bool +var mainExited bool // Queues used by the scheduler. var ( @@ -166,7 +166,7 @@ func removeTimer(tim *timer) bool { func scheduler(returnAtDeadlock bool) { // Main scheduler loop. var now timeUnit - for !schedulerDone { + for !mainExited { scheduleLog("") scheduleLog(" schedule") if sleepQueue != nil || timerQueue != nil { diff --git a/src/runtime/scheduler_any.go b/src/runtime/scheduler_any.go index 5e969f84ff..2e7861a156 100644 --- a/src/runtime/scheduler_any.go +++ b/src/runtime/scheduler_any.go @@ -23,7 +23,7 @@ func run() { go func() { initAll() callMain() - schedulerDone = true + mainExited = true }() scheduler(false) } diff --git a/testdata/wasmexport-noscheduler.go b/testdata/wasmexport-noscheduler.go index b996b2faa0..cc99d7136a 100644 --- a/testdata/wasmexport-noscheduler.go +++ b/testdata/wasmexport-noscheduler.go @@ -1,5 +1,7 @@ package main +import "time" + func init() { println("called init") } @@ -8,6 +10,10 @@ func init() { func callTestMain() func main() { + // Check that exported functions can still be called after calling + // time.Sleep. + time.Sleep(time.Millisecond) + // main.main is not used when using -buildmode=c-shared. callTestMain() } From ceb789198643d372cceb162581f059940b601578 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 30 Oct 2024 12:24:21 +0100 Subject: [PATCH 264/444] wasm: correctly return from run() in wasm_exec.js Instead of hanging forever, it should return the exit code from os.Exit. --- main_test.go | 54 ++++++++++++++++++++++- src/runtime/runtime_tinygowasm.go | 11 +++-- src/runtime/runtime_tinygowasm_unknown.go | 4 ++ src/runtime/runtime_tinygowasmp2.go | 7 +++ src/runtime/runtime_wasip1.go | 4 -- src/runtime/runtime_wasip2.go | 3 -- src/runtime/runtime_wasm_js.go | 4 -- src/runtime/runtime_wasm_unknown.go | 5 ++- src/runtime/runtime_wasmentry.go | 3 +- targets/wasm_exec.js | 46 +++++++++++++------ testdata/wasmexit.go | 27 ++++++++++++ testdata/wasmexit.js | 35 +++++++++++++++ 12 files changed, 173 insertions(+), 30 deletions(-) create mode 100644 testdata/wasmexit.go create mode 100644 testdata/wasmexit.js diff --git a/main_test.go b/main_test.go index 723a522670..ecff134275 100644 --- a/main_test.go +++ b/main_test.go @@ -25,6 +25,7 @@ import ( "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" + "github.com/tetratelabs/wazero/sys" "github.com/tinygo-org/tinygo/builder" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/diagnostics" @@ -686,7 +687,14 @@ func TestWasmExport(t *testing.T) { if tc.command { // Call _start (the entry point), which calls // tester.callTestMain, which then runs all the tests. - mustCall(mod.ExportedFunction("_start").Call(ctx)) + _, err := mod.ExportedFunction("_start").Call(ctx) + if err != nil { + if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() == 0 { + // Exited with code 0. Nothing to worry about. + } else { + t.Error("failed to run _start:", err) + } + } } else { // Run the _initialize call, because this is reactor mode wasm. mustCall(mod.ExportedFunction("_initialize").Call(ctx)) @@ -772,12 +780,56 @@ func TestWasmExportJS(t *testing.T) { } } +// Test whether Go.run() (in wasm_exec.js) normally returns and returns the +// right exit code. +func TestWasmExit(t *testing.T) { + t.Parallel() + + type testCase struct { + name string + output string + } + + tests := []testCase{ + {name: "normal", output: "exit code: 0\n"}, + {name: "exit-0", output: "exit code: 0\n"}, + {name: "exit-0-sleep", output: "slept\nexit code: 0\n"}, + {name: "exit-1", output: "exit code: 1\n"}, + {name: "exit-1-sleep", output: "slept\nexit code: 1\n"}, + } + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + options := optionsFromTarget("wasm", sema) + buildConfig, err := builder.NewConfig(&options) + if err != nil { + t.Fatal(err) + } + buildConfig.Target.Emulator = "node testdata/wasmexit.js {}" + output := &bytes.Buffer{} + _, err = buildAndRun("testdata/wasmexit.go", buildConfig, output, []string{tc.name}, nil, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error { + return cmd.Run() + }) + if err != nil { + t.Error(err) + } + expected := "wasmexit test: " + tc.name + "\n" + tc.output + checkOutputData(t, []byte(expected), output.Bytes()) + }) + } +} + // Check whether the output of a test equals the expected output. func checkOutput(t *testing.T, filename string, actual []byte) { expectedOutput, err := os.ReadFile(filename) if err != nil { t.Fatal("could not read output file:", err) } + checkOutputData(t, expectedOutput, actual) +} + +func checkOutputData(t *testing.T, expectedOutput, actual []byte) { expectedOutput = bytes.ReplaceAll(expectedOutput, []byte("\r\n"), []byte("\n")) actual = bytes.ReplaceAll(actual, []byte("\r\n"), []byte("\n")) diff --git a/src/runtime/runtime_tinygowasm.go b/src/runtime/runtime_tinygowasm.go index f791ffacdf..7bc65e9c44 100644 --- a/src/runtime/runtime_tinygowasm.go +++ b/src/runtime/runtime_tinygowasm.go @@ -80,12 +80,17 @@ func abort() { //go:linkname syscall_Exit syscall.Exit func syscall_Exit(code int) { - // TODO: should we call __stdio_exit here? - // It's a low-level exit (syscall.Exit) so doing any libc stuff seems - // unexpected, but then where else should stdio buffers be flushed? + // Flush stdio buffers. + __stdio_exit() + + // Exit the program. proc_exit(uint32(code)) } +func mainReturnExit() { + syscall_Exit(0) +} + // TinyGo does not yet support any form of parallelism on WebAssembly, so these // can be left empty. diff --git a/src/runtime/runtime_tinygowasm_unknown.go b/src/runtime/runtime_tinygowasm_unknown.go index 39caa245a2..e426f36ff8 100644 --- a/src/runtime/runtime_tinygowasm_unknown.go +++ b/src/runtime/runtime_tinygowasm_unknown.go @@ -31,6 +31,10 @@ func abort() { //go:linkname syscall_Exit syscall.Exit func syscall_Exit(code int) { + // Because this is the "unknown" target we can't call an exit function. + // But we also can't just return since the program will likely expect this + // function to never return. So we panic instead. + runtimePanic("unsupported: syscall.Exit") } // There is not yet any support for any form of parallelism on WebAssembly, so these diff --git a/src/runtime/runtime_tinygowasmp2.go b/src/runtime/runtime_tinygowasmp2.go index eb3c507fd2..70b5a6d11e 100644 --- a/src/runtime/runtime_tinygowasmp2.go +++ b/src/runtime/runtime_tinygowasmp2.go @@ -60,6 +60,13 @@ func syscall_Exit(code int) { exit.Exit(code != 0) } +func mainReturnExit() { + // WASIp2 does not use _start, instead it uses _initialize and a custom + // WASIp2-specific main function. So this should never be called in + // practice. + runtimePanic("unreachable: _start was called") +} + // TinyGo does not yet support any form of parallelism on WebAssembly, so these // can be left empty. diff --git a/src/runtime/runtime_wasip1.go b/src/runtime/runtime_wasip1.go index ad66b0d860..92adb9bef6 100644 --- a/src/runtime/runtime_wasip1.go +++ b/src/runtime/runtime_wasip1.go @@ -91,10 +91,6 @@ func ticks() timeUnit { return timeUnit(nano) } -func beforeExit() { - __stdio_exit() -} - // Implementations of WASI APIs //go:wasmimport wasi_snapshot_preview1 args_get diff --git a/src/runtime/runtime_wasip2.go b/src/runtime/runtime_wasip2.go index ba8f52100b..296f4a45bd 100644 --- a/src/runtime/runtime_wasip2.go +++ b/src/runtime/runtime_wasip2.go @@ -52,6 +52,3 @@ func sleepTicks(d timeUnit) { func ticks() timeUnit { return timeUnit(monotonicclock.Now()) } - -func beforeExit() { -} diff --git a/src/runtime/runtime_wasm_js.go b/src/runtime/runtime_wasm_js.go index b49ffd15d6..21a0bc1055 100644 --- a/src/runtime/runtime_wasm_js.go +++ b/src/runtime/runtime_wasm_js.go @@ -32,7 +32,3 @@ func sleepTicks(d timeUnit) //go:wasmimport gojs runtime.ticks func ticks() timeUnit - -func beforeExit() { - __stdio_exit() -} diff --git a/src/runtime/runtime_wasm_unknown.go b/src/runtime/runtime_wasm_unknown.go index 846b95d2a8..27e2485791 100644 --- a/src/runtime/runtime_wasm_unknown.go +++ b/src/runtime/runtime_wasm_unknown.go @@ -34,5 +34,8 @@ func ticks() timeUnit { return timeUnit(0) } -func beforeExit() { +func mainReturnExit() { + // Don't exit explicitly here. We can't (there is no environment with an + // exit call) but also it's not needed. We can just let _start and main.main + // return to the caller. } diff --git a/src/runtime/runtime_wasmentry.go b/src/runtime/runtime_wasmentry.go index 756db50955..1d2cec6cae 100644 --- a/src/runtime/runtime_wasmentry.go +++ b/src/runtime/runtime_wasmentry.go @@ -19,7 +19,8 @@ func wasmEntryCommand() { heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) run() if mainExited { - beforeExit() + // To make sure wasm_exec.js knows that we've exited, exit explicitly. + mainReturnExit() } } diff --git a/targets/wasm_exec.js b/targets/wasm_exec.js index c430cc2b23..d6270adbfd 100644 --- a/targets/wasm_exec.js +++ b/targets/wasm_exec.js @@ -132,6 +132,7 @@ const decoder = new TextDecoder("utf-8"); let reinterpretBuf = new DataView(new ArrayBuffer(8)); var logLine = []; + const wasmExit = {}; // thrown to exit via proc_exit (not an error) global.Go = class { constructor() { @@ -270,14 +271,11 @@ fd_close: () => 0, // dummy fd_fdstat_get: () => 0, // dummy fd_seek: () => 0, // dummy - "proc_exit": (code) => { - if (global.process) { - // Node.js - process.exit(code); - } else { - // Can't exit in a browser. - throw 'trying to exit with code ' + code; - } + proc_exit: (code) => { + this.exited = true; + this.exitCode = code; + this._resolveExitPromise(); + throw wasmExit; }, random_get: (bufPtr, bufLen) => { crypto.getRandomValues(loadSlice(bufPtr, bufLen)); @@ -293,7 +291,14 @@ // func sleepTicks(timeout float64) "runtime.sleepTicks": (timeout) => { // Do not sleep, only reactivate scheduler after the given timeout. - setTimeout(this._inst.exports.go_scheduler, timeout); + setTimeout(() => { + if (this.exited) return; + try { + this._inst.exports.go_scheduler(); + } catch (e) { + if (e !== wasmExit) throw e; + } + }, timeout); }, // func finalizeRef(v ref) @@ -465,12 +470,23 @@ this._ids = new Map(); // mapping from JS values to reference ids this._idPool = []; // unused ids that have been garbage collected this.exited = false; // whether the Go program has exited + this.exitCode = 0; if (this._inst.exports._start) { - this._inst.exports._start(); + let exitPromise = new Promise((resolve, reject) => { + this._resolveExitPromise = resolve; + }); + + // Run program, but catch the wasmExit exception that's thrown + // to return back here. + try { + this._inst.exports._start(); + } catch (e) { + if (e !== wasmExit) throw e; + } - // TODO: wait until the program exists. - await new Promise(() => {}); + await exitPromise; + return this.exitCode; } else { this._inst.exports._initialize(); } @@ -480,7 +496,11 @@ if (this.exited) { throw new Error("Go program has already exited"); } - this._inst.exports.resume(); + try { + this._inst.exports.resume(); + } catch (e) { + if (e !== wasmExit) throw e; + } if (this.exited) { this._resolveExitPromise(); } diff --git a/testdata/wasmexit.go b/testdata/wasmexit.go new file mode 100644 index 0000000000..cbf5878450 --- /dev/null +++ b/testdata/wasmexit.go @@ -0,0 +1,27 @@ +package main + +import ( + "os" + "time" +) + +func main() { + println("wasmexit test:", os.Args[1]) + switch os.Args[1] { + case "normal": + return + case "exit-0": + os.Exit(0) + case "exit-0-sleep": + time.Sleep(time.Millisecond) + println("slept") + os.Exit(0) + case "exit-1": + os.Exit(1) + case "exit-1-sleep": + time.Sleep(time.Millisecond) + println("slept") + os.Exit(1) + } + println("unknown wasmexit test") +} diff --git a/testdata/wasmexit.js b/testdata/wasmexit.js new file mode 100644 index 0000000000..b41991e3a7 --- /dev/null +++ b/testdata/wasmexit.js @@ -0,0 +1,35 @@ +require('../targets/wasm_exec.js'); + +function runTests() { + let testCall = (name, params, expected) => { + let result = go._inst.exports[name].apply(null, params); + if (result !== expected) { + console.error(`${name}(...${params}): expected result ${expected}, got ${result}`); + } + } + + // These are the same tests as in TestWasmExport. + testCall('hello', [], undefined); + testCall('add', [3, 5], 8); + testCall('add', [7, 9], 16); + testCall('add', [6, 1], 7); + testCall('reentrantCall', [2, 3], 5); + testCall('reentrantCall', [1, 8], 9); +} + +let go = new Go(); +go.importObject.tester = { + callOutside: (a, b) => { + return go._inst.exports.add(a, b); + }, + callTestMain: () => { + runTests(); + }, +}; +WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then(async (result) => { + let value = await go.run(result.instance); + console.log('exit code:', value); +}).catch((err) => { + console.error(err); + process.exit(1); +}); From c728e031df92115cea14099b1c672c07f4d14ff3 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 3 Nov 2024 10:03:56 +0100 Subject: [PATCH 265/444] goenv: read git hash embedded in the binary The git hash that's part of `tinygo version` is now read from the binary itself instead of embedding it with a `-ldflags` flag. This means it is also present when building TinyGo using `go build` or `go install`. --- GNUmakefile | 2 +- goenv/version.go | 24 ++++++++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 155f5c5d57..e66b6af29f 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -291,7 +291,7 @@ endif tinygo: ## Build the TinyGo compiler @if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " $(MAKE) llvm-source"; echo " $(MAKE) $(LLVM_BUILDDIR)"; exit 1; fi - CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" -ldflags="-X github.com/tinygo-org/tinygo/goenv.GitSha1=`git rev-parse --short HEAD`" . + CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" . test: wasi-libc check-nodejs-version CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags "byollvm osusergo" $(GOTESTPKGS) diff --git a/goenv/version.go b/goenv/version.go index 5b71820f2d..158c3caf20 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io" + "runtime/debug" "strings" ) @@ -11,22 +12,29 @@ import ( // Update this value before release of new version of software. const version = "0.35.0-dev" -var ( - // This variable is set at build time using -ldflags parameters. - // See: https://stackoverflow.com/a/11355611 - GitSha1 string -) - // Return TinyGo version, either in the form 0.30.0 or as a development version // (like 0.30.0-dev-abcd012). func Version() string { v := version - if strings.HasSuffix(version, "-dev") && GitSha1 != "" { - v += "-" + GitSha1 + if strings.HasSuffix(version, "-dev") { + if hash := readGitHash(); hash != "" { + v += "-" + hash + } } return v } +func readGitHash() string { + if info, ok := debug.ReadBuildInfo(); ok { + for _, setting := range info.Settings { + if setting.Key == "vcs.revision" { + return setting.Value[:8] + } + } + } + return "" +} + // GetGorootVersion returns the major and minor version for a given GOROOT path. // If the goroot cannot be determined, (0, 0) is returned. func GetGorootVersion() (major, minor int, err error) { From e98fea0bba058b0a46dd1874d9a9f516bcc19304 Mon Sep 17 00:00:00 2001 From: Ben Krieger Date: Sun, 13 Oct 2024 10:23:23 -0400 Subject: [PATCH 266/444] reflect: add Value.Clear; support anytype->interface{}, Slice->(*)Array in Value.Convert --- src/reflect/type.go | 17 +++- src/reflect/value.go | 72 ++++++++++++--- src/reflect/value_test.go | 180 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 257 insertions(+), 12 deletions(-) diff --git a/src/reflect/type.go b/src/reflect/type.go index b18a1a9d4f..05915350af 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -424,8 +424,8 @@ type rawType struct { meta uint8 // metadata byte, contains kind and flags (see constants above) } -// All types that have an element type: named, chan, slice, array, map (but not -// pointer because it doesn't have ptrTo). +// All types that have an element type: named, chan, slice, array, map, interface +// (but not pointer because it doesn't have ptrTo). type elemType struct { rawType numMethod uint16 @@ -439,6 +439,12 @@ type ptrType struct { elem *rawType } +type interfaceType struct { + rawType + ptrTo *rawType + // TODO: methods +} + type arrayType struct { rawType numMethod uint16 @@ -995,6 +1001,10 @@ func (t *rawType) AssignableTo(u Type) bool { return true } + if t.underlying() == u.(*rawType).underlying() && (!t.isNamed() || !u.(*rawType).isNamed()) { + return true + } + if u.Kind() == Interface && u.NumMethod() == 0 { return true } @@ -1060,6 +1070,9 @@ func (t *rawType) NumMethod() int { return int((*ptrType)(unsafe.Pointer(t)).numMethod) case Struct: return int((*structType)(unsafe.Pointer(t)).numMethod) + case Interface: + //FIXME: Use len(methods) + return (*interfaceType)(unsafe.Pointer(t)).ptrTo.NumMethod() } // Other types have no methods attached. Note we don't panic here. diff --git a/src/reflect/value.go b/src/reflect/value.go index d1f8cb2f76..78453be343 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -689,6 +689,25 @@ func (v Value) Cap() int { } } +//go:linkname mapclear runtime.hashmapClear +func mapclear(p unsafe.Pointer) + +// Clear clears the contents of a map or zeros the contents of a slice +// +// It panics if v's Kind is not Map or Slice. +func (v Value) Clear() { + switch v.typecode.Kind() { + case Map: + mapclear(v.pointer()) + case Slice: + hdr := (*sliceHeader)(v.value) + elemSize := v.typecode.underlying().elem().Size() + memzero(hdr.data, elemSize*hdr.len) + default: + panic(&ValueError{Method: "Clear", Kind: v.Kind()}) + } +} + // NumField returns the number of fields of this struct. It panics for other // value types. func (v Value) NumField() int { @@ -888,6 +907,9 @@ func (v Value) Index(i int) Value { } func (v Value) NumMethod() int { + if v.typecode == nil { + panic(&ValueError{Method: "reflect.Value.NumMethod", Kind: Invalid}) + } return v.typecode.NumMethod() } @@ -1062,7 +1084,7 @@ func (v Value) Set(x Value) { v.checkAddressable() v.checkRO() if !x.typecode.AssignableTo(v.typecode) { - panic("reflect: cannot set") + panic("reflect.Value.Set: value of type " + x.typecode.String() + " cannot be assigned to type " + v.typecode.String()) } if v.typecode.Kind() == Interface && x.typecode.Kind() != Interface { @@ -1240,7 +1262,9 @@ func (v Value) OverflowUint(x uint64) bool { } func (v Value) CanConvert(t Type) bool { - panic("unimplemented: (reflect.Value).CanConvert()") + // TODO: Optimize this to not actually perform a conversion + _, ok := convertOp(v, t) + return ok } func (v Value) Convert(t Type) Value { @@ -1262,6 +1286,15 @@ func convertOp(src Value, typ Type) (Value, bool) { }, true } + if rtype := typ.(*rawType); rtype.Kind() == Interface && rtype.NumMethod() == 0 { + iface := composeInterface(unsafe.Pointer(src.typecode), src.value) + return Value{ + typecode: rtype, + value: unsafe.Pointer(&iface), + flags: valueFlagExported, + }, true + } + switch src.Kind() { case Int, Int8, Int16, Int32, Int64: switch rtype := typ.(*rawType); rtype.Kind() { @@ -1302,14 +1335,33 @@ func convertOp(src Value, typ Type) (Value, bool) { */ case Slice: - if typ.Kind() == String && !src.typecode.elem().isNamed() { - rtype := typ.(*rawType) - - switch src.Type().Elem().Kind() { - case Uint8: - return cvtBytesString(src, rtype), true - case Int32: - return cvtRunesString(src, rtype), true + switch rtype := typ.(*rawType); rtype.Kind() { + case Array: + if src.typecode.elem() == rtype.elem() && rtype.Len() <= src.Len() { + return Value{ + typecode: rtype, + value: (*sliceHeader)(src.value).data, + flags: src.flags | valueFlagIndirect, + }, true + } + case Pointer: + if rtype.Elem().Kind() == Array { + if src.typecode.elem() == rtype.elem().elem() && rtype.elem().Len() <= src.Len() { + return Value{ + typecode: rtype, + value: (*sliceHeader)(src.value).data, + flags: src.flags & (valueFlagExported | valueFlagRO), + }, true + } + } + case String: + if !src.typecode.elem().isNamed() { + switch src.Type().Elem().Kind() { + case Uint8: + return cvtBytesString(src, rtype), true + case Int32: + return cvtRunesString(src, rtype), true + } } } diff --git a/src/reflect/value_test.go b/src/reflect/value_test.go index 508b358ad9..a32ba4fa9d 100644 --- a/src/reflect/value_test.go +++ b/src/reflect/value_test.go @@ -1,6 +1,7 @@ package reflect_test import ( + "bytes" "encoding/base64" . "reflect" "sort" @@ -599,6 +600,29 @@ func TestAssignableTo(t *testing.T) { if got, want := refa.Interface().(int), 4; got != want { t.Errorf("AssignableTo / Set failed, got %v, want %v", got, want) } + + b := []byte{0x01, 0x02} + refb := ValueOf(&b).Elem() + refb.Set(ValueOf([]byte{0x02, 0x03})) + if got, want := refb.Interface().([]byte), []byte{0x02, 0x03}; !bytes.Equal(got, want) { + t.Errorf("AssignableTo / Set failed, got %v, want %v", got, want) + } + + type bstr []byte + + c := bstr{0x01, 0x02} + refc := ValueOf(&c).Elem() + refc.Set(ValueOf([]byte{0x02, 0x03})) + if got, want := refb.Interface().([]byte), []byte{0x02, 0x03}; !bytes.Equal(got, want) { + t.Errorf("AssignableTo / Set failed, got %v, want %v", got, want) + } + + d := []byte{0x01, 0x02} + refd := ValueOf(&d).Elem() + refd.Set(ValueOf(bstr{0x02, 0x03})) + if got, want := refb.Interface().([]byte), []byte{0x02, 0x03}; !bytes.Equal(got, want) { + t.Errorf("AssignableTo / Set failed, got %v, want %v", got, want) + } } func TestConvert(t *testing.T) { @@ -624,6 +648,162 @@ func TestConvert(t *testing.T) { } } +func TestConvertSliceToArrayOrArrayPointer(t *testing.T) { + s := make([]byte, 2, 4) + // a0 := [0]byte(s) + // a1 := [1]byte(s[1:]) // a1[0] == s[1] + // a2 := [2]byte(s) // a2[0] == s[0] + // a4 := [4]byte(s) // panics: len([4]byte) > len(s) + + v := ValueOf(s).Convert(TypeFor[[0]byte]()) + if v.Kind() != Array || v.Type().Len() != 0 { + t.Error("Convert([]byte -> [0]byte)") + } + v = ValueOf(s[1:]).Convert(TypeFor[[1]byte]()) + if v.Kind() != Array || v.Type().Len() != 1 { + t.Error("Convert([]byte -> [1]byte)") + } + v = ValueOf(s).Convert(TypeFor[[2]byte]()) + if v.Kind() != Array || v.Type().Len() != 2 { + t.Error("Convert([]byte -> [2]byte)") + } + if ValueOf(s).CanConvert(TypeFor[[4]byte]()) { + t.Error("Converting a slice with len smaller than array to array should fail") + } + + // s0 := (*[0]byte)(s) // s0 != nil + // s1 := (*[1]byte)(s[1:]) // &s1[0] == &s[1] + // s2 := (*[2]byte)(s) // &s2[0] == &s[0] + // s4 := (*[4]byte)(s) // panics: len([4]byte) > len(s) + v = ValueOf(s).Convert(TypeFor[*[0]byte]()) + if v.Kind() != Pointer || v.Elem().Kind() != Array || v.Elem().Type().Len() != 0 { + t.Error("Convert([]byte -> *[0]byte)") + } + v = ValueOf(s[1:]).Convert(TypeFor[*[1]byte]()) + if v.Kind() != Pointer || v.Elem().Kind() != Array || v.Elem().Type().Len() != 1 { + t.Error("Convert([]byte -> *[1]byte)") + } + v = ValueOf(s).Convert(TypeFor[*[2]byte]()) + if v.Kind() != Pointer || v.Elem().Kind() != Array || v.Elem().Type().Len() != 2 { + t.Error("Convert([]byte -> *[2]byte)") + } + if ValueOf(s).CanConvert(TypeFor[*[4]byte]()) { + t.Error("Converting a slice with len smaller than array to array pointer should fail") + } + + // Test converting slices with backing arrays <= and >64bits + slice64 := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + array64 := ValueOf(slice64).Convert(TypeFor[[8]byte]()).Interface().([8]byte) + if !bytes.Equal(slice64, array64[:]) { + t.Errorf("converted array %x does not match backing array of slice %x", array64, slice64) + } + + slice72 := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09} + array72 := ValueOf(slice72).Convert(TypeFor[[9]byte]()).Interface().([9]byte) + if !bytes.Equal(slice72, array72[:]) { + t.Errorf("converted array %x does not match backing array of slice %x", array72, slice72) + } +} + +func TestConvertToEmptyInterface(t *testing.T) { + anyType := TypeFor[interface{}]() + + v := ValueOf(false).Convert(anyType) + if v.Kind() != Interface || v.NumMethod() > 0 { + t.Error("Convert(bool -> interface{})") + } + _ = v.Interface().(interface{}).(bool) + + v = ValueOf(int64(3)).Convert(anyType) + if v.Kind() != Interface || v.NumMethod() > 0 { + t.Error("Convert(int64 -> interface{})") + } + _ = v.Interface().(interface{}).(int64) + + v = ValueOf(struct{}{}).Convert(anyType) + if v.Kind() != Interface || v.NumMethod() > 0 { + t.Error("Convert(struct -> interface{})") + } + _ = v.Interface().(interface{}).(struct{}) + + v = ValueOf([]struct{}{}).Convert(anyType) + if v.Kind() != Interface || v.NumMethod() > 0 { + t.Error("Convert(slice -> interface{})") + } + _ = v.Interface().(interface{}).([]struct{}) + + v = ValueOf(map[string]string{"A": "B"}).Convert(anyType) + if v.Kind() != Interface || v.NumMethod() > 0 { + t.Error("Convert(map -> interface{})") + } + _ = v.Interface().(interface{}).(map[string]string) +} + +func TestClearSlice(t *testing.T) { + type stringSlice []string + for _, test := range []struct { + slice any + expect any + }{ + { + slice: []bool{true, false, true}, + expect: []bool{false, false, false}, + }, + { + slice: []byte{0x00, 0x01, 0x02, 0x03}, + expect: []byte{0x00, 0x00, 0x00, 0x00}, + }, + { + slice: [][]int{[]int{2, 1}, []int{3}, []int{}}, + expect: [][]int{nil, nil, nil}, + }, + { + slice: []stringSlice{stringSlice{"hello", "world"}, stringSlice{}, stringSlice{"goodbye"}}, + expect: []stringSlice{nil, nil, nil}, + }, + } { + v := ValueOf(test.slice) + expectLen, expectCap := v.Len(), v.Cap() + + v.Clear() + if len := v.Len(); len != expectLen { + t.Errorf("Clear(slice) altered len, got %d, expected %d", len, expectLen) + } + if cap := v.Cap(); cap != expectCap { + t.Errorf("Clear(slice) altered cap, got %d, expected %d", cap, expectCap) + } + if !DeepEqual(test.slice, test.expect) { + t.Errorf("Clear(slice) got %v, expected %v", test.slice, test.expect) + } + } +} + +func TestClearMap(t *testing.T) { + for _, test := range []struct { + m any + expect any + }{ + { + m: map[int]bool{1: true, 2: false, 3: true}, + expect: map[int]bool{}, + }, + { + m: map[string][]byte{"hello": []byte("world")}, + expect: map[string][]byte{}, + }, + } { + v := ValueOf(test.m) + + v.Clear() + if len := v.Len(); len != 0 { + t.Errorf("Clear(map) should set len to 0, got %d", len) + } + if !DeepEqual(test.m, test.expect) { + t.Errorf("Clear(map) got %v, expected %v", test.m, test.expect) + } + } +} + func TestIssue4040(t *testing.T) { var value interface{} = uint16(0) From 4f96a50fd0ba05f186403f8bcbf8e8871dcc2534 Mon Sep 17 00:00:00 2001 From: Ben Krieger Date: Fri, 25 Oct 2024 12:10:59 -0400 Subject: [PATCH 267/444] reflect: fix Copy of non-pointer array with size > 64bits --- src/reflect/value.go | 2 +- src/reflect/value_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/reflect/value.go b/src/reflect/value.go index 78453be343..69b57fd1cc 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -1712,7 +1712,7 @@ func buflen(v Value) (unsafe.Pointer, uintptr) { buf = hdr.data len = hdr.len case Array: - if v.isIndirect() { + if v.isIndirect() || v.typecode.Size() > unsafe.Sizeof(uintptr(0)) { buf = v.value } else { buf = unsafe.Pointer(&v.value) diff --git a/src/reflect/value_test.go b/src/reflect/value_test.go index a32ba4fa9d..4df9db4d19 100644 --- a/src/reflect/value_test.go +++ b/src/reflect/value_test.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/base64" . "reflect" + "slices" "sort" "strings" "testing" @@ -804,6 +805,36 @@ func TestClearMap(t *testing.T) { } } +func TestCopyArrayToSlice(t *testing.T) { + // Test copying array <=64 bits and >64bits + // See issue #4554 + a1 := [1]int64{1} + s1 := make([]int64, 1) + a2 := [2]int64{1, 2} + s2 := make([]int64, 2) + a8 := [8]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + s8 := make([]byte, 8) + a9 := [9]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09} + s9 := make([]byte, 9) + + Copy(ValueOf(s1), ValueOf(a1)) + if !slices.Equal(s1, a1[:]) { + t.Errorf("copied slice %x does not match input array %x", s1, a1[:]) + } + Copy(ValueOf(s2), ValueOf(a2)) + if !slices.Equal(s2, a2[:]) { + t.Errorf("copied slice %x does not match input array %x", s2, a2[:]) + } + Copy(ValueOf(s8), ValueOf(a8)) + if !bytes.Equal(s8, a8[:]) { + t.Errorf("copied slice %x does not match input array %x", s8, a8[:]) + } + Copy(ValueOf(s9), ValueOf(a9)) + if !bytes.Equal(s9, a9[:]) { + t.Errorf("copied slice %x does not match input array %x", s9, a9[:]) + } +} + func TestIssue4040(t *testing.T) { var value interface{} = uint16(0) From 91563cff1f91efd58671f7b8b4d9880424071c4a Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Wed, 13 Nov 2024 13:44:31 -0800 Subject: [PATCH 268/444] targets/wasm_exec: call process.exit() when go.run() returns --- targets/wasm_exec.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/targets/wasm_exec.js b/targets/wasm_exec.js index d6270adbfd..41ccaed12c 100644 --- a/targets/wasm_exec.js +++ b/targets/wasm_exec.js @@ -531,7 +531,10 @@ const go = new Go(); WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { - return go.run(result.instance); + go.run(result.instance).then((result) => { + process.exit(result); + }). + catch((e) => { throw e }); }).catch((err) => { console.error(err); process.exit(1); From 860697257b8deb3bd2e7c8d9ee72420448e8861e Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 31 Oct 2024 12:03:49 +0100 Subject: [PATCH 269/444] runtime: optimize findHead This is similar to https://github.com/tinygo-org/tinygo/pull/3899, but smaller and hopefully just as efficient. Thanks to @HattoriHanzo031 for starting this work, benchmarking, and for improving the performance of the code even further. --- builder/sizes_test.go | 6 +++--- src/runtime/gc_blocks.go | 40 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/builder/sizes_test.go b/builder/sizes_test.go index dd89609e71..650a5fbb59 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -41,9 +41,9 @@ func TestBinarySize(t *testing.T) { // This is a small number of very diverse targets that we want to test. tests := []sizeTest{ // microcontrollers - {"hifive1b", "examples/echo", 4580, 280, 0, 2268}, - {"microbit", "examples/serial", 2888, 388, 8, 2272}, - {"wioterminal", "examples/pininterrupt", 6124, 1484, 116, 6832}, + {"hifive1b", "examples/echo", 4600, 280, 0, 2268}, + {"microbit", "examples/serial", 2908, 388, 8, 2272}, + {"wioterminal", "examples/pininterrupt", 6140, 1484, 116, 6832}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the diff --git a/src/runtime/gc_blocks.go b/src/runtime/gc_blocks.go index 3c3862dbba..b2d2fed7f5 100644 --- a/src/runtime/gc_blocks.go +++ b/src/runtime/gc_blocks.go @@ -76,6 +76,13 @@ const ( blockStateMask blockState = 3 // 11 ) +// The byte value of a block where every block is a 'tail' block. +const blockStateByteAllTails = 0 | + uint8(blockStateTail<<(stateBits*3)) | + uint8(blockStateTail<<(stateBits*2)) | + uint8(blockStateTail<<(stateBits*1)) | + uint8(blockStateTail<<(stateBits*0)) + // String returns a human-readable version of the block state, for debugging. func (s blockState) String() string { switch s { @@ -123,7 +130,25 @@ func (b gcBlock) address() uintptr { // points to an allocated object. It returns the same block if this block // already points to the head. func (b gcBlock) findHead() gcBlock { - for b.state() == blockStateTail { + for { + // Optimization: check whether the current block state byte (which + // contains the state of multiple blocks) is composed entirely of tail + // blocks. If so, we can skip back to the last block in the previous + // state byte. + // This optimization speeds up findHead for pointers that point into a + // large allocation. + stateByte := b.stateByte() + if stateByte == blockStateByteAllTails { + b -= (b % blocksPerStateByte) + 1 + continue + } + + // Check whether we've found a non-tail block, which means we found the + // head. + state := b.stateFromByte(stateByte) + if state != blockStateTail { + break + } b-- } if gcAsserts { @@ -146,10 +171,19 @@ func (b gcBlock) findNext() gcBlock { return b } +func (b gcBlock) stateByte() byte { + return *(*uint8)(unsafe.Add(metadataStart, b/blocksPerStateByte)) +} + +// Return the block state given a state byte. The state byte must have been +// obtained using b.stateByte(), otherwise the result is incorrect. +func (b gcBlock) stateFromByte(stateByte byte) blockState { + return blockState(stateByte>>((b%blocksPerStateByte)*stateBits)) & blockStateMask +} + // State returns the current block state. func (b gcBlock) state() blockState { - stateBytePtr := (*uint8)(unsafe.Add(metadataStart, b/blocksPerStateByte)) - return blockState(*stateBytePtr>>((b%blocksPerStateByte)*stateBits)) & blockStateMask + return b.stateFromByte(b.stateByte()) } // setState sets the current block to the given state, which must contain more From b7d91e2f33c12f3a17ca36173dab789d80c63d26 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 26 Oct 2024 10:39:18 +0200 Subject: [PATCH 270/444] ci: use TinyGo version in artifact files This avoids needing to rename them ourselves (which is kinda annoying) and also avoids mistakes in the process. --- .github/workflows/build-macos.yml | 9 ++++-- .github/workflows/linux.yml | 36 +++++++++++++-------- .github/workflows/tinygo-extract-version.sh | 4 +++ .github/workflows/windows.yml | 24 ++++++++------ 4 files changed, 47 insertions(+), 26 deletions(-) create mode 100755 .github/workflows/tinygo-extract-version.sh diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 7b0c177ed5..4f26e1fb8f 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -33,6 +33,9 @@ jobs: uses: actions/checkout@v4 with: submodules: true + - name: Extract TinyGo version + id: version + run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT" - name: Install Go uses: actions/setup-go@v5 with: @@ -104,7 +107,7 @@ jobs: - name: Test stdlib packages run: make tinygo-test - name: Make release artifact - run: cp -p build/release.tar.gz build/tinygo.darwin-${{ matrix.goarch }}.tar.gz + run: cp -p build/release.tar.gz build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz - name: Publish release artifact # Note: this release artifact is double-zipped, see: # https://github.com/actions/upload-artifact/issues/39 @@ -114,8 +117,8 @@ jobs: # We're doing the former here, to keep artifact uploads fast. uses: actions/upload-artifact@v4 with: - name: darwin-${{ matrix.goarch }}-double-zipped - path: build/tinygo.darwin-${{ matrix.goarch }}.tar.gz + name: darwin-${{ matrix.goarch }}-double-zipped-${{ steps.version.outputs.version }} + path: build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz - name: Smoke tests run: make smoketest TINYGO=$(PWD)/build/tinygo test-macos-homebrew: diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index ab150317d3..206e6b7670 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -19,6 +19,8 @@ jobs: runs-on: ubuntu-latest container: image: golang:1.23-alpine + outputs: + version: ${{ steps.version.outputs.version }} steps: - name: Install apk dependencies # tar: needed for actions/cache@v4 @@ -32,6 +34,9 @@ jobs: uses: actions/checkout@v4 with: submodules: true + - name: Extract TinyGo version + id: version + run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT" - name: Cache Go uses: actions/cache@v4 with: @@ -120,15 +125,15 @@ jobs: - name: Build TinyGo release run: | make release deb -j3 STATIC=1 - cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz - cp -p build/release.deb /tmp/tinygo_amd64.deb + cp -p build/release.tar.gz /tmp/tinygo${{ steps.version.outputs.version }}.linux-amd64.tar.gz + cp -p build/release.deb /tmp/tinygo_${{ steps.version.outputs.version }}_amd64.deb - name: Publish release artifact uses: actions/upload-artifact@v4 with: - name: linux-amd64-double-zipped + name: linux-amd64-double-zipped-${{ steps.version.outputs.version }} path: | - /tmp/tinygo.linux-amd64.tar.gz - /tmp/tinygo_amd64.deb + /tmp/tinygo${{ steps.version.outputs.version }}.linux-amd64.tar.gz + /tmp/tinygo_${{ steps.version.outputs.version }}_amd64.deb test-linux-build: # Test the binaries built in the build-linux job by running the smoke tests. runs-on: ubuntu-latest @@ -152,11 +157,11 @@ jobs: - name: Download release artifact uses: actions/download-artifact@v4 with: - name: linux-amd64-double-zipped + name: linux-amd64-double-zipped-${{ needs.build-linux.outputs.version }} - name: Extract release tarball run: | mkdir -p ~/lib - tar -C ~/lib -xf tinygo.linux-amd64.tar.gz + tar -C ~/lib -xf tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz ln -s ~/lib/tinygo/bin/tinygo ~/go/bin/tinygo - run: make tinygo-test-wasip1-fast - run: make tinygo-test-wasip2-fast @@ -297,6 +302,9 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + - name: Get TinyGo version + id: version + run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT" - name: Install apt dependencies run: | sudo apt-get update @@ -381,11 +389,11 @@ jobs: - name: Download amd64 release uses: actions/download-artifact@v4 with: - name: linux-amd64-double-zipped + name: linux-amd64-double-zipped-${{ needs.build-linux.outputs.version }} - name: Extract amd64 release run: | mkdir -p build/release - tar -xf tinygo.linux-amd64.tar.gz -C build/release tinygo + tar -xf tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz -C build/release tinygo - name: Modify release run: | cp -p build/tinygo build/release/tinygo/bin @@ -393,12 +401,12 @@ jobs: - name: Create ${{ matrix.goarch }} release run: | make release deb RELEASEONLY=1 DEB_ARCH=${{ matrix.libc }} - cp -p build/release.tar.gz /tmp/tinygo.linux-${{ matrix.goarch }}.tar.gz - cp -p build/release.deb /tmp/tinygo_${{ matrix.libc }}.deb + cp -p build/release.tar.gz /tmp/tinygo${{ steps.version.outputs.version }}.linux-${{ matrix.goarch }}.tar.gz + cp -p build/release.deb /tmp/tinygo_${{ steps.version.outputs.version }}_${{ matrix.libc }}.deb - name: Publish release artifact uses: actions/upload-artifact@v4 with: - name: linux-${{ matrix.goarch }}-double-zipped + name: linux-${{ matrix.goarch }}-double-zipped-${{ steps.version.outputs.version }} path: | - /tmp/tinygo.linux-${{ matrix.goarch }}.tar.gz - /tmp/tinygo_${{ matrix.libc }}.deb + /tmp/tinygo${{ steps.version.outputs.version }}.linux-${{ matrix.goarch }}.tar.gz + /tmp/tinygo_${{ steps.version.outputs.version }}_${{ matrix.libc }}.deb diff --git a/.github/workflows/tinygo-extract-version.sh b/.github/workflows/tinygo-extract-version.sh new file mode 100755 index 0000000000..2bc1c85e35 --- /dev/null +++ b/.github/workflows/tinygo-extract-version.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +# Extract the version string from the source code, to be stored in a variable. +grep 'const version' goenv/version.go | sed 's/^const version = "\(.*\)"$/version=\1/g' diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 5287e108ea..0994d47a70 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -14,6 +14,8 @@ concurrency: jobs: build-windows: runs-on: windows-2022 + outputs: + version: ${{ steps.version.outputs.version }} steps: - name: Configure pagefile uses: al-cheb/configure-pagefile-action@v1.4 @@ -32,6 +34,10 @@ jobs: uses: actions/checkout@v4 with: submodules: true + - name: Extract TinyGo version + id: version + shell: bash + run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT" - name: Install Go uses: actions/setup-go@v5 with: @@ -108,7 +114,7 @@ jobs: - name: Make release artifact shell: bash working-directory: build/release - run: 7z -tzip a release.zip tinygo + run: 7z -tzip a tinygo${{ steps.version.outputs.version }}.windows-amd64.zip tinygo - name: Publish release artifact # Note: this release artifact is double-zipped, see: # https://github.com/actions/upload-artifact/issues/39 @@ -118,8 +124,8 @@ jobs: # We're doing the former here, to keep artifact uploads fast. uses: actions/upload-artifact@v4 with: - name: windows-amd64-double-zipped - path: build/release/release.zip + name: windows-amd64-double-zipped-${{ steps.version.outputs.version }} + path: build/release/tinygo${{ steps.version.outputs.version }}.windows-amd64.zip smoke-test-windows: runs-on: windows-2022 @@ -148,12 +154,12 @@ jobs: - name: Download TinyGo build uses: actions/download-artifact@v4 with: - name: windows-amd64-double-zipped + name: windows-amd64-double-zipped-${{ needs.build-windows.outputs.version }} path: build/ - name: Unzip TinyGo build shell: bash working-directory: build - run: 7z x release.zip -r + run: 7z x tinygo*.windows-amd64.zip -r - name: Smoke tests shell: bash run: make smoketest TINYGO=$(PWD)/build/tinygo/bin/tinygo @@ -178,12 +184,12 @@ jobs: - name: Download TinyGo build uses: actions/download-artifact@v4 with: - name: windows-amd64-double-zipped + name: windows-amd64-double-zipped-${{ needs.build-windows.outputs.version }} path: build/ - name: Unzip TinyGo build shell: bash working-directory: build - run: 7z x release.zip -r + run: 7z x tinygo*.windows-amd64.zip -r - name: Test stdlib packages run: make tinygo-test TINYGO=$(PWD)/build/tinygo/bin/tinygo @@ -214,11 +220,11 @@ jobs: - name: Download TinyGo build uses: actions/download-artifact@v4 with: - name: windows-amd64-double-zipped + name: windows-amd64-double-zipped-${{ needs.build-windows.outputs.version }} path: build/ - name: Unzip TinyGo build shell: bash working-directory: build - run: 7z x release.zip -r + run: 7z x tinygo*.windows-amd64.zip -r - name: Test stdlib packages on wasip1 run: make tinygo-test-wasip1-fast TINYGO=$(PWD)/build/tinygo/bin/tinygo From 258dac23247209dc54a2a986da9205549600d487 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 14 Nov 2024 09:21:30 +0100 Subject: [PATCH 271/444] wasm: tidy up wasm_exec.js a bit --- targets/wasm_exec.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/targets/wasm_exec.js b/targets/wasm_exec.js index 41ccaed12c..defc73ba82 100644 --- a/targets/wasm_exec.js +++ b/targets/wasm_exec.js @@ -530,11 +530,9 @@ } const go = new Go(); - WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { - go.run(result.instance).then((result) => { - process.exit(result); - }). - catch((e) => { throw e }); + WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then(async (result) => { + let exitCode = await go.run(result.instance); + process.exit(exitCode); }).catch((err) => { console.error(err); process.exit(1); From ac9f72be617e6e72423fa7afd6bcd2aaf9377d27 Mon Sep 17 00:00:00 2001 From: sivchari Date: Fri, 15 Nov 2024 15:39:22 +0900 Subject: [PATCH 272/444] support to parse devl version Signed-off-by: sivchari --- goenv/version.go | 3 +++ goenv/version_test.go | 1 + 2 files changed, 4 insertions(+) diff --git a/goenv/version.go b/goenv/version.go index 158c3caf20..4815d434a3 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -50,6 +50,9 @@ func GetGorootVersion() (major, minor int, err error) { // major, minor, and patch version: 1, 3, and 2 in this example. // If there is an error, (0, 0, 0) and an error will be returned. func Parse(version string) (major, minor, patch int, err error) { + if strings.HasPrefix(version, "devel ") { + version = strings.Split(strings.TrimPrefix(version, "devel "), version)[0] + } if version == "" || version[:2] != "go" { return 0, 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix") } diff --git a/goenv/version_test.go b/goenv/version_test.go index 1744d2b22f..cb408a2eae 100644 --- a/goenv/version_test.go +++ b/goenv/version_test.go @@ -21,6 +21,7 @@ func TestParse(t *testing.T) { {"go1.23.5-rc6", 1, 23, 5, false}, {"go2.0", 2, 0, 0, false}, {"go2.0.15", 2, 0, 15, false}, + {"devel go1.24-f99f5da18f Thu Nov 14 22:29:26 2024 +0000 darwin/arm64", 1, 24, 0, false}, } for _, tt := range tests { t.Run(tt.v, func(t *testing.T) { From 6d4dfcf72fb5dd4cc9130c3b883fd313a5b8dac3 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 15 Nov 2024 09:09:04 +0100 Subject: [PATCH 273/444] compiler, runtime: move constants into shared package Use a single package for certain constants that must be the same between the compiler and the runtime. While just using the same values in both places works, this is much more obvious and harder to mess up. It also avoids the need for comments pointing to the other location the constant is defined. And having it in code makes it possible for IDEs to analyze the source. In the future, more such constants and maybe algorithms can be added. --- compiler/compiler.go | 6 +++--- compiler/map.go | 16 +++++----------- loader/goroot.go | 1 + src/runtime/hashmap.go | 29 +++++++++++------------------ src/runtime/panic.go | 10 +++------- src/runtime/runtime_unix.go | 3 ++- src/tinygo/runtime.go | 17 +++++++++++++++++ 7 files changed, 42 insertions(+), 40 deletions(-) create mode 100644 src/tinygo/runtime.go diff --git a/compiler/compiler.go b/compiler/compiler.go index 981993fe76..8b5f0d1cb2 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -17,6 +17,7 @@ import ( "github.com/tinygo-org/tinygo/compiler/llvmutil" "github.com/tinygo-org/tinygo/loader" + "github.com/tinygo-org/tinygo/src/tinygo" "golang.org/x/tools/go/ssa" "golang.org/x/tools/go/types/typeutil" "tinygo.org/x/go-llvm" @@ -1869,10 +1870,9 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) } return llvm.ConstInt(b.ctx.Int1Type(), supportsRecover, false), nil case name == "runtime.panicStrategy": - // These constants are defined in src/runtime/panic.go. panicStrategy := map[string]uint64{ - "print": 1, // panicStrategyPrint - "trap": 2, // panicStrategyTrap + "print": tinygo.PanicStrategyPrint, + "trap": tinygo.PanicStrategyTrap, }[b.Config.PanicStrategy] return llvm.ConstInt(b.ctx.Int8Type(), panicStrategy, false), nil case name == "runtime/interrupt.New": diff --git a/compiler/map.go b/compiler/map.go index b4c5267233..71fb3f0da8 100644 --- a/compiler/map.go +++ b/compiler/map.go @@ -6,17 +6,11 @@ import ( "go/token" "go/types" + "github.com/tinygo-org/tinygo/src/tinygo" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) -// constants for hashmap algorithms; must match src/runtime/hashmap.go -const ( - hashmapAlgorithmBinary = iota - hashmapAlgorithmString - hashmapAlgorithmInterface -) - // createMakeMap creates a new map object (runtime.hashmap) by allocating and // initializing an appropriately sized object. func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) { @@ -24,20 +18,20 @@ func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) { keyType := mapType.Key().Underlying() llvmValueType := b.getLLVMType(mapType.Elem().Underlying()) var llvmKeyType llvm.Type - var alg uint64 // must match values in src/runtime/hashmap.go + var alg uint64 if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { // String keys. llvmKeyType = b.getLLVMType(keyType) - alg = hashmapAlgorithmString + alg = uint64(tinygo.HashmapAlgorithmString) } else if hashmapIsBinaryKey(keyType) { // Trivially comparable keys. llvmKeyType = b.getLLVMType(keyType) - alg = hashmapAlgorithmBinary + alg = uint64(tinygo.HashmapAlgorithmBinary) } else { // All other keys. Implemented as map[interface{}]valueType for ease of // implementation. llvmKeyType = b.getLLVMRuntimeType("_interface") - alg = hashmapAlgorithmInterface + alg = uint64(tinygo.HashmapAlgorithmInterface) } keySize := b.targetData.TypeAllocSize(llvmKeyType) valueSize := b.targetData.TypeAllocSize(llvmValueType) diff --git a/loader/goroot.go b/loader/goroot.go index 20fee016bf..631cc6e7e6 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -256,6 +256,7 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { "runtime/": false, "sync/": true, "testing/": true, + "tinygo/": false, "unique/": false, } diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index 7b46b42bef..ad42fda753 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -7,6 +7,7 @@ package runtime import ( "reflect" + "tinygo" "unsafe" ) @@ -22,14 +23,6 @@ type hashmap struct { keyHash func(key unsafe.Pointer, size, seed uintptr) uint32 } -type hashmapAlgorithm uint8 - -const ( - hashmapAlgorithmBinary hashmapAlgorithm = iota - hashmapAlgorithmString - hashmapAlgorithmInterface -) - // A hashmap bucket. A bucket is a container of 8 key/value pairs: first the // following two entries, then the 8 keys, then the 8 values. This somewhat odd // ordering is to make sure the keys and values are well aligned when one of @@ -76,8 +69,8 @@ func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) *hashm bucketBufSize := unsafe.Sizeof(hashmapBucket{}) + keySize*8 + valueSize*8 buckets := alloc(bucketBufSize*(1< Date: Tue, 12 Nov 2024 10:52:14 +0100 Subject: [PATCH 274/444] builder: whitelist temporary directory env var for Clang invocation It looks like this breaks on Windows: https://github.com/tinygo-org/tinygo/issues/4557 I haven't confirmed this is indeed the problem, but it would make sense. And passing through the temporary directory seems like a good idea regardless, there's not much that could break due to that. --- builder/tools.go | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/builder/tools.go b/builder/tools.go index b4087828f1..66329a099b 100644 --- a/builder/tools.go +++ b/builder/tools.go @@ -32,10 +32,26 @@ func runCCompiler(flags ...string) error { cmd.Stderr = os.Stderr // Make sure the command doesn't use any environmental variables. - // Most importantly, it should not use C_INCLUDE_PATH and the like. But - // removing all environmental variables also works. + // Most importantly, it should not use C_INCLUDE_PATH and the like. cmd.Env = []string{} + // Let some environment variables through. One important one is the + // temporary directory, especially on Windows it looks like Clang breaks if + // the temporary directory has not been set. + // See: https://github.com/tinygo-org/tinygo/issues/4557 + // Also see: https://github.com/llvm/llvm-project/blob/release/18.x/llvm/lib/Support/Unix/Path.inc#L1435 + for _, env := range os.Environ() { + // We could parse the key and look it up in a map, but since there are + // only a few keys iterating through them is easier and maybe even + // faster. + for _, prefix := range []string{"TMPDIR=", "TMP=", "TEMP=", "TEMPDIR="} { + if strings.HasPrefix(env, prefix) { + cmd.Env = append(cmd.Env, env) + break + } + } + } + return cmd.Run() } From 1dcccf87b5825de57eb522feb3d704fda95b62a5 Mon Sep 17 00:00:00 2001 From: leongross Date: Mon, 18 Nov 2024 14:44:06 +0100 Subject: [PATCH 275/444] linux: add runtime.fcntl function This is needed for the internal/syscall/unix package. Signed-off-by: leongross --- GNUmakefile | 1 + builder/musl.go | 1 + src/runtime/os_linux.go | 20 +++++++++++++++++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/GNUmakefile b/GNUmakefile index e66b6af29f..2bf023c627 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -926,6 +926,7 @@ endif @cp -rp lib/musl/src/env build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/errno build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/exit build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/fcntl build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/include build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/internal build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/legacy build/release/tinygo/lib/musl/src diff --git a/builder/musl.go b/builder/musl.go index a6699ad8d1..b156430d4f 100644 --- a/builder/musl.go +++ b/builder/musl.go @@ -116,6 +116,7 @@ var libMusl = Library{ "env/*.c", "errno/*.c", "exit/*.c", + "fcntl/*.c", "internal/defsysinfo.c", "internal/libc.c", "internal/syscall_ret.c", diff --git a/src/runtime/os_linux.go b/src/runtime/os_linux.go index df5870a2df..0ae105c5fc 100644 --- a/src/runtime/os_linux.go +++ b/src/runtime/os_linux.go @@ -5,7 +5,9 @@ package runtime // This file is for systems that are _actually_ Linux (not systems that pretend // to be Linux, like baremetal systems). -import "unsafe" +import ( + "unsafe" +) const GOOS = "linux" @@ -83,6 +85,11 @@ type elfProgramHeader32 struct { //go:extern __ehdr_start var ehdr_start elfHeader +// int *__errno_location(void); +// +//export __errno_location +func libc_errno_location() *int32 + // findGlobals finds globals in the .data/.bss sections. // It parses the ELF program header to find writable segments. func findGlobals(found func(start, end uintptr)) { @@ -139,3 +146,14 @@ func hardwareRand() (n uint64, ok bool) { // //export getrandom func libc_getrandom(buf unsafe.Pointer, buflen uintptr, flags uint32) uint32 + +// int fcntl(int fd, int cmd, int arg); +// +//export fcntl +func libc_fcntl(fd int, cmd int, arg int) (ret int) + +func fcntl(fd int32, cmd int32, arg int32) (ret int32, errno int32) { + ret = int32(libc_fcntl(int(fd), int(cmd), int(arg))) + errno = *libc_errno_location() + return +} From 9b3eb3fe59fc6443b05dfa8f6d7c4512829cc246 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 29 Oct 2024 09:47:14 +0100 Subject: [PATCH 276/444] ci: run at least some tests on older Go/LLVM versions These should make sure basic functionality is still working. Using the `-short` flag to avoid taking too long to run all tests (and to install all the necessary emulators), and because some targets might not work in older Go/LLVM versions (such as WASI). This does _not_ run tests and checks against expected IR, because LLVM IR changes a lot across versions. --- .circleci/config.yml | 2 ++ main_test.go | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 826240d9a2..ce3417ffb3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -90,6 +90,8 @@ commands: name: Check Go code formatting command: make fmt-check lint - run: make gen-device -j4 + # TODO: change this to -skip='TestErrors|TestWasm' with Go 1.20 + - run: go test -tags=llvm<> -short -run='TestBuild|TestTest|TestGetList|TestTraceback' - run: make smoketest XTENSA=0 - save_cache: key: go-cache-v4-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} diff --git a/main_test.go b/main_test.go index ecff134275..22ac549de7 100644 --- a/main_test.go +++ b/main_test.go @@ -15,7 +15,6 @@ import ( "reflect" "regexp" "runtime" - "slices" "strings" "sync" "testing" @@ -521,7 +520,7 @@ func TestWebAssembly(t *testing.T) { } } } - if !slices.Equal(imports, tc.imports) { + if !stringSlicesEqual(imports, tc.imports) { t.Errorf("import list not as expected!\nexpected: %v\nactual: %v", tc.imports, imports) } } @@ -529,6 +528,20 @@ func TestWebAssembly(t *testing.T) { } } +func stringSlicesEqual(s1, s2 []string) bool { + // We can use slices.Equal once we drop support for Go 1.20 (it was added in + // Go 1.21). + if len(s1) != len(s2) { + return false + } + for i, s := range s1 { + if s != s2[i] { + return false + } + } + return true +} + func TestWasmExport(t *testing.T) { t.Parallel() From 8068419854baf06333ccff38517e8d061e6d953d Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 18 Nov 2024 09:30:39 +0100 Subject: [PATCH 277/444] runtime: heapptr only needs to be initialized once There is no need to initialize it twice. --- src/runtime/gc_leaking.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/runtime/gc_leaking.go b/src/runtime/gc_leaking.go index 71c4258b67..a40602a98a 100644 --- a/src/runtime/gc_leaking.go +++ b/src/runtime/gc_leaking.go @@ -11,7 +11,7 @@ import ( ) // Ever-incrementing pointer: no memory is freed. -var heapptr = heapStart +var heapptr uintptr // Total amount allocated for runtime.MemStats var gcTotalAlloc uint64 @@ -93,7 +93,8 @@ func SetFinalizer(obj interface{}, finalizer interface{}) { } func initHeap() { - // preinit() may have moved heapStart; reset heapptr + // Initialize this bump-pointer allocator to the start of the heap. + // Needed here because heapStart may not be a compile-time constant. heapptr = heapStart } From c4867c8743e82433526b07eff3c599d05e0b7e6e Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 5 Nov 2024 08:58:40 +0100 Subject: [PATCH 278/444] cgo: define idents referenced only from macros --- cgo/cgo.go | 4 ++-- cgo/const.go | 22 +++++++++++++++++++--- cgo/const_test.go | 2 +- cgo/libclang.go | 2 +- cgo/testdata/const.go | 3 +++ cgo/testdata/const.out.go | 2 ++ 6 files changed, 28 insertions(+), 7 deletions(-) diff --git a/cgo/cgo.go b/cgo/cgo.go index 4a5d7efedf..a90b307538 100644 --- a/cgo/cgo.go +++ b/cgo/cgo.go @@ -1148,7 +1148,7 @@ func (f *cgoFile) getASTDeclName(name string, found clangCursor, iscall bool) st if alias := cgoAliases["C."+name]; alias != "" { return alias } - node := f.getASTDeclNode(name, found, iscall) + node := f.getASTDeclNode(name, found) if node, ok := node.(*ast.FuncDecl); ok { if !iscall { return node.Name.Name + "$funcaddr" @@ -1160,7 +1160,7 @@ func (f *cgoFile) getASTDeclName(name string, found clangCursor, iscall bool) st // getASTDeclNode will declare the given C AST node (if not already defined) and // returns it. -func (f *cgoFile) getASTDeclNode(name string, found clangCursor, iscall bool) ast.Node { +func (f *cgoFile) getASTDeclNode(name string, found clangCursor) ast.Node { if node, ok := f.defined[name]; ok { // Declaration was found in the current file, so return it immediately. return node diff --git a/cgo/const.go b/cgo/const.go index f4707c80a8..ab088b3c80 100644 --- a/cgo/const.go +++ b/cgo/const.go @@ -54,8 +54,8 @@ func init() { } // parseConst parses the given string as a C constant. -func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) { - t := newTokenizer(pos, fset, value) +func parseConst(pos token.Pos, fset *token.FileSet, value string, f *cgoFile) (ast.Expr, *scanner.Error) { + t := newTokenizer(pos, fset, value, f) expr, err := parseConstExpr(t, precedenceLowest) t.Next() if t.curToken != token.EOF { @@ -96,6 +96,20 @@ func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) { } func parseIdent(t *tokenizer) (ast.Expr, *scanner.Error) { + // Normally the name is something defined in the file (like another macro) + // which we get the declaration from using getASTDeclName. + // This ensures that names that are only referenced inside a macro are still + // getting defined. + if t.f != nil { + if cursor, ok := t.f.names[t.curValue]; ok { + return &ast.Ident{ + NamePos: t.curPos, + Name: t.f.getASTDeclName(t.curValue, cursor, false), + }, nil + } + } + + // t.f is nil during testing. This is a fallback. return &ast.Ident{ NamePos: t.curPos, Name: "C." + t.curValue, @@ -164,6 +178,7 @@ func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error { // tokenizer reads C source code and converts it to Go tokens. type tokenizer struct { + f *cgoFile curPos, peekPos token.Pos fset *token.FileSet curToken, peekToken token.Token @@ -173,8 +188,9 @@ type tokenizer struct { // newTokenizer initializes a new tokenizer, positioned at the first token in // the string. -func newTokenizer(start token.Pos, fset *token.FileSet, buf string) *tokenizer { +func newTokenizer(start token.Pos, fset *token.FileSet, buf string, f *cgoFile) *tokenizer { t := &tokenizer{ + f: f, peekPos: start, fset: fset, buf: buf, diff --git a/cgo/const_test.go b/cgo/const_test.go index d150e751f4..c2f52c53c5 100644 --- a/cgo/const_test.go +++ b/cgo/const_test.go @@ -59,7 +59,7 @@ func TestParseConst(t *testing.T) { } { fset := token.NewFileSet() startPos := fset.AddFile("", -1, 1000).Pos(0) - expr, err := parseConst(startPos, fset, tc.C) + expr, err := parseConst(startPos, fset, tc.C, nil) s := "" if err != nil { if !strings.HasPrefix(tc.Go, "error: ") { diff --git a/cgo/libclang.go b/cgo/libclang.go index 59236bad42..c66112d53e 100644 --- a/cgo/libclang.go +++ b/cgo/libclang.go @@ -408,7 +408,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { if pos != token.NoPos { tokenPos = pos + token.Pos(len(name)) } - expr, scannerError := parseConst(tokenPos, f.fset, value) + expr, scannerError := parseConst(tokenPos, f.fset, value, f) if scannerError != nil { f.errors = append(f.errors, *scannerError) return nil, nil diff --git a/cgo/testdata/const.go b/cgo/testdata/const.go index 43645d3ac4..2589422351 100644 --- a/cgo/testdata/const.go +++ b/cgo/testdata/const.go @@ -3,10 +3,13 @@ package main /* #define foo 3 #define bar foo +#define unreferenced 4 +#define referenced unreferenced */ import "C" const ( Foo = C.foo Bar = C.bar + Baz = C.referenced ) diff --git a/cgo/testdata/const.out.go b/cgo/testdata/const.out.go index 2b48163b4b..fb0bbeeba2 100644 --- a/cgo/testdata/const.out.go +++ b/cgo/testdata/const.out.go @@ -47,3 +47,5 @@ type ( const C.foo = 3 const C.bar = C.foo +const C.unreferenced = 4 +const C.referenced = C.unreferenced From e12da15f7d230874ca95a257c50bca0fc39f262c Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 6 Nov 2024 13:46:49 +0100 Subject: [PATCH 279/444] cgo: support function-like macros This is needed for code like this: #define __WASI_ERRNO_INVAL (UINT16_C(28)) #define EINVAL __WASI_ERRNO_INVAL --- cgo/const.go | 128 +++++++++++++++++++++++++++++++++++-- cgo/const_test.go | 2 +- cgo/libclang.go | 98 +++++++++++++++++----------- cgo/libclang_stubs.c | 4 ++ cgo/testdata/const.go | 13 ++++ cgo/testdata/const.out.go | 3 + cgo/testdata/errors.go | 8 +++ cgo/testdata/errors.out.go | 4 ++ 8 files changed, 214 insertions(+), 46 deletions(-) diff --git a/cgo/const.go b/cgo/const.go index ab088b3c80..9e7b06b4de 100644 --- a/cgo/const.go +++ b/cgo/const.go @@ -54,8 +54,72 @@ func init() { } // parseConst parses the given string as a C constant. -func parseConst(pos token.Pos, fset *token.FileSet, value string, f *cgoFile) (ast.Expr, *scanner.Error) { +func parseConst(pos token.Pos, fset *token.FileSet, value string, params []ast.Expr, callerPos token.Pos, f *cgoFile) (ast.Expr, *scanner.Error) { t := newTokenizer(pos, fset, value, f) + + // If params is non-nil (could be a zero length slice), this const is + // actually a function-call like expression from another macro. + // This means we have to parse a string like "(a, b) (a+b)". + // We do this by parsing the parameters at the start and then treating the + // following like a normal constant expression. + if params != nil { + // Parse opening paren. + if t.curToken != token.LPAREN { + return nil, unexpectedToken(t, token.LPAREN) + } + t.Next() + + // Parse parameters (identifiers) and closing paren. + var paramIdents []string + for i := 0; ; i++ { + if i == 0 && t.curToken == token.RPAREN { + // No parameters, break early. + t.Next() + break + } + + // Read the parameter name. + if t.curToken != token.IDENT { + return nil, unexpectedToken(t, token.IDENT) + } + paramIdents = append(paramIdents, t.curValue) + t.Next() + + // Read the next token: either a continuation (comma) or end of list + // (rparen). + if t.curToken == token.RPAREN { + // End of parameter list. + t.Next() + break + } else if t.curToken == token.COMMA { + // Comma, so there will be another parameter name. + t.Next() + } else { + return nil, &scanner.Error{ + Pos: t.fset.Position(t.curPos), + Msg: "unexpected token " + t.curToken.String() + " inside macro parameters, expected ',' or ')'", + } + } + } + + // Report an error if there is a mismatch in parameter length. + // The error is reported at the location of the closing paren from the + // caller location. + if len(params) != len(paramIdents) { + return nil, &scanner.Error{ + Pos: t.fset.Position(callerPos), + Msg: fmt.Sprintf("unexpected number of parameters: expected %d, got %d", len(paramIdents), len(params)), + } + } + + // Assign values to the parameters. + // These parameter names are closer in 'scope' than other identifiers so + // will be used first when parsing an identifier. + for i, name := range paramIdents { + t.params[name] = params[i] + } + } + expr, err := parseConstExpr(t, precedenceLowest) t.Next() if t.curToken != token.EOF { @@ -96,11 +160,59 @@ func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) { } func parseIdent(t *tokenizer) (ast.Expr, *scanner.Error) { - // Normally the name is something defined in the file (like another macro) - // which we get the declaration from using getASTDeclName. - // This ensures that names that are only referenced inside a macro are still - // getting defined. + // If the identifier is one of the parameters of this function-like macro, + // use the parameter value. + if val, ok := t.params[t.curValue]; ok { + return val, nil + } + if t.f != nil { + // Check whether this identifier is actually a macro "call" with + // parameters. In that case, we should parse the parameters and pass it + // on to a new invocation of parseConst. + if t.peekToken == token.LPAREN { + if cursor, ok := t.f.names[t.curValue]; ok && t.f.isFunctionLikeMacro(cursor) { + // We know the current and peek tokens (the peek one is the '(' + // token). So skip ahead until the current token is the first + // unknown token. + t.Next() + t.Next() + + // Parse the list of parameters until ')' (rparen) is found. + params := []ast.Expr{} + for i := 0; ; i++ { + if i == 0 && t.curToken == token.RPAREN { + break + } + x, err := parseConstExpr(t, precedenceLowest) + if err != nil { + return nil, err + } + params = append(params, x) + t.Next() + if t.curToken == token.COMMA { + t.Next() + } else if t.curToken == token.RPAREN { + break + } else { + return nil, &scanner.Error{ + Pos: t.fset.Position(t.curPos), + Msg: "unexpected token " + t.curToken.String() + ", ',' or ')'", + } + } + } + + // Evaluate the macro value and use it as the identifier value. + rparen := t.curPos + pos, text := t.f.getMacro(cursor) + return parseConst(pos, t.fset, text, params, rparen, t.f) + } + } + + // Normally the name is something defined in the file (like another + // macro) which we get the declaration from using getASTDeclName. + // This ensures that names that are only referenced inside a macro are + // still getting defined. if cursor, ok := t.f.names[t.curValue]; ok { return &ast.Ident{ NamePos: t.curPos, @@ -184,6 +296,7 @@ type tokenizer struct { curToken, peekToken token.Token curValue, peekValue string buf string + params map[string]ast.Expr } // newTokenizer initializes a new tokenizer, positioned at the first token in @@ -195,6 +308,7 @@ func newTokenizer(start token.Pos, fset *token.FileSet, buf string, f *cgoFile) fset: fset, buf: buf, peekToken: token.ILLEGAL, + params: make(map[string]ast.Expr), } // Parse the first two tokens (cur and peek). t.Next() @@ -246,7 +360,7 @@ func (t *tokenizer) Next() { t.peekValue = t.buf[:2] t.buf = t.buf[2:] return - case c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || c == '&' || c == '|' || c == '^': + case c == '(' || c == ')' || c == ',' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || c == '&' || c == '|' || c == '^': // Single-character tokens. // TODO: ++ (increment) and -- (decrement) operators. switch c { @@ -254,6 +368,8 @@ func (t *tokenizer) Next() { t.peekToken = token.LPAREN case ')': t.peekToken = token.RPAREN + case ',': + t.peekToken = token.COMMA case '+': t.peekToken = token.ADD case '-': diff --git a/cgo/const_test.go b/cgo/const_test.go index c2f52c53c5..b87f8063a4 100644 --- a/cgo/const_test.go +++ b/cgo/const_test.go @@ -59,7 +59,7 @@ func TestParseConst(t *testing.T) { } { fset := token.NewFileSet() startPos := fset.AddFile("", -1, 1000).Pos(0) - expr, err := parseConst(startPos, fset, tc.C, nil) + expr, err := parseConst(startPos, fset, tc.C, nil, token.NoPos, nil) s := "" if err != nil { if !strings.HasPrefix(tc.Go, "error: ") { diff --git a/cgo/libclang.go b/cgo/libclang.go index c66112d53e..794d4e81f1 100644 --- a/cgo/libclang.go +++ b/cgo/libclang.go @@ -63,6 +63,7 @@ long long tinygo_clang_getEnumConstantDeclValue(GoCXCursor c); CXType tinygo_clang_getEnumDeclIntegerType(GoCXCursor c); unsigned tinygo_clang_Cursor_isAnonymous(GoCXCursor c); unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c); +unsigned tinygo_clang_Cursor_isMacroFunctionLike(GoCXCursor c); int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); @@ -370,45 +371,8 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { gen.Specs = append(gen.Specs, valueSpec) return gen, nil case C.CXCursor_MacroDefinition: - // Extract tokens from the Clang tokenizer. - // See: https://stackoverflow.com/a/19074846/559350 - sourceRange := C.tinygo_clang_getCursorExtent(c) - tu := C.tinygo_clang_Cursor_getTranslationUnit(c) - var rawTokens *C.CXToken - var numTokens C.unsigned - C.clang_tokenize(tu, sourceRange, &rawTokens, &numTokens) - tokens := unsafe.Slice(rawTokens, numTokens) - // Convert this range of tokens back to source text. - // Ugly, but it works well enough. - sourceBuf := &bytes.Buffer{} - var startOffset int - for i, token := range tokens { - spelling := getString(C.clang_getTokenSpelling(tu, token)) - location := C.clang_getTokenLocation(tu, token) - var tokenOffset C.unsigned - C.clang_getExpansionLocation(location, nil, nil, nil, &tokenOffset) - if i == 0 { - // The first token is the macro name itself. - // Skip it (after using its location). - startOffset = int(tokenOffset) + len(name) - } else { - // Later tokens are the macro contents. - for int(tokenOffset) > (startOffset + sourceBuf.Len()) { - // Pad the source text with whitespace (that must have been - // present in the original source as well). - sourceBuf.WriteByte(' ') - } - sourceBuf.WriteString(spelling) - } - } - C.clang_disposeTokens(tu, rawTokens, numTokens) - value := sourceBuf.String() - // Try to convert this #define into a Go constant expression. - tokenPos := token.NoPos - if pos != token.NoPos { - tokenPos = pos + token.Pos(len(name)) - } - expr, scannerError := parseConst(tokenPos, f.fset, value, f) + tokenPos, value := f.getMacro(c) + expr, scannerError := parseConst(tokenPos, f.fset, value, nil, token.NoPos, f) if scannerError != nil { f.errors = append(f.errors, *scannerError) return nil, nil @@ -488,6 +452,62 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { } } +// Return whether this is a macro that's also function-like, like this: +// +// #define add(a, b) (a+b) +func (f *cgoFile) isFunctionLikeMacro(c clangCursor) bool { + if C.tinygo_clang_getCursorKind(c) != C.CXCursor_MacroDefinition { + return false + } + return C.tinygo_clang_Cursor_isMacroFunctionLike(c) != 0 +} + +// Get the macro value: the position in the source file and the string value of +// the macro. +func (f *cgoFile) getMacro(c clangCursor) (pos token.Pos, value string) { + // Extract tokens from the Clang tokenizer. + // See: https://stackoverflow.com/a/19074846/559350 + sourceRange := C.tinygo_clang_getCursorExtent(c) + tu := C.tinygo_clang_Cursor_getTranslationUnit(c) + var rawTokens *C.CXToken + var numTokens C.unsigned + C.clang_tokenize(tu, sourceRange, &rawTokens, &numTokens) + tokens := unsafe.Slice(rawTokens, numTokens) + defer C.clang_disposeTokens(tu, rawTokens, numTokens) + + // Convert this range of tokens back to source text. + // Ugly, but it works well enough. + sourceBuf := &bytes.Buffer{} + var startOffset int + for i, token := range tokens { + spelling := getString(C.clang_getTokenSpelling(tu, token)) + location := C.clang_getTokenLocation(tu, token) + var tokenOffset C.unsigned + C.clang_getExpansionLocation(location, nil, nil, nil, &tokenOffset) + if i == 0 { + // The first token is the macro name itself. + // Skip it (after using its location). + startOffset = int(tokenOffset) + } else { + // Later tokens are the macro contents. + for int(tokenOffset) > (startOffset + sourceBuf.Len()) { + // Pad the source text with whitespace (that must have been + // present in the original source as well). + sourceBuf.WriteByte(' ') + } + sourceBuf.WriteString(spelling) + } + } + value = sourceBuf.String() + + // Obtain the position of this token. This is the position of the first + // character in the 'value' string and is used to report errors at the + // correct location in the source file. + pos = f.getCursorPosition(c) + + return +} + func getString(clangString C.CXString) (s string) { rawString := C.clang_getCString(clangString) s = C.GoString(rawString) diff --git a/cgo/libclang_stubs.c b/cgo/libclang_stubs.c index 1b157d0aa7..e8098fac09 100644 --- a/cgo/libclang_stubs.c +++ b/cgo/libclang_stubs.c @@ -84,3 +84,7 @@ unsigned tinygo_clang_Cursor_isAnonymous(CXCursor c) { unsigned tinygo_clang_Cursor_isBitField(CXCursor c) { return clang_Cursor_isBitField(c); } + +unsigned tinygo_clang_Cursor_isMacroFunctionLike(CXCursor c) { + return clang_Cursor_isMacroFunctionLike(c); +} diff --git a/cgo/testdata/const.go b/cgo/testdata/const.go index 2589422351..d5a7dfd396 100644 --- a/cgo/testdata/const.go +++ b/cgo/testdata/const.go @@ -3,13 +3,26 @@ package main /* #define foo 3 #define bar foo + #define unreferenced 4 #define referenced unreferenced + +#define fnlike() 5 +#define fnlike_val fnlike() +#define square(n) (n*n) +#define square_val square(20) +#define add(a, b) (a + b) +#define add_val add(3, 5) */ import "C" const ( Foo = C.foo Bar = C.bar + Baz = C.referenced + + fnlike = C.fnlike_val + square = C.square_val + add = C.add_val ) diff --git a/cgo/testdata/const.out.go b/cgo/testdata/const.out.go index fb0bbeeba2..e7ee15380a 100644 --- a/cgo/testdata/const.out.go +++ b/cgo/testdata/const.out.go @@ -49,3 +49,6 @@ const C.foo = 3 const C.bar = C.foo const C.unreferenced = 4 const C.referenced = C.unreferenced +const C.fnlike_val = 5 +const C.square_val = (20 * 20) +const C.add_val = (3 + 5) diff --git a/cgo/testdata/errors.go b/cgo/testdata/errors.go index e5e809881f..75828ce0f1 100644 --- a/cgo/testdata/errors.go +++ b/cgo/testdata/errors.go @@ -26,6 +26,11 @@ import "C" // #warning another warning import "C" +// #define add(a, b) (a+b) +// #define add_toomuch add(1, 2, 3) +// #define add_toolittle add(1) +import "C" + // Make sure that errors for the following lines won't change with future // additions to the CGo preamble. // @@ -51,4 +56,7 @@ var ( // constants passed by a command line parameter _ = C.SOME_PARAM_CONST_invalid _ = C.SOME_PARAM_CONST_valid + + _ = C.add_toomuch + _ = C.add_toolittle ) diff --git a/cgo/testdata/errors.out.go b/cgo/testdata/errors.out.go index 43a6a65c97..baadba68d2 100644 --- a/cgo/testdata/errors.out.go +++ b/cgo/testdata/errors.out.go @@ -7,6 +7,8 @@ // testdata/errors.go:16:33: unexpected token ), expected end of expression // testdata/errors.go:17:34: unexpected token ), expected end of expression // -: unexpected token INT, expected end of expression +// testdata/errors.go:30:35: unexpected number of parameters: expected 2, got 3 +// testdata/errors.go:31:31: unexpected number of parameters: expected 2, got 1 // Type checking errors after CGo processing: // testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as C.char value in variable declaration (overflows) @@ -17,6 +19,8 @@ // testdata/errors.go:114: undefined: C.SOME_CONST_b // testdata/errors.go:116: undefined: C.SOME_CONST_startspace // testdata/errors.go:119: undefined: C.SOME_PARAM_CONST_invalid +// testdata/errors.go:122: undefined: C.add_toomuch +// testdata/errors.go:123: undefined: C.add_toolittle package main From 289fceb3eaf0829d1e3b65228b95a79bd5fc9389 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 18 Nov 2024 11:43:50 +0100 Subject: [PATCH 280/444] syscall: refactor environment handling * Move environment functions to their own files. * Rewrite the WASIp2 version of environment variables to be much simpler (don't go through C functions). --- src/syscall/env_libc.go | 52 ++++++++++++++++++++++++++++++++ src/syscall/env_nonhosted.go | 45 +++++++++++++++++++++++++++ src/syscall/env_wasip2.go | 45 +++++++++++++++++++++++++++ src/syscall/libc_wasip2.go | 52 -------------------------------- src/syscall/syscall_libc.go | 52 -------------------------------- src/syscall/syscall_nonhosted.go | 42 -------------------------- 6 files changed, 142 insertions(+), 146 deletions(-) create mode 100644 src/syscall/env_nonhosted.go diff --git a/src/syscall/env_libc.go b/src/syscall/env_libc.go index 4ad078dc54..fbf7d04e0e 100644 --- a/src/syscall/env_libc.go +++ b/src/syscall/env_libc.go @@ -55,5 +55,57 @@ func Environ() []string { return envs } +func Getenv(key string) (value string, found bool) { + data := cstring(key) + raw := libc_getenv(&data[0]) + if raw == nil { + return "", false + } + + ptr := uintptr(unsafe.Pointer(raw)) + for size := uintptr(0); ; size++ { + v := *(*byte)(unsafe.Pointer(ptr)) + if v == 0 { + src := *(*[]byte)(unsafe.Pointer(&sliceHeader{buf: raw, len: size, cap: size})) + return string(src), true + } + ptr += unsafe.Sizeof(byte(0)) + } +} + +func Setenv(key, val string) (err error) { + if len(key) == 0 { + return EINVAL + } + for i := 0; i < len(key); i++ { + if key[i] == '=' || key[i] == 0 { + return EINVAL + } + } + for i := 0; i < len(val); i++ { + if val[i] == 0 { + return EINVAL + } + } + runtimeSetenv(key, val) + return +} + +func Unsetenv(key string) (err error) { + runtimeUnsetenv(key) + return +} + +func Clearenv() { + for _, s := range Environ() { + for j := 0; j < len(s); j++ { + if s[j] == '=' { + Unsetenv(s[0:j]) + break + } + } + } +} + //go:extern environ var libc_environ *unsafe.Pointer diff --git a/src/syscall/env_nonhosted.go b/src/syscall/env_nonhosted.go new file mode 100644 index 0000000000..446ba55d2d --- /dev/null +++ b/src/syscall/env_nonhosted.go @@ -0,0 +1,45 @@ +//go:build baremetal || js || wasm_unknown + +package syscall + +func Environ() []string { + env := runtime_envs() + envCopy := make([]string, len(env)) + copy(envCopy, env) + return envCopy +} + +func Getenv(key string) (value string, found bool) { + env := runtime_envs() + for _, keyval := range env { + // Split at '=' character. + var k, v string + for i := 0; i < len(keyval); i++ { + if keyval[i] == '=' { + k = keyval[:i] + v = keyval[i+1:] + } + } + if k == key { + return v, true + } + } + return "", false +} + +func Setenv(key, val string) (err error) { + // stub for now + return ENOSYS +} + +func Unsetenv(key string) (err error) { + // stub for now + return ENOSYS +} + +func Clearenv() (err error) { + // stub for now + return ENOSYS +} + +func runtime_envs() []string diff --git a/src/syscall/env_wasip2.go b/src/syscall/env_wasip2.go index 970400d644..8064d0d281 100644 --- a/src/syscall/env_wasip2.go +++ b/src/syscall/env_wasip2.go @@ -2,6 +2,19 @@ package syscall +import ( + "internal/wasi/cli/v0.2.0/environment" +) + +var libc_envs map[string]string + +func populateEnvironment() { + libc_envs = make(map[string]string) + for _, kv := range environment.GetEnvironment().Slice() { + libc_envs[kv[0]] = kv[1] + } +} + func Environ() []string { var env []string for k, v := range libc_envs { @@ -9,3 +22,35 @@ func Environ() []string { } return env } + +func Getenv(key string) (value string, found bool) { + value, found = libc_envs[key] + return +} + +func Setenv(key, val string) (err error) { + if len(key) == 0 { + return EINVAL + } + for i := 0; i < len(key); i++ { + if key[i] == '=' || key[i] == 0 { + return EINVAL + } + } + for i := 0; i < len(val); i++ { + if val[i] == 0 { + return EINVAL + } + } + libc_envs[key] = val + return nil +} + +func Unsetenv(key string) (err error) { + delete(libc_envs, key) + return nil +} + +func Clearenv() { + clear(libc_envs) +} diff --git a/src/syscall/libc_wasip2.go b/src/syscall/libc_wasip2.go index a89e64a80b..5621c1a683 100644 --- a/src/syscall/libc_wasip2.go +++ b/src/syscall/libc_wasip2.go @@ -1227,58 +1227,6 @@ func p2fileTypeToStatType(t types.DescriptorType) uint32 { return 0 } -var libc_envs map[string]string - -func populateEnvironment() { - libc_envs = make(map[string]string) - for _, kv := range environment.GetEnvironment().Slice() { - libc_envs[kv[0]] = kv[1] - } -} - -// char * getenv(const char *name); -// -//export getenv -func getenv(key *byte) *byte { - k := goString(key) - - v, ok := libc_envs[k] - if !ok { - return nil - } - - // The new allocation is zero-filled; allocating an extra byte and then - // copying the data over will leave the last byte untouched, - // null-terminating the string. - vbytes := make([]byte, len(v)+1) - copy(vbytes, v) - return unsafe.SliceData(vbytes) -} - -// int setenv(const char *name, const char *value, int overwrite); -// -//export setenv -func setenv(key, value *byte, overwrite int) int { - k := goString(key) - if _, ok := libc_envs[k]; ok && overwrite == 0 { - return 0 - } - - v := goString(value) - libc_envs[k] = v - - return 0 -} - -// int unsetenv(const char *name); -// -//export unsetenv -func unsetenv(key *byte) int { - k := goString(key) - delete(libc_envs, k) - return 0 -} - // void arc4random_buf (void *, size_t); // //export arc4random_buf diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index 8b032d3831..d8293efde4 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -238,58 +238,6 @@ func Wait4(pid int, wstatus *WaitStatus, options int, rusage uintptr) (wpid int, return 0, ENOSYS // TODO } -func Getenv(key string) (value string, found bool) { - data := cstring(key) - raw := libc_getenv(&data[0]) - if raw == nil { - return "", false - } - - ptr := uintptr(unsafe.Pointer(raw)) - for size := uintptr(0); ; size++ { - v := *(*byte)(unsafe.Pointer(ptr)) - if v == 0 { - src := *(*[]byte)(unsafe.Pointer(&sliceHeader{buf: raw, len: size, cap: size})) - return string(src), true - } - ptr += unsafe.Sizeof(byte(0)) - } -} - -func Setenv(key, val string) (err error) { - if len(key) == 0 { - return EINVAL - } - for i := 0; i < len(key); i++ { - if key[i] == '=' || key[i] == 0 { - return EINVAL - } - } - for i := 0; i < len(val); i++ { - if val[i] == 0 { - return EINVAL - } - } - runtimeSetenv(key, val) - return -} - -func Unsetenv(key string) (err error) { - runtimeUnsetenv(key) - return -} - -func Clearenv() { - for _, s := range Environ() { - for j := 0; j < len(s); j++ { - if s[j] == '=' { - Unsetenv(s[0:j]) - break - } - } - } -} - func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error) { addr := libc_mmap(nil, uintptr(length), int32(prot), int32(flags), int32(fd), uintptr(offset)) if addr == unsafe.Pointer(^uintptr(0)) { diff --git a/src/syscall/syscall_nonhosted.go b/src/syscall/syscall_nonhosted.go index b56d71af64..4867d70f65 100644 --- a/src/syscall/syscall_nonhosted.go +++ b/src/syscall/syscall_nonhosted.go @@ -87,48 +87,6 @@ const ( MAP_ANONYMOUS = MAP_ANON ) -func runtime_envs() []string - -func Getenv(key string) (value string, found bool) { - env := runtime_envs() - for _, keyval := range env { - // Split at '=' character. - var k, v string - for i := 0; i < len(keyval); i++ { - if keyval[i] == '=' { - k = keyval[:i] - v = keyval[i+1:] - } - } - if k == key { - return v, true - } - } - return "", false -} - -func Setenv(key, val string) (err error) { - // stub for now - return ENOSYS -} - -func Unsetenv(key string) (err error) { - // stub for now - return ENOSYS -} - -func Clearenv() (err error) { - // stub for now - return ENOSYS -} - -func Environ() []string { - env := runtime_envs() - envCopy := make([]string, len(env)) - copy(envCopy, env) - return envCopy -} - func Open(path string, mode int, perm uint32) (fd int, err error) { return 0, ENOSYS } From 0087d4c6bf13a1bac648b0e61459d717bae87b02 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 18 Nov 2024 11:49:30 +0100 Subject: [PATCH 281/444] syscall: use wasi-libc tables for wasm/js target Instead of using fake tables for errno and others, use the ones that correspond to wasi-libc. --- src/syscall/errno_other.go | 2 +- src/syscall/{errno_wasip1.go => errno_wasilibc.go} | 2 +- src/syscall/syscall_libc.go | 2 +- src/syscall/syscall_libc_wasi.go | 4 +++- src/syscall/syscall_nonhosted.go | 2 +- src/syscall/tables_nonhosted.go | 2 +- 6 files changed, 8 insertions(+), 6 deletions(-) rename src/syscall/{errno_wasip1.go => errno_wasilibc.go} (83%) diff --git a/src/syscall/errno_other.go b/src/syscall/errno_other.go index 3a06ac018f..8c58f5f01a 100644 --- a/src/syscall/errno_other.go +++ b/src/syscall/errno_other.go @@ -1,4 +1,4 @@ -//go:build !wasip1 && !wasip2 +//go:build !js && !wasip1 && !wasip2 package syscall diff --git a/src/syscall/errno_wasip1.go b/src/syscall/errno_wasilibc.go similarity index 83% rename from src/syscall/errno_wasip1.go rename to src/syscall/errno_wasilibc.go index c494d7da09..efb97260f5 100644 --- a/src/syscall/errno_wasip1.go +++ b/src/syscall/errno_wasilibc.go @@ -1,4 +1,4 @@ -//go:build wasip1 +//go:build wasip1 || js package syscall diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index d8293efde4..0ef9784283 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -1,4 +1,4 @@ -//go:build nintendoswitch || wasip1 || wasip2 +//go:build js || nintendoswitch || wasip1 || wasip2 package syscall diff --git a/src/syscall/syscall_libc_wasi.go b/src/syscall/syscall_libc_wasi.go index 06169bb2b9..bbf81cd059 100644 --- a/src/syscall/syscall_libc_wasi.go +++ b/src/syscall/syscall_libc_wasi.go @@ -1,4 +1,6 @@ -//go:build wasip1 || wasip2 +//go:build js || wasip1 || wasip2 + +// Note: also including js in here because it also uses wasi-libc. package syscall diff --git a/src/syscall/syscall_nonhosted.go b/src/syscall/syscall_nonhosted.go index 4867d70f65..a0965692f4 100644 --- a/src/syscall/syscall_nonhosted.go +++ b/src/syscall/syscall_nonhosted.go @@ -1,4 +1,4 @@ -//go:build baremetal || js || wasm_unknown +//go:build baremetal || wasm_unknown package syscall diff --git a/src/syscall/tables_nonhosted.go b/src/syscall/tables_nonhosted.go index e66620cbf4..a45834827e 100644 --- a/src/syscall/tables_nonhosted.go +++ b/src/syscall/tables_nonhosted.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build baremetal || nintendoswitch || js || wasm_unknown +//go:build baremetal || nintendoswitch || wasm_unknown package syscall From 2d26b6cc8dd9b9bb81274e29335790f9b2682e7c Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 19 Nov 2024 11:15:53 +0100 Subject: [PATCH 282/444] windows: don't return, exit via exit(0) instead This fixes a bug where output would not actually be written to stdout before exiting the process, leading to a flaky Windows CI. Exiting using `exit(0)` appears to fix this. For some background, see: https://github.com/tinygo-org/tinygo/pull/4589 --- src/runtime/runtime_windows.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/runtime/runtime_windows.go b/src/runtime/runtime_windows.go index 4b0b8f65b8..88857fc3a5 100644 --- a/src/runtime/runtime_windows.go +++ b/src/runtime/runtime_windows.go @@ -52,7 +52,16 @@ func mainCRTStartup() int { stackTop = getCurrentStackPointer() runMain() - // For libc compatibility. + // Exit via exit(0) instead of returning. This matches + // mingw-w64-crt/crt/crtexe.c, which exits using exit(0) instead of + // returning the return value. + // Exiting this way (instead of returning) also fixes an issue where not all + // output would be sent to stdout before exit. + // See: https://github.com/tinygo-org/tinygo/pull/4589 + libc_exit(0) + + // Unreachable, since we've already exited. But we need to return something + // here to make this valid Go code. return 0 } From 548fba82e691b125bdac71f6c393f67fcf4d769f Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Tue, 19 Nov 2024 16:06:27 -0800 Subject: [PATCH 283/444] compiler: Fix wasmimport -> wasmexport in error message Fixes #4615 --- compiler/symbol.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/symbol.go b/compiler/symbol.go index 9b9b1d10e8..e53df30f1c 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -345,7 +345,7 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { continue } if len(parts) != 2 { - c.addError(f.Pos(), fmt.Sprintf("expected one parameter to //go:wasmimport, not %d", len(parts)-1)) + c.addError(f.Pos(), fmt.Sprintf("expected one parameter to //go:wasmexport, not %d", len(parts)-1)) continue } name := parts[1] From 6593cf22fad6fab2a201ed00c2cc20c76e29161a Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 6 Nov 2024 14:57:56 +0100 Subject: [PATCH 284/444] cgo: support errno value as second return parameter Making this work on all targets was interesting but there's now a test in place to make sure this works on all targets that have the CGo test enabled (which is almost all targets). --- builder/picolibc.go | 1 + cgo/cgo.go | 121 +++++++++++++++++++++- cgo/cgo_test.go | 27 ++++- cgo/testdata/basic.out.go | 8 ++ cgo/testdata/const.out.go | 8 ++ cgo/testdata/errors.out.go | 8 ++ cgo/testdata/flags.out.go | 8 ++ cgo/testdata/symbols.out.go | 8 ++ cgo/testdata/types.out.go | 8 ++ compileopts/config.go | 2 + loader/loader.go | 2 +- src/runtime/baremetal.go | 13 +++ src/runtime/os_darwin.go | 12 +-- src/runtime/os_windows.go | 3 + src/runtime/runtime.go | 5 + src/runtime/runtime_nintendoswitch.go | 6 ++ src/runtime/runtime_tinygowasm.go | 5 + src/runtime/runtime_tinygowasm_unknown.go | 6 ++ src/runtime/runtime_tinygowasmp2.go | 6 ++ testdata/cgo/main.c | 5 + testdata/cgo/main.go | 12 ++- testdata/cgo/main.h | 3 + testdata/cgo/out.txt | 2 + 23 files changed, 263 insertions(+), 16 deletions(-) diff --git a/builder/picolibc.go b/builder/picolibc.go index d33f31c094..9026b99ee4 100644 --- a/builder/picolibc.go +++ b/builder/picolibc.go @@ -34,6 +34,7 @@ var libPicolibc = Library{ "-D__OBSOLETE_MATH_FLOAT=1", // use old math code that doesn't expect a FPU "-D__OBSOLETE_MATH_DOUBLE=0", "-D_WANT_IO_C99_FORMATS", + "-D__PICOLIBC_ERRNO_FUNCTION=__errno_location", "-nostdlibinc", "-isystem", newlibDir + "/libc/include", "-I" + newlibDir + "/libc/tinystdio", diff --git a/cgo/cgo.go b/cgo/cgo.go index a90b307538..0ed26e3fb8 100644 --- a/cgo/cgo.go +++ b/cgo/cgo.go @@ -138,7 +138,8 @@ typedef unsigned long long _Cgo_ulonglong; // first. // These functions will be modified to get a "C." prefix, so the source below // doesn't reflect the final AST. -const generatedGoFilePrefix = ` +const generatedGoFilePrefixBase = ` +import "syscall" import "unsafe" var _ unsafe.Pointer @@ -169,6 +170,74 @@ func __CBytes([]byte) unsafe.Pointer func CBytes(b []byte) unsafe.Pointer { return C.__CBytes(b) } + +//go:linkname C.__get_errno_num runtime.cgo_errno +func __get_errno_num() uintptr +` + +const generatedGoFilePrefixOther = generatedGoFilePrefixBase + ` +func __get_errno() error { + return syscall.Errno(C.__get_errno_num()) +} +` + +// Windows uses fake errno values in the syscall package. +// See for example: https://github.com/golang/go/issues/23468 +// TinyGo uses mingw-w64 though, which does have defined errno values. Since the +// syscall package is the standard library one we can't change it, but we can +// map the errno values to match the values in the syscall package. +// Source of the errno values: lib/mingw-w64/mingw-w64-headers/crt/errno.h +const generatedGoFilePrefixWindows = generatedGoFilePrefixBase + ` +var __errno_mapping = [...]syscall.Errno{ + 1: syscall.EPERM, + 2: syscall.ENOENT, + 3: syscall.ESRCH, + 4: syscall.EINTR, + 5: syscall.EIO, + 6: syscall.ENXIO, + 7: syscall.E2BIG, + 8: syscall.ENOEXEC, + 9: syscall.EBADF, + 10: syscall.ECHILD, + 11: syscall.EAGAIN, + 12: syscall.ENOMEM, + 13: syscall.EACCES, + 14: syscall.EFAULT, + 16: syscall.EBUSY, + 17: syscall.EEXIST, + 18: syscall.EXDEV, + 19: syscall.ENODEV, + 20: syscall.ENOTDIR, + 21: syscall.EISDIR, + 22: syscall.EINVAL, + 23: syscall.ENFILE, + 24: syscall.EMFILE, + 25: syscall.ENOTTY, + 27: syscall.EFBIG, + 28: syscall.ENOSPC, + 29: syscall.ESPIPE, + 30: syscall.EROFS, + 31: syscall.EMLINK, + 32: syscall.EPIPE, + 33: syscall.EDOM, + 34: syscall.ERANGE, + 36: syscall.EDEADLK, + 38: syscall.ENAMETOOLONG, + 39: syscall.ENOLCK, + 40: syscall.ENOSYS, + 41: syscall.ENOTEMPTY, + 42: syscall.EILSEQ, +} + +func __get_errno() error { + num := C.__get_errno_num() + if num < uintptr(len(__errno_mapping)) { + if mapped := __errno_mapping[num]; mapped != 0 { + return mapped + } + } + return syscall.Errno(num) +} ` // Process extracts `import "C"` statements from the AST, parses the comment @@ -178,7 +247,7 @@ func CBytes(b []byte) unsafe.Pointer { // functions), the CFLAGS and LDFLAGS found in #cgo lines, and a map of file // hashes of the accessed C header files. If there is one or more error, it // returns these in the []error slice but still modifies the AST. -func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string) ([]*ast.File, []string, []string, []string, map[string][]byte, []error) { +func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string, goos string) ([]*ast.File, []string, []string, []string, map[string][]byte, []error) { p := &cgoPackage{ packageName: files[0].Name.Name, currentDir: dir, @@ -210,7 +279,12 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl // Construct a new in-memory AST for CGo declarations of this package. // The first part is written as Go code that is then parsed, but more code // is added later to the AST to declare functions, globals, etc. - goCode := "package " + files[0].Name.Name + "\n\n" + generatedGoFilePrefix + goCode := "package " + files[0].Name.Name + "\n\n" + if goos == "windows" { + goCode += generatedGoFilePrefixWindows + } else { + goCode += generatedGoFilePrefixOther + } p.generated, err = parser.ParseFile(fset, dir+"/!cgo.go", goCode, parser.ParseComments) if err != nil { // This is always a bug in the cgo package. @@ -225,7 +299,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl switch decl := decl.(type) { case *ast.FuncDecl: switch decl.Name.Name { - case "CString", "GoString", "GoStringN", "__GoStringN", "GoBytes", "__GoBytes", "CBytes", "__CBytes": + case "CString", "GoString", "GoStringN", "__GoStringN", "GoBytes", "__GoBytes", "CBytes", "__CBytes", "__get_errno_num", "__get_errno", "__errno_mapping": // Adjust the name to have a "C." prefix so it is correctly // resolved. decl.Name.Name = "C." + decl.Name.Name @@ -1279,6 +1353,45 @@ extern __typeof(%s) %s __attribute__((alias(%#v))); // separate namespace (no _Cgo_ hacks like in gc). func (f *cgoFile) walker(cursor *astutil.Cursor, names map[string]clangCursor) bool { switch node := cursor.Node().(type) { + case *ast.AssignStmt: + // An assign statement could be something like this: + // + // val, errno := C.some_func() + // + // Check whether it looks like that, and if so, read the errno value and + // return it as the second return value. The call will be transformed + // into something like this: + // + // val, errno := C.some_func(), C.__get_errno() + if len(node.Lhs) != 2 || len(node.Rhs) != 1 { + return true + } + rhs, ok := node.Rhs[0].(*ast.CallExpr) + if !ok { + return true + } + fun, ok := rhs.Fun.(*ast.SelectorExpr) + if !ok { + return true + } + x, ok := fun.X.(*ast.Ident) + if !ok { + return true + } + if found, ok := names[fun.Sel.Name]; ok && x.Name == "C" { + // Replace "C"."some_func" into "C.somefunc". + rhs.Fun = &ast.Ident{ + NamePos: x.NamePos, + Name: f.getASTDeclName(fun.Sel.Name, found, true), + } + // Add the errno value as the second value in the statement. + node.Rhs = append(node.Rhs, &ast.CallExpr{ + Fun: &ast.Ident{ + NamePos: node.Lhs[1].End(), + Name: "C.__get_errno", + }, + }) + } case *ast.CallExpr: fun, ok := node.Fun.(*ast.SelectorExpr) if !ok { diff --git a/cgo/cgo_test.go b/cgo/cgo_test.go index dc79b21d53..f852c7f5f9 100644 --- a/cgo/cgo_test.go +++ b/cgo/cgo_test.go @@ -56,7 +56,7 @@ func TestCGo(t *testing.T) { } // Process the AST with CGo. - cgoFiles, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags) + cgoFiles, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags, "linux") // Check the AST for type errors. var typecheckErrors []error @@ -64,7 +64,7 @@ func TestCGo(t *testing.T) { Error: func(err error) { typecheckErrors = append(typecheckErrors, err) }, - Importer: simpleImporter{}, + Importer: newSimpleImporter(), Sizes: types.SizesFor("gccgo", "arm"), } _, err = config.Check("", fset, append([]*ast.File{f}, cgoFiles...), nil) @@ -202,14 +202,33 @@ func Test_cgoPackage_isEquivalentAST(t *testing.T) { } // simpleImporter implements the types.Importer interface, but only allows -// importing the unsafe package. +// importing the syscall and unsafe packages. type simpleImporter struct { + syscallPkg *types.Package +} + +func newSimpleImporter() *simpleImporter { + i := &simpleImporter{} + + // Implement a dummy syscall package with the Errno type. + i.syscallPkg = types.NewPackage("syscall", "syscall") + obj := types.NewTypeName(token.NoPos, i.syscallPkg, "Errno", nil) + named := types.NewNamed(obj, nil, nil) + i.syscallPkg.Scope().Insert(obj) + named.SetUnderlying(types.Typ[types.Uintptr]) + sig := types.NewSignatureType(nil, nil, nil, types.NewTuple(), types.NewTuple(types.NewParam(token.NoPos, i.syscallPkg, "", types.Typ[types.String])), false) + named.AddMethod(types.NewFunc(token.NoPos, i.syscallPkg, "Error", sig)) + i.syscallPkg.MarkComplete() + + return i } // Import implements the Importer interface. For testing usage only: it only // supports importing the unsafe package. -func (i simpleImporter) Import(path string) (*types.Package, error) { +func (i *simpleImporter) Import(path string) (*types.Package, error) { switch path { + case "syscall": + return i.syscallPkg, nil case "unsafe": return types.Unsafe, nil default: diff --git a/cgo/testdata/basic.out.go b/cgo/testdata/basic.out.go index 6c2623980e..7348ee3791 100644 --- a/cgo/testdata/basic.out.go +++ b/cgo/testdata/basic.out.go @@ -1,5 +1,6 @@ package main +import "syscall" import "unsafe" var _ unsafe.Pointer @@ -31,6 +32,13 @@ func C.CBytes(b []byte) unsafe.Pointer { return C.__CBytes(b) } +//go:linkname C.__get_errno_num runtime.cgo_errno +func C.__get_errno_num() uintptr + +func C.__get_errno() error { + return syscall.Errno(C.__get_errno_num()) +} + type ( C.char uint8 C.schar int8 diff --git a/cgo/testdata/const.out.go b/cgo/testdata/const.out.go index e7ee15380a..21705afc4f 100644 --- a/cgo/testdata/const.out.go +++ b/cgo/testdata/const.out.go @@ -1,5 +1,6 @@ package main +import "syscall" import "unsafe" var _ unsafe.Pointer @@ -31,6 +32,13 @@ func C.CBytes(b []byte) unsafe.Pointer { return C.__CBytes(b) } +//go:linkname C.__get_errno_num runtime.cgo_errno +func C.__get_errno_num() uintptr + +func C.__get_errno() error { + return syscall.Errno(C.__get_errno_num()) +} + type ( C.char uint8 C.schar int8 diff --git a/cgo/testdata/errors.out.go b/cgo/testdata/errors.out.go index baadba68d2..cbac4fceb3 100644 --- a/cgo/testdata/errors.out.go +++ b/cgo/testdata/errors.out.go @@ -24,6 +24,7 @@ package main +import "syscall" import "unsafe" var _ unsafe.Pointer @@ -55,6 +56,13 @@ func C.CBytes(b []byte) unsafe.Pointer { return C.__CBytes(b) } +//go:linkname C.__get_errno_num runtime.cgo_errno +func C.__get_errno_num() uintptr + +func C.__get_errno() error { + return syscall.Errno(C.__get_errno_num()) +} + type ( C.char uint8 C.schar int8 diff --git a/cgo/testdata/flags.out.go b/cgo/testdata/flags.out.go index 83ca604200..8662412296 100644 --- a/cgo/testdata/flags.out.go +++ b/cgo/testdata/flags.out.go @@ -5,6 +5,7 @@ package main +import "syscall" import "unsafe" var _ unsafe.Pointer @@ -36,6 +37,13 @@ func C.CBytes(b []byte) unsafe.Pointer { return C.__CBytes(b) } +//go:linkname C.__get_errno_num runtime.cgo_errno +func C.__get_errno_num() uintptr + +func C.__get_errno() error { + return syscall.Errno(C.__get_errno_num()) +} + type ( C.char uint8 C.schar int8 diff --git a/cgo/testdata/symbols.out.go b/cgo/testdata/symbols.out.go index 569cb65f6a..b49150e601 100644 --- a/cgo/testdata/symbols.out.go +++ b/cgo/testdata/symbols.out.go @@ -1,5 +1,6 @@ package main +import "syscall" import "unsafe" var _ unsafe.Pointer @@ -31,6 +32,13 @@ func C.CBytes(b []byte) unsafe.Pointer { return C.__CBytes(b) } +//go:linkname C.__get_errno_num runtime.cgo_errno +func C.__get_errno_num() uintptr + +func C.__get_errno() error { + return syscall.Errno(C.__get_errno_num()) +} + type ( C.char uint8 C.schar int8 diff --git a/cgo/testdata/types.out.go b/cgo/testdata/types.out.go index e5382ec803..b3fe414b07 100644 --- a/cgo/testdata/types.out.go +++ b/cgo/testdata/types.out.go @@ -1,5 +1,6 @@ package main +import "syscall" import "unsafe" var _ unsafe.Pointer @@ -31,6 +32,13 @@ func C.CBytes(b []byte) unsafe.Pointer { return C.__CBytes(b) } +//go:linkname C.__get_errno_num runtime.cgo_errno +func C.__get_errno_num() uintptr + +func C.__get_errno() error { + return syscall.Errno(C.__get_errno_num()) +} + type ( C.char uint8 C.schar int8 diff --git a/compileopts/config.go b/compileopts/config.go index 76215b1817..ee5c34537c 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -331,6 +331,7 @@ func (c *Config) CFlags(libclang bool) []string { "-isystem", filepath.Join(path, "include"), "-isystem", filepath.Join(picolibcDir, "include"), "-isystem", filepath.Join(picolibcDir, "tinystdio"), + "-D__PICOLIBC_ERRNO_FUNCTION=__errno_location", ) case "musl": root := goenv.Get("TINYGOROOT") @@ -340,6 +341,7 @@ func (c *Config) CFlags(libclang bool) []string { "-nostdlibinc", "-isystem", filepath.Join(path, "include"), "-isystem", filepath.Join(root, "lib", "musl", "arch", arch), + "-isystem", filepath.Join(root, "lib", "musl", "arch", "generic"), "-isystem", filepath.Join(root, "lib", "musl", "include"), ) case "wasi-libc": diff --git a/loader/loader.go b/loader/loader.go index f3ffa86144..e935a9de3a 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -485,7 +485,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) { var initialCFlags []string initialCFlags = append(initialCFlags, p.program.config.CFlags(true)...) initialCFlags = append(initialCFlags, "-I"+p.Dir) - generated, headerCode, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.ImportPath, p.program.fset, initialCFlags) + generated, headerCode, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.ImportPath, p.program.fset, initialCFlags, p.program.config.GOOS()) p.CFlags = append(initialCFlags, cflags...) p.CGoHeaders = headerCode for path, hash := range accessedFiles { diff --git a/src/runtime/baremetal.go b/src/runtime/baremetal.go index 173d0db25e..aecb189720 100644 --- a/src/runtime/baremetal.go +++ b/src/runtime/baremetal.go @@ -86,3 +86,16 @@ func AdjustTimeOffset(offset int64) { // TODO: do this atomically? timeOffset += offset } + +// Picolibc is not configured to define its own errno value, instead it calls +// __errno_location. +// TODO: a global works well enough for now (same as errno on Linux with +// -scheduler=tasks), but this should ideally be a thread-local variable stored +// in task.Task. +// Especially when we add multicore support for microcontrollers. +var errno int32 + +//export __errno_location +func libc_errno_location() *int32 { + return &errno +} diff --git a/src/runtime/os_darwin.go b/src/runtime/os_darwin.go index 8338d0f18b..e7f7b368fb 100644 --- a/src/runtime/os_darwin.go +++ b/src/runtime/os_darwin.go @@ -151,7 +151,7 @@ func syscall_rawSyscall(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) { r1 = uintptr(result) if result == -1 { // Syscall returns -1 on failure. - err = uintptr(*libc___error()) + err = uintptr(*libc_errno_location()) } return } @@ -161,7 +161,7 @@ func syscall_syscallX(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) { r1 = call_syscallX(fn, a1, a2, a3) if int64(r1) == -1 { // Syscall returns -1 on failure. - err = uintptr(*libc___error()) + err = uintptr(*libc_errno_location()) } return } @@ -171,7 +171,7 @@ func syscall_syscallPtr(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) { r1 = call_syscallX(fn, a1, a2, a3) if r1 == 0 { // Syscall returns a pointer on success, or NULL on failure. - err = uintptr(*libc___error()) + err = uintptr(*libc_errno_location()) } return } @@ -182,7 +182,7 @@ func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) r1 = uintptr(result) if result == -1 { // Syscall returns -1 on failure. - err = uintptr(*libc___error()) + err = uintptr(*libc_errno_location()) } return } @@ -192,7 +192,7 @@ func syscall_syscall6X(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) r1 = call_syscall6X(fn, a1, a2, a3, a4, a5, a6) if int64(r1) == -1 { // Syscall returns -1 on failure. - err = uintptr(*libc___error()) + err = uintptr(*libc_errno_location()) } return } @@ -216,7 +216,7 @@ func libc_getpagesize() int32 // } // //export __error -func libc___error() *int32 +func libc_errno_location() *int32 //export tinygo_syscall func call_syscall(fn, a1, a2, a3 uintptr) int32 diff --git a/src/runtime/os_windows.go b/src/runtime/os_windows.go index 3750d51940..a124e7ab14 100644 --- a/src/runtime/os_windows.go +++ b/src/runtime/os_windows.go @@ -113,3 +113,6 @@ func syscall_Getpagesize() int { _GetSystemInfo(unsafe.Pointer(&info)) return int(info.dwpagesize) } + +//export _errno +func libc_errno_location() *int32 diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go index bb937f0447..99ca34f2c8 100644 --- a/src/runtime/runtime.go +++ b/src/runtime/runtime.go @@ -127,3 +127,8 @@ func write(fd uintptr, p unsafe.Pointer, n int32) int32 { func getAuxv() []uintptr { return nil } + +// Called from cgo to obtain the errno value. +func cgo_errno() uintptr { + return uintptr(*libc_errno_location()) +} diff --git a/src/runtime/runtime_nintendoswitch.go b/src/runtime/runtime_nintendoswitch.go index 7d67a86264..d2567b1ccf 100644 --- a/src/runtime/runtime_nintendoswitch.go +++ b/src/runtime/runtime_nintendoswitch.go @@ -313,3 +313,9 @@ func hardwareRand() (n uint64, ok bool) { // TODO: see whether there is a RNG and use it. return 0, false } + +func libc_errno_location() *int32 { + // CGo is unavailable, so this function should be unreachable. + runtimePanic("runtime: no cgo errno") + return nil +} diff --git a/src/runtime/runtime_tinygowasm.go b/src/runtime/runtime_tinygowasm.go index 7bc65e9c44..67367b479e 100644 --- a/src/runtime/runtime_tinygowasm.go +++ b/src/runtime/runtime_tinygowasm.go @@ -112,3 +112,8 @@ func hardwareRand() (n uint64, ok bool) { // //export arc4random func libc_arc4random() uint32 + +// int *__errno_location(void); +// +//export __errno_location +func libc_errno_location() *int32 diff --git a/src/runtime/runtime_tinygowasm_unknown.go b/src/runtime/runtime_tinygowasm_unknown.go index e426f36ff8..b67d70aeab 100644 --- a/src/runtime/runtime_tinygowasm_unknown.go +++ b/src/runtime/runtime_tinygowasm_unknown.go @@ -51,3 +51,9 @@ func procUnpin() { func hardwareRand() (n uint64, ok bool) { return 0, false } + +func libc_errno_location() *int32 { + // CGo is unavailable, so this function should be unreachable. + runtimePanic("runtime: no cgo errno") + return nil +} diff --git a/src/runtime/runtime_tinygowasmp2.go b/src/runtime/runtime_tinygowasmp2.go index 70b5a6d11e..5565cb4ee6 100644 --- a/src/runtime/runtime_tinygowasmp2.go +++ b/src/runtime/runtime_tinygowasmp2.go @@ -81,3 +81,9 @@ func procUnpin() { func hardwareRand() (n uint64, ok bool) { return random.GetRandomU64(), true } + +func libc_errno_location() *int32 { + // CGo is unavailable, so this function should be unreachable. + runtimePanic("runtime: no cgo errno") + return nil +} diff --git a/testdata/cgo/main.c b/testdata/cgo/main.c index 4a5bd6b9c6..94b338dda2 100644 --- a/testdata/cgo/main.c +++ b/testdata/cgo/main.c @@ -77,3 +77,8 @@ double doSqrt(double x) { void printf_single_int(char *format, int arg) { printf(format, arg); } + +int set_errno(int err) { + errno = err; + return -1; +} diff --git a/testdata/cgo/main.go b/testdata/cgo/main.go index 00e0ba01da..38d11386a9 100644 --- a/testdata/cgo/main.go +++ b/testdata/cgo/main.go @@ -18,7 +18,10 @@ import "C" // static int headerfunc_static(int a) { return a - 1; } import "C" -import "unsafe" +import ( + "syscall" + "unsafe" +) func main() { println("fortytwo:", C.fortytwo()) @@ -168,6 +171,13 @@ func main() { println("len(C.GoBytes(C.CBytes(nil),0)):", len(C.GoBytes(C.CBytes(nil), 0))) println(`rountrip CBytes:`, C.GoString((*C.char)(C.CBytes([]byte("hello\000"))))) + // Check that errno is returned from the second return value, and that it + // matches the errno value that was just set. + _, errno := C.set_errno(C.EINVAL) + println("EINVAL:", errno == syscall.EINVAL) + _, errno = C.set_errno(C.EAGAIN) + println("EAGAIN:", errno == syscall.EAGAIN) + // libc: test whether C functions work at all. buf1 := []byte("foobar\x00") buf2 := make([]byte, len(buf1)) diff --git a/testdata/cgo/main.h b/testdata/cgo/main.h index e7c64ffc3f..3942497f23 100644 --- a/testdata/cgo/main.h +++ b/testdata/cgo/main.h @@ -1,5 +1,6 @@ #include #include +#include typedef short myint; typedef short unusedTypedef; @@ -154,3 +155,5 @@ void arraydecay(int buf1[5], int buf2[3][8], arraydecay_buf3 buf3); double doSqrt(double); void printf_single_int(char *format, int arg); + +int set_errno(int err); diff --git a/testdata/cgo/out.txt b/testdata/cgo/out.txt index 3088d4cb4d..1d63f5e82f 100644 --- a/testdata/cgo/out.txt +++ b/testdata/cgo/out.txt @@ -75,6 +75,8 @@ len(C.GoStringN(nil, 0)): 0 len(C.GoBytes(nil, 0)): 0 len(C.GoBytes(C.CBytes(nil),0)): 0 rountrip CBytes: hello +EINVAL: true +EAGAIN: true copied string: foobar CGo sqrt(3): +1.732051e+000 C sqrt(3): +1.732051e+000 From d1fe02df230e1a31ba9ff8f440a5ef33a60d1313 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 25 Oct 2024 11:14:57 +0200 Subject: [PATCH 285/444] runtime: move scheduler code around This moves all scheduler code into a separate file that is only compiled when there's a scheduler in use (the tasks or asyncify scheduler, which are both cooperative). The main goal of this change is to make it easier to add a new "scheduler" based on OS threads. It also fixes a few subtle issues with `-gc=none`: - Gosched() panicked. This is now fixed to just return immediately (the only logical thing to do when there's only one goroutine). - Timers aren't supported without a scheduler, but the relevant code was still present and would happily add a timer to the queue. It just never ran. So now it exits with a runtime error, similar to any blocking operation. --- src/internal/task/task.go | 3 + src/internal/task/task_asyncify.go | 5 +- src/internal/task/task_stack.go | 5 +- src/runtime/arch_tinygoriscv.go | 2 +- src/runtime/chan.go | 6 +- src/runtime/cond.go | 2 +- src/runtime/gc_blocks.go | 5 +- src/runtime/scheduler.go | 221 +---------------------- src/runtime/scheduler_any.go | 31 ---- src/runtime/scheduler_cooperative.go | 252 +++++++++++++++++++++++++++ src/runtime/scheduler_none.go | 58 ++++-- src/sync/mutex.go | 2 +- 12 files changed, 315 insertions(+), 277 deletions(-) delete mode 100644 src/runtime/scheduler_any.go create mode 100644 src/runtime/scheduler_cooperative.go diff --git a/src/internal/task/task.go b/src/internal/task/task.go index c1ee57ddae..bce65b582e 100644 --- a/src/internal/task/task.go +++ b/src/internal/task/task.go @@ -33,3 +33,6 @@ func getGoroutineStackSize(fn uintptr) uintptr //go:linkname runtime_alloc runtime.alloc func runtime_alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer + +//go:linkname scheduleTask runtime.scheduleTask +func scheduleTask(*Task) diff --git a/src/internal/task/task_asyncify.go b/src/internal/task/task_asyncify.go index 55a1044e4a..637a6b2237 100644 --- a/src/internal/task/task_asyncify.go +++ b/src/internal/task/task_asyncify.go @@ -52,7 +52,7 @@ type stackState struct { func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) { t := &Task{} t.state.initialize(fn, args, stackSize) - runqueuePushBack(t) + scheduleTask(t) } //export tinygo_launch @@ -82,9 +82,6 @@ func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) { s.csp = unsafe.Add(stack, stackSize) } -//go:linkname runqueuePushBack runtime.runqueuePushBack -func runqueuePushBack(*Task) - // currentTask is the current running task, or nil if currently in the scheduler. var currentTask *Task diff --git a/src/internal/task/task_stack.go b/src/internal/task/task_stack.go index 551612425f..88a0970685 100644 --- a/src/internal/task/task_stack.go +++ b/src/internal/task/task_stack.go @@ -101,15 +101,12 @@ func swapTask(oldStack uintptr, newStack *uintptr) //go:extern tinygo_startTask var startTask [0]uint8 -//go:linkname runqueuePushBack runtime.runqueuePushBack -func runqueuePushBack(*Task) - // start creates and starts a new goroutine with the given function and arguments. // The new goroutine is scheduled to run later. func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) { t := &Task{} t.state.initialize(fn, args, stackSize) - runqueuePushBack(t) + scheduleTask(t) } // OnSystemStack returns whether the caller is running on the system stack. diff --git a/src/runtime/arch_tinygoriscv.go b/src/runtime/arch_tinygoriscv.go index 0d376c48b8..921c775a5e 100644 --- a/src/runtime/arch_tinygoriscv.go +++ b/src/runtime/arch_tinygoriscv.go @@ -36,7 +36,7 @@ func procUnpin() { func waitForEvents() { mask := riscv.DisableInterrupts() - if !runqueue.Empty() { + if runqueue := schedulerRunQueue(); runqueue == nil || !runqueue.Empty() { riscv.Asm("wfi") } riscv.EnableInterrupts(mask) diff --git a/src/runtime/chan.go b/src/runtime/chan.go index 269f5a01b6..1f0d7ced8d 100644 --- a/src/runtime/chan.go +++ b/src/runtime/chan.go @@ -183,8 +183,7 @@ func (ch *channel) resumeRX(ok bool) unsafe.Pointer { b.detach() } - // push task onto runqueue - runqueue.Push(b.t) + scheduleTask(b.t) return dst } @@ -210,8 +209,7 @@ func (ch *channel) resumeTX() unsafe.Pointer { b.detach() } - // push task onto runqueue - runqueue.Push(b.t) + scheduleTask(b.t) return src } diff --git a/src/runtime/cond.go b/src/runtime/cond.go index 00e89932a5..b27ab08abc 100644 --- a/src/runtime/cond.go +++ b/src/runtime/cond.go @@ -34,7 +34,7 @@ func (c *Cond) Notify() bool { default: // Unblock the waiting task. if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), unsafe.Pointer(t), nil) { - runqueuePushBack(t) + scheduleTask(t) return true } } diff --git a/src/runtime/gc_blocks.go b/src/runtime/gc_blocks.go index b2d2fed7f5..d58bfd92a2 100644 --- a/src/runtime/gc_blocks.go +++ b/src/runtime/gc_blocks.go @@ -464,12 +464,13 @@ func runGC() (freeBytes uintptr) { // Therefore we need to scan the runqueue separately. var markedTaskQueue task.Queue runqueueScan: + runqueue := schedulerRunQueue() for !runqueue.Empty() { // Pop the next task off of the runqueue. t := runqueue.Pop() // Mark the task if it has not already been marked. - markRoot(uintptr(unsafe.Pointer(&runqueue)), uintptr(unsafe.Pointer(t))) + markRoot(uintptr(unsafe.Pointer(runqueue)), uintptr(unsafe.Pointer(t))) // Push the task onto our temporary queue. markedTaskQueue.Push(t) @@ -484,7 +485,7 @@ func runGC() (freeBytes uintptr) { interrupt.Restore(i) goto runqueueScan } - runqueue = markedTaskQueue + *runqueue = markedTaskQueue interrupt.Restore(i) } else { finishMark() diff --git a/src/runtime/scheduler.go b/src/runtime/scheduler.go index 8ba461e4db..d84ccf3e0e 100644 --- a/src/runtime/scheduler.go +++ b/src/runtime/scheduler.go @@ -1,36 +1,11 @@ package runtime -// This file implements the TinyGo scheduler. This scheduler is a very simple -// cooperative round robin scheduler, with a runqueue that contains a linked -// list of goroutines (tasks) that should be run next, in order of when they -// were added to the queue (first-in, first-out). It also contains a sleep queue -// with sleeping goroutines in order of when they should be re-activated. -// -// The scheduler is used both for the asyncify based scheduler and for the task -// based scheduler. In both cases, the 'internal/task.Task' type is used to represent one -// goroutine. - -import ( - "internal/task" - "runtime/interrupt" -) +import "internal/task" const schedulerDebug = false -// On JavaScript, we can't do a blocking sleep. Instead we have to return and -// queue a new scheduler invocation using setTimeout. -const asyncScheduler = GOOS == "js" - var mainExited bool -// Queues used by the scheduler. -var ( - runqueue task.Queue - sleepQueue *task.Task - sleepQueueBaseTime timeUnit - timerQueue *timerNode -) - // Simple logging, for debugging. func scheduleLog(msg string) { if schedulerDebug { @@ -52,204 +27,12 @@ func scheduleLogChan(msg string, ch *channel, t *task.Task) { } } -// deadlock is called when a goroutine cannot proceed any more, but is in theory -// not exited (so deferred calls won't run). This can happen for example in code -// like this, that blocks forever: -// -// select{} -// -//go:noinline -func deadlock() { - // call yield without requesting a wakeup - task.Pause() - panic("unreachable") -} - // Goexit terminates the currently running goroutine. No other goroutines are affected. // // Unlike the main Go implementation, no deferred calls will be run. // //go:inline func Goexit() { - // its really just a deadlock + // TODO: run deferred functions deadlock() } - -// Add this task to the end of the run queue. -func runqueuePushBack(t *task.Task) { - runqueue.Push(t) -} - -// Add this task to the sleep queue, assuming its state is set to sleeping. -func addSleepTask(t *task.Task, duration timeUnit) { - if schedulerDebug { - println(" set sleep:", t, duration) - if t.Next != nil { - panic("runtime: addSleepTask: expected next task to be nil") - } - } - t.Data = uint64(duration) - now := ticks() - if sleepQueue == nil { - scheduleLog(" -> sleep new queue") - - // set new base time - sleepQueueBaseTime = now - } - - // Add to sleep queue. - q := &sleepQueue - for ; *q != nil; q = &(*q).Next { - if t.Data < (*q).Data { - // this will finish earlier than the next - insert here - break - } else { - // this will finish later - adjust delay - t.Data -= (*q).Data - } - } - if *q != nil { - // cut delay time between this sleep task and the next - (*q).Data -= t.Data - } - t.Next = *q - *q = t -} - -// addTimer adds the given timer node to the timer queue. It must not be in the -// queue already. -// This function is very similar to addSleepTask but for timerQueue instead of -// sleepQueue. -func addTimer(tim *timerNode) { - mask := interrupt.Disable() - - // Add to timer queue. - q := &timerQueue - for ; *q != nil; q = &(*q).next { - if tim.whenTicks() < (*q).whenTicks() { - // this will finish earlier than the next - insert here - break - } - } - tim.next = *q - *q = tim - interrupt.Restore(mask) -} - -// removeTimer is the implementation of time.stopTimer. It removes a timer from -// the timer queue, returning true if the timer is present in the timer queue. -func removeTimer(tim *timer) bool { - removedTimer := false - mask := interrupt.Disable() - for t := &timerQueue; *t != nil; t = &(*t).next { - if (*t).timer == tim { - scheduleLog("removed timer") - *t = (*t).next - removedTimer = true - break - } - } - if !removedTimer { - scheduleLog("did not remove timer") - } - interrupt.Restore(mask) - return removedTimer -} - -// Run the scheduler until all tasks have finished. -// There are a few special cases: -// - When returnAtDeadlock is true, it also returns when there are no more -// runnable goroutines. -// - When using the asyncify scheduler, it returns when it has to wait -// (JavaScript uses setTimeout so the scheduler must return to the JS -// environment). -func scheduler(returnAtDeadlock bool) { - // Main scheduler loop. - var now timeUnit - for !mainExited { - scheduleLog("") - scheduleLog(" schedule") - if sleepQueue != nil || timerQueue != nil { - now = ticks() - } - - // Add tasks that are done sleeping to the end of the runqueue so they - // will be executed soon. - if sleepQueue != nil && now-sleepQueueBaseTime >= timeUnit(sleepQueue.Data) { - t := sleepQueue - scheduleLogTask(" awake:", t) - sleepQueueBaseTime += timeUnit(t.Data) - sleepQueue = t.Next - t.Next = nil - runqueue.Push(t) - } - - // Check for expired timers to trigger. - if timerQueue != nil && now >= timerQueue.whenTicks() { - scheduleLog("--- timer awoke") - delay := ticksToNanoseconds(now - timerQueue.whenTicks()) - // Pop timer from queue. - tn := timerQueue - timerQueue = tn.next - tn.next = nil - // Run the callback stored in this timer node. - tn.callback(tn, delay) - } - - t := runqueue.Pop() - if t == nil { - if sleepQueue == nil && timerQueue == nil { - if returnAtDeadlock { - return - } - if asyncScheduler { - // JavaScript is treated specially, see below. - return - } - waitForEvents() - continue - } - - var timeLeft timeUnit - if sleepQueue != nil { - timeLeft = timeUnit(sleepQueue.Data) - (now - sleepQueueBaseTime) - } - if timerQueue != nil { - timeLeftForTimer := timerQueue.whenTicks() - now - if sleepQueue == nil || timeLeftForTimer < timeLeft { - timeLeft = timeLeftForTimer - } - } - - if schedulerDebug { - println(" sleeping...", sleepQueue, uint(timeLeft)) - for t := sleepQueue; t != nil; t = t.Next { - println(" task sleeping:", t, timeUnit(t.Data)) - } - for tim := timerQueue; tim != nil; tim = tim.next { - println("--- timer waiting:", tim, tim.whenTicks()) - } - } - if timeLeft > 0 { - sleepTicks(timeLeft) - if asyncScheduler { - // The sleepTicks function above only sets a timeout at - // which point the scheduler will be called again. It does - // not really sleep. So instead of sleeping, we return and - // expect to be called again. - break - } - } - continue - } - - // Run the given task. - scheduleLogTask(" run:", t) - t.Resume() - } -} - -func Gosched() { - runqueue.Push(task.Current()) - task.Pause() -} diff --git a/src/runtime/scheduler_any.go b/src/runtime/scheduler_any.go deleted file mode 100644 index 2e7861a156..0000000000 --- a/src/runtime/scheduler_any.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build !scheduler.none - -package runtime - -import "internal/task" - -// Pause the current task for a given time. -// -//go:linkname sleep time.Sleep -func sleep(duration int64) { - if duration <= 0 { - return - } - - addSleepTask(task.Current(), nanosecondsToTicks(duration)) - task.Pause() -} - -// run is called by the program entry point to execute the go program. -// With a scheduler, init and the main function are invoked in a goroutine before starting the scheduler. -func run() { - initHeap() - go func() { - initAll() - callMain() - mainExited = true - }() - scheduler(false) -} - -const hasScheduler = true diff --git a/src/runtime/scheduler_cooperative.go b/src/runtime/scheduler_cooperative.go new file mode 100644 index 0000000000..5c4dfd5bf5 --- /dev/null +++ b/src/runtime/scheduler_cooperative.go @@ -0,0 +1,252 @@ +//go:build scheduler.tasks || scheduler.asyncify + +package runtime + +// This file implements the TinyGo scheduler. This scheduler is a very simple +// cooperative round robin scheduler, with a runqueue that contains a linked +// list of goroutines (tasks) that should be run next, in order of when they +// were added to the queue (first-in, first-out). It also contains a sleep queue +// with sleeping goroutines in order of when they should be re-activated. +// +// The scheduler is used both for the asyncify based scheduler and for the task +// based scheduler. In both cases, the 'internal/task.Task' type is used to represent one +// goroutine. + +import ( + "internal/task" + "runtime/interrupt" +) + +// On JavaScript, we can't do a blocking sleep. Instead we have to return and +// queue a new scheduler invocation using setTimeout. +const asyncScheduler = GOOS == "js" + +// Queues used by the scheduler. +var ( + runqueue task.Queue + sleepQueue *task.Task + sleepQueueBaseTime timeUnit + timerQueue *timerNode +) + +// deadlock is called when a goroutine cannot proceed any more, but is in theory +// not exited (so deferred calls won't run). This can happen for example in code +// like this, that blocks forever: +// +// select{} +// +//go:noinline +func deadlock() { + // call yield without requesting a wakeup + task.Pause() + panic("unreachable") +} + +// Add this task to the end of the run queue. +func scheduleTask(t *task.Task) { + runqueue.Push(t) +} + +func Gosched() { + runqueue.Push(task.Current()) + task.Pause() +} + +// Add this task to the sleep queue, assuming its state is set to sleeping. +func addSleepTask(t *task.Task, duration timeUnit) { + if schedulerDebug { + println(" set sleep:", t, duration) + if t.Next != nil { + panic("runtime: addSleepTask: expected next task to be nil") + } + } + t.Data = uint64(duration) + now := ticks() + if sleepQueue == nil { + scheduleLog(" -> sleep new queue") + + // set new base time + sleepQueueBaseTime = now + } + + // Add to sleep queue. + q := &sleepQueue + for ; *q != nil; q = &(*q).Next { + if t.Data < (*q).Data { + // this will finish earlier than the next - insert here + break + } else { + // this will finish later - adjust delay + t.Data -= (*q).Data + } + } + if *q != nil { + // cut delay time between this sleep task and the next + (*q).Data -= t.Data + } + t.Next = *q + *q = t +} + +// addTimer adds the given timer node to the timer queue. It must not be in the +// queue already. +// This function is very similar to addSleepTask but for timerQueue instead of +// sleepQueue. +func addTimer(tim *timerNode) { + mask := interrupt.Disable() + + // Add to timer queue. + q := &timerQueue + for ; *q != nil; q = &(*q).next { + if tim.whenTicks() < (*q).whenTicks() { + // this will finish earlier than the next - insert here + break + } + } + tim.next = *q + *q = tim + interrupt.Restore(mask) +} + +// removeTimer is the implementation of time.stopTimer. It removes a timer from +// the timer queue, returning true if the timer is present in the timer queue. +func removeTimer(tim *timer) bool { + removedTimer := false + mask := interrupt.Disable() + for t := &timerQueue; *t != nil; t = &(*t).next { + if (*t).timer == tim { + scheduleLog("removed timer") + *t = (*t).next + removedTimer = true + break + } + } + if !removedTimer { + scheduleLog("did not remove timer") + } + interrupt.Restore(mask) + return removedTimer +} + +func schedulerRunQueue() *task.Queue { + return &runqueue +} + +// Run the scheduler until all tasks have finished. +// There are a few special cases: +// - When returnAtDeadlock is true, it also returns when there are no more +// runnable goroutines. +// - When using the asyncify scheduler, it returns when it has to wait +// (JavaScript uses setTimeout so the scheduler must return to the JS +// environment). +func scheduler(returnAtDeadlock bool) { + // Main scheduler loop. + var now timeUnit + for !mainExited { + scheduleLog("") + scheduleLog(" schedule") + if sleepQueue != nil || timerQueue != nil { + now = ticks() + } + + // Add tasks that are done sleeping to the end of the runqueue so they + // will be executed soon. + if sleepQueue != nil && now-sleepQueueBaseTime >= timeUnit(sleepQueue.Data) { + t := sleepQueue + scheduleLogTask(" awake:", t) + sleepQueueBaseTime += timeUnit(t.Data) + sleepQueue = t.Next + t.Next = nil + runqueue.Push(t) + } + + // Check for expired timers to trigger. + if timerQueue != nil && now >= timerQueue.whenTicks() { + scheduleLog("--- timer awoke") + delay := ticksToNanoseconds(now - timerQueue.whenTicks()) + // Pop timer from queue. + tn := timerQueue + timerQueue = tn.next + tn.next = nil + // Run the callback stored in this timer node. + tn.callback(tn, delay) + } + + t := runqueue.Pop() + if t == nil { + if sleepQueue == nil && timerQueue == nil { + if returnAtDeadlock { + return + } + if asyncScheduler { + // JavaScript is treated specially, see below. + return + } + waitForEvents() + continue + } + + var timeLeft timeUnit + if sleepQueue != nil { + timeLeft = timeUnit(sleepQueue.Data) - (now - sleepQueueBaseTime) + } + if timerQueue != nil { + timeLeftForTimer := timerQueue.whenTicks() - now + if sleepQueue == nil || timeLeftForTimer < timeLeft { + timeLeft = timeLeftForTimer + } + } + + if schedulerDebug { + println(" sleeping...", sleepQueue, uint(timeLeft)) + for t := sleepQueue; t != nil; t = t.Next { + println(" task sleeping:", t, timeUnit(t.Data)) + } + for tim := timerQueue; tim != nil; tim = tim.next { + println("--- timer waiting:", tim, tim.whenTicks()) + } + } + if timeLeft > 0 { + sleepTicks(timeLeft) + if asyncScheduler { + // The sleepTicks function above only sets a timeout at + // which point the scheduler will be called again. It does + // not really sleep. So instead of sleeping, we return and + // expect to be called again. + break + } + } + continue + } + + // Run the given task. + scheduleLogTask(" run:", t) + t.Resume() + } +} + +// Pause the current task for a given time. +// +//go:linkname sleep time.Sleep +func sleep(duration int64) { + if duration <= 0 { + return + } + + addSleepTask(task.Current(), nanosecondsToTicks(duration)) + task.Pause() +} + +// run is called by the program entry point to execute the go program. +// With a scheduler, init and the main function are invoked in a goroutine before starting the scheduler. +func run() { + initHeap() + go func() { + initAll() + callMain() + mainExited = true + }() + scheduler(false) +} + +const hasScheduler = true diff --git a/src/runtime/scheduler_none.go b/src/runtime/scheduler_none.go index 5079a80853..7775b360eb 100644 --- a/src/runtime/scheduler_none.go +++ b/src/runtime/scheduler_none.go @@ -2,6 +2,19 @@ package runtime +import "internal/task" + +const hasScheduler = false + +// run is called by the program entry point to execute the go program. +// With the "none" scheduler, init and the main function are invoked directly. +func run() { + initHeap() + initAll() + callMain() + mainExited = true +} + //go:linkname sleep time.Sleep func sleep(duration int64) { if duration <= 0 { @@ -11,18 +24,43 @@ func sleep(duration int64) { sleepTicks(nanosecondsToTicks(duration)) } +func deadlock() { + // The only goroutine available is deadlocked. + runtimePanic("all goroutines are asleep - deadlock!") +} + +func scheduleTask(t *task.Task) { + // Pause() will panic, so this should not be reachable. +} + +func Gosched() { + // There are no other goroutines, so there's nothing to schedule. +} + +func addTimer(tim *timerNode) { + runtimePanic("timers not supported without a scheduler") +} + +func removeTimer(tim *timer) bool { + runtimePanic("timers not supported without a scheduler") + return false +} + +func schedulerRunQueue() *task.Queue { + // This function is not actually used, it is only called when hasScheduler + // is true. + runtimePanic("unreachable: no runqueue without a scheduler") + return nil +} + +func scheduler(returnAtDeadlock bool) { + // The scheduler should never be run when using -scheduler=none. Meaning, + // this code should be unreachable. + runtimePanic("unreachable: scheduler must not be called with the 'none' scheduler") +} + // getSystemStackPointer returns the current stack pointer of the system stack. // This is always the current stack pointer. func getSystemStackPointer() uintptr { return getCurrentStackPointer() } - -// run is called by the program entry point to execute the go program. -// With the "none" scheduler, init and the main function are invoked directly. -func run() { - initHeap() - initAll() - callMain() -} - -const hasScheduler = false diff --git a/src/sync/mutex.go b/src/sync/mutex.go index 3db705af0c..b62b9fafdb 100644 --- a/src/sync/mutex.go +++ b/src/sync/mutex.go @@ -10,7 +10,7 @@ type Mutex struct { blocked task.Stack } -//go:linkname scheduleTask runtime.runqueuePushBack +//go:linkname scheduleTask runtime.scheduleTask func scheduleTask(*task.Task) func (m *Mutex) Lock() { From fd625f7265ed316ee28c1b322e19851ce9fd61f0 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 2 Sep 2023 13:11:36 +0200 Subject: [PATCH 286/444] compiler: add //go:noescape pragma This only works on declarations, not definitions. This is intentional: it follows the upstream Go implemetation. However, we might want to loosen this requirement at some point: TinyGo sometimes stores pointers in memory mapped I/O knowing they won't actually escape, but the compiler doesn't know about this. --- compiler/calls.go | 12 +++++++----- compiler/symbol.go | 23 ++++++++++++++++++++--- compiler/testdata/pragma.go | 9 +++++++++ compiler/testdata/pragma.ll | 8 ++++++++ 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/compiler/calls.go b/compiler/calls.go index f4b76a5135..6400e634bd 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -19,8 +19,9 @@ const maxFieldsPerParam = 3 // useful while declaring or defining a function. type paramInfo struct { llvmType llvm.Type - name string // name, possibly with suffixes for e.g. struct fields - elemSize uint64 // size of pointer element type, or 0 if this isn't a pointer + name string // name, possibly with suffixes for e.g. struct fields + elemSize uint64 // size of pointer element type, or 0 if this isn't a pointer + flags paramFlags // extra flags for this parameter } // paramFlags identifies parameter attributes for flags. Most importantly, it @@ -28,9 +29,9 @@ type paramInfo struct { type paramFlags uint8 const ( - // Parameter may have the deferenceable_or_null attribute. This attribute - // cannot be applied to unsafe.Pointer and to the data pointer of slices. - paramIsDeferenceableOrNull = 1 << iota + // Whether this is a full or partial Go parameter (int, slice, etc). + // The extra context parameter is not a Go parameter. + paramIsGoParam = 1 << iota ) // createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or @@ -195,6 +196,7 @@ func (c *compilerContext) getParamInfo(t llvm.Type, name string, goType types.Ty info := paramInfo{ llvmType: t, name: name, + flags: paramIsGoParam, } if goType != nil { switch underlying := goType.Underlying().(type) { diff --git a/compiler/symbol.go b/compiler/symbol.go index e53df30f1c..93c27803e2 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -33,6 +33,7 @@ type functionInfo struct { exported bool // go:export, CGo interrupt bool // go:interrupt nobounds bool // go:nobounds + noescape bool // go:noescape variadic bool // go:variadic (CGo only) inline inlineType // go:inline } @@ -127,11 +128,20 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) c.addStandardDeclaredAttributes(llvmFn) dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null") - for i, info := range paramInfos { - if info.elemSize != 0 { - dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, info.elemSize) + for i, paramInfo := range paramInfos { + if paramInfo.elemSize != 0 { + dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, paramInfo.elemSize) llvmFn.AddAttributeAtIndex(i+1, dereferenceableOrNull) } + if info.noescape && paramInfo.flags¶mIsGoParam != 0 && paramInfo.llvmType.TypeKind() == llvm.PointerTypeKind { + // Parameters to functions with a //go:noescape parameter should get + // the nocapture attribute. However, the context parameter should + // not. + // (It may be safe to add the nocapture parameter to the context + // parameter, but I'd like to stay on the safe side here). + nocapture := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0) + llvmFn.AddAttributeAtIndex(i+1, nocapture) + } } // Set a number of function or parameter attributes, depending on the @@ -394,6 +404,13 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { if hasUnsafeImport(f.Pkg.Pkg) { info.nobounds = true } + case "//go:noescape": + // Don't let pointer parameters escape. + // Following the upstream Go implementation, we only do this for + // declarations, not definitions. + if len(f.Blocks) == 0 { + info.noescape = true + } case "//go:variadic": // The //go:variadic pragma is emitted by the CGo preprocessing // pass for C variadic functions. This includes both explicit diff --git a/compiler/testdata/pragma.go b/compiler/testdata/pragma.go index 1f6badf7fb..1e6e967f53 100644 --- a/compiler/testdata/pragma.go +++ b/compiler/testdata/pragma.go @@ -106,3 +106,12 @@ var undefinedGlobalNotInSection uint32 //go:align 1024 //go:section .global_section var multipleGlobalPragmas uint32 + +//go:noescape +func doesNotEscapeParam(a *int, b []int, c chan int, d *[0]byte) + +// The //go:noescape pragma only works on declarations, not definitions. +// +//go:noescape +func stillEscapes(a *int, b []int, c chan int, d *[0]byte) { +} diff --git a/compiler/testdata/pragma.ll b/compiler/testdata/pragma.ll index 28e678359d..fa8015a90d 100644 --- a/compiler/testdata/pragma.ll +++ b/compiler/testdata/pragma.ll @@ -85,6 +85,14 @@ entry: declare void @main.undefinedFunctionNotInSection(ptr) #1 +declare void @main.doesNotEscapeParam(ptr nocapture dereferenceable_or_null(4), ptr nocapture, i32, i32, ptr nocapture dereferenceable_or_null(32), ptr nocapture, ptr) #1 + +; Function Attrs: nounwind +define hidden void @main.stillEscapes(ptr dereferenceable_or_null(4) %a, ptr %b.data, i32 %b.len, i32 %b.cap, ptr dereferenceable_or_null(32) %c, ptr %d, ptr %context) unnamed_addr #2 { +entry: + ret void +} + attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } From b7a3fd8d2f7075e913909e50ab7a87bb811d2b01 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 2 Sep 2023 13:52:21 +0200 Subject: [PATCH 287/444] cgo: add support for `#cgo noescape` lines Here is the proposal: https://github.com/golang/go/issues/56378 They are documented here: https://pkg.go.dev/cmd/cgo@master#hdr-Optimizing_calls_of_C_code This would have been very useful to fix https://github.com/tinygo-org/bluetooth/issues/176 in a nice way. That bug is now fixed in a different way using a wrapper function, but once this new noescape pragma gets included in TinyGo we could remove the workaround and use `#cgo noescape` instead. --- cgo/cgo.go | 53 +++++++++++++++++++++++++++++++++++++ cgo/libclang.go | 10 ++++++- cgo/testdata/errors.go | 5 ++++ cgo/testdata/errors.out.go | 17 +++++++----- cgo/testdata/symbols.go | 5 ++++ cgo/testdata/symbols.out.go | 5 ++++ 6 files changed, 87 insertions(+), 8 deletions(-) diff --git a/cgo/cgo.go b/cgo/cgo.go index 0ed26e3fb8..cb822a38ec 100644 --- a/cgo/cgo.go +++ b/cgo/cgo.go @@ -18,6 +18,7 @@ import ( "go/scanner" "go/token" "path/filepath" + "sort" "strconv" "strings" @@ -42,6 +43,7 @@ type cgoPackage struct { fset *token.FileSet tokenFiles map[string]*token.File definedGlobally map[string]ast.Node + noescapingFuncs map[string]*noescapingFunc // #cgo noescape lines anonDecls map[interface{}]string cflags []string // CFlags from #cgo lines ldflags []string // LDFlags from #cgo lines @@ -80,6 +82,13 @@ type bitfieldInfo struct { endBit int64 // may be 0 meaning "until the end of the field" } +// Information about a #cgo noescape line in the source code. +type noescapingFunc struct { + name string + pos token.Pos + used bool // true if used somewhere in the source (for proper error reporting) +} + // cgoAliases list type aliases between Go and C, for types that are equivalent // in both languages. See addTypeAliases. var cgoAliases = map[string]string{ @@ -255,6 +264,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl fset: fset, tokenFiles: map[string]*token.File{}, definedGlobally: map[string]ast.Node{}, + noescapingFuncs: map[string]*noescapingFunc{}, anonDecls: map[interface{}]string{}, visitedFiles: map[string][]byte{}, } @@ -418,6 +428,22 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl }) } + // Show an error when a #cgo noescape line isn't used in practice. + // This matches upstream Go. I think the goal is to avoid issues with + // misspelled function names, which seems very useful. + var unusedNoescapeLines []*noescapingFunc + for _, value := range p.noescapingFuncs { + if !value.used { + unusedNoescapeLines = append(unusedNoescapeLines, value) + } + } + sort.SliceStable(unusedNoescapeLines, func(i, j int) bool { + return unusedNoescapeLines[i].pos < unusedNoescapeLines[j].pos + }) + for _, value := range unusedNoescapeLines { + p.addError(value.pos, fmt.Sprintf("function %#v in #cgo noescape line is not used", value.name)) + } + // Print the newly generated in-memory AST, for debugging. //ast.Print(fset, p.generated) @@ -483,6 +509,33 @@ func (p *cgoPackage) parseCGoPreprocessorLines(text string, pos token.Pos) strin } text = text[:lineStart] + string(spaces) + text[lineEnd:] + allFields := strings.Fields(line[4:]) + switch allFields[0] { + case "noescape": + // The code indicates that pointer parameters will not be captured + // by the called C function. + if len(allFields) < 2 { + p.addErrorAfter(pos, text[:lineStart], "missing function name in #cgo noescape line") + continue + } + if len(allFields) > 2 { + p.addErrorAfter(pos, text[:lineStart], "multiple function names in #cgo noescape line") + continue + } + name := allFields[1] + p.noescapingFuncs[name] = &noescapingFunc{ + name: name, + pos: pos, + used: false, + } + continue + case "nocallback": + // We don't do anything special when calling a C function, so there + // appears to be no optimization that we can do here. + // Accept, but ignore the parameter for compatibility. + continue + } + // Get the text before the colon in the #cgo directive. colon := strings.IndexByte(line, ':') if colon < 0 { diff --git a/cgo/libclang.go b/cgo/libclang.go index 794d4e81f1..54ff9f53fb 100644 --- a/cgo/libclang.go +++ b/cgo/libclang.go @@ -256,10 +256,18 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { }, }, } + var doc []string if C.clang_isFunctionTypeVariadic(cursorType) != 0 { + doc = append(doc, "//go:variadic") + } + if _, ok := f.noescapingFuncs[name]; ok { + doc = append(doc, "//go:noescape") + f.noescapingFuncs[name].used = true + } + if len(doc) != 0 { decl.Doc.List = append(decl.Doc.List, &ast.Comment{ Slash: pos - 1, - Text: "//go:variadic", + Text: strings.Join(doc, "\n"), }) } for i := 0; i < numArgs; i++ { diff --git a/cgo/testdata/errors.go b/cgo/testdata/errors.go index 75828ce0f1..8813f83cf4 100644 --- a/cgo/testdata/errors.go +++ b/cgo/testdata/errors.go @@ -10,6 +10,11 @@ typedef struct { typedef someType noType; // undefined type +// Some invalid noescape lines +#cgo noescape +#cgo noescape foo bar +#cgo noescape unusedFunction + #define SOME_CONST_1 5) // invalid const syntax #define SOME_CONST_2 6) // const not used (so no error) #define SOME_CONST_3 1234 // const too large for byte diff --git a/cgo/testdata/errors.out.go b/cgo/testdata/errors.out.go index cbac4fceb3..e0f7d1f541 100644 --- a/cgo/testdata/errors.out.go +++ b/cgo/testdata/errors.out.go @@ -1,14 +1,17 @@ // CGo errors: +// testdata/errors.go:14:1: missing function name in #cgo noescape line +// testdata/errors.go:15:1: multiple function names in #cgo noescape line // testdata/errors.go:4:2: warning: some warning // testdata/errors.go:11:9: error: unknown type name 'someType' -// testdata/errors.go:26:5: warning: another warning -// testdata/errors.go:13:23: unexpected token ), expected end of expression -// testdata/errors.go:21:26: unexpected token ), expected end of expression -// testdata/errors.go:16:33: unexpected token ), expected end of expression -// testdata/errors.go:17:34: unexpected token ), expected end of expression +// testdata/errors.go:31:5: warning: another warning +// testdata/errors.go:18:23: unexpected token ), expected end of expression +// testdata/errors.go:26:26: unexpected token ), expected end of expression +// testdata/errors.go:21:33: unexpected token ), expected end of expression +// testdata/errors.go:22:34: unexpected token ), expected end of expression // -: unexpected token INT, expected end of expression -// testdata/errors.go:30:35: unexpected number of parameters: expected 2, got 3 -// testdata/errors.go:31:31: unexpected number of parameters: expected 2, got 1 +// testdata/errors.go:35:35: unexpected number of parameters: expected 2, got 3 +// testdata/errors.go:36:31: unexpected number of parameters: expected 2, got 1 +// testdata/errors.go:3:1: function "unusedFunction" in #cgo noescape line is not used // Type checking errors after CGo processing: // testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as C.char value in variable declaration (overflows) diff --git a/cgo/testdata/symbols.go b/cgo/testdata/symbols.go index fb585c2f87..c8029a1481 100644 --- a/cgo/testdata/symbols.go +++ b/cgo/testdata/symbols.go @@ -9,6 +9,10 @@ static void staticfunc(int x); // Global variable signatures. extern int someValue; + +void notEscapingFunction(int *a); + +#cgo noescape notEscapingFunction */ import "C" @@ -18,6 +22,7 @@ func accessFunctions() { C.variadic0() C.variadic2(3, 5) C.staticfunc(3) + C.notEscapingFunction(nil) } func accessGlobals() { diff --git a/cgo/testdata/symbols.out.go b/cgo/testdata/symbols.out.go index b49150e601..2ca80c1e65 100644 --- a/cgo/testdata/symbols.out.go +++ b/cgo/testdata/symbols.out.go @@ -75,5 +75,10 @@ func C.staticfunc!symbols.go(x C.int) var C.staticfunc!symbols.go$funcaddr unsafe.Pointer +//export notEscapingFunction +//go:noescape +func C.notEscapingFunction(a *C.int) + +var C.notEscapingFunction$funcaddr unsafe.Pointer //go:extern someValue var C.someValue C.int From 5ebda89d787af6d27cfde6d9c0f3af13b1270959 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 27 Oct 2024 08:44:19 +0100 Subject: [PATCH 288/444] runtime: rewrite channel implementation This rewrite simplifies the channel implementation considerably, with 34% less LOC. Perhaps the most important change is the removal of the channel state, which made sense when we had only send and receive operations but only makes things more compliated when multiple select operations can be pending on a single channel. I did this rewrite originally to make it possible to make channels parallelism-safe. The current implementation is not parallelism-safe, but it will be easy to make it so (the main additions will be a channel lock, a global select lock, and an atomic compare-and-swap in chanQueue.pop). --- compiler/channel.go | 37 +- compiler/testdata/channel.ll | 52 +- .../testdata/goroutine-cortex-m-qemu-tasks.ll | 4 +- compiler/testdata/goroutine-wasm-asyncify.ll | 4 +- src/runtime/chan.go | 848 +++++++----------- 5 files changed, 383 insertions(+), 562 deletions(-) diff --git a/compiler/channel.go b/compiler/channel.go index 9969835e84..7e867c2789 100644 --- a/compiler/channel.go +++ b/compiler/channel.go @@ -41,17 +41,17 @@ func (b *builder) createChanSend(instr *ssa.Send) { b.CreateStore(chanValue, valueAlloca) } - // Allocate blockedlist buffer. - channelBlockedList := b.getLLVMRuntimeType("channelBlockedList") - channelBlockedListAlloca, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList") + // Allocate buffer for the channel operation. + channelOp := b.getLLVMRuntimeType("channelOp") + channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op") // Do the send. - b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelBlockedListAlloca}, "") + b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "") // End the lifetime of the allocas. // This also works around a bug in CoroSplit, at least in LLVM 8: // https://bugs.llvm.org/show_bug.cgi?id=41742 - b.emitLifetimeEnd(channelBlockedListAlloca, channelBlockedListAllocaSize) + b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize) if !isZeroSize { b.emitLifetimeEnd(valueAlloca, valueAllocaSize) } @@ -72,12 +72,12 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value { valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value") } - // Allocate blockedlist buffer. - channelBlockedList := b.getLLVMRuntimeType("channelBlockedList") - channelBlockedListAlloca, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList") + // Allocate buffer for the channel operation. + channelOp := b.getLLVMRuntimeType("channelOp") + channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op") // Do the receive. - commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelBlockedListAlloca}, "") + commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "") var received llvm.Value if isZeroSize { received = llvm.ConstNull(valueType) @@ -85,7 +85,7 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value { received = b.CreateLoad(valueType, valueAlloca, "chan.received") b.emitLifetimeEnd(valueAlloca, valueAllocaSize) } - b.emitLifetimeEnd(channelBlockedListAlloca, channelBlockedListAllocaSize) + b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize) if unop.CommaOk { tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false)) @@ -198,10 +198,10 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value { if expr.Blocking { // Stack-allocate operation structures. // If these were simply created as a slice, they would heap-allocate. - chBlockAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelBlockedList"), len(selectStates)) - chBlockAlloca, chBlockSize := b.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca") - chBlockLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false) - chBlockPtr := b.CreateGEP(chBlockAllocaType, chBlockAlloca, []llvm.Value{ + opsAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelOp"), len(selectStates)) + opsAlloca, opsSize := b.createTemporaryAlloca(opsAllocaType, "select.block.alloca") + opsLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false) + opsPtr := b.CreateGEP(opsAllocaType, opsAlloca, []llvm.Value{ llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false), }, "select.block") @@ -209,15 +209,18 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value { results = b.createRuntimeCall("chanSelect", []llvm.Value{ recvbuf, statesPtr, statesLen, statesLen, // []chanSelectState - chBlockPtr, chBlockLen, chBlockLen, // []channelBlockList + opsPtr, opsLen, opsLen, // []channelOp }, "select.result") // Terminate the lifetime of the operation structures. - b.emitLifetimeEnd(chBlockAlloca, chBlockSize) + b.emitLifetimeEnd(opsAlloca, opsSize) } else { - results = b.createRuntimeCall("tryChanSelect", []llvm.Value{ + opsPtr := llvm.ConstNull(b.dataPtrType) + opsLen := llvm.ConstInt(b.uintptrType, 0, false) + results = b.createRuntimeCall("chanSelect", []llvm.Value{ recvbuf, statesPtr, statesLen, statesLen, // []chanSelectState + opsPtr, opsLen, opsLen, // []channelOp (nil slice) }, "select.result") } diff --git a/compiler/testdata/channel.ll b/compiler/testdata/channel.ll index 65e18dea85..68982d051c 100644 --- a/compiler/testdata/channel.ll +++ b/compiler/testdata/channel.ll @@ -3,7 +3,7 @@ source_filename = "channel.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.channelBlockedList = type { ptr, ptr, ptr, { ptr, i32, i32 } } +%runtime.channelOp = type { ptr, ptr, i32, ptr } %runtime.chanSelectState = type { ptr, ptr } ; Function Attrs: allockind("alloc,zeroed") allocsize(0) @@ -18,15 +18,15 @@ entry: } ; Function Attrs: nounwind -define hidden void @main.chanIntSend(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 { +define hidden void @main.chanIntSend(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 { entry: - %chan.blockedList = alloca %runtime.channelBlockedList, align 8 + %chan.op = alloca %runtime.channelOp, align 8 %chan.value = alloca i32, align 4 call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %chan.value) store i32 3, ptr %chan.value, align 4 - call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList) - call void @runtime.chanSend(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.blockedList, ptr undef) #4 - call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList) + call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) + call void @runtime.chanSend(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #4 + call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %chan.value) ret void } @@ -34,48 +34,48 @@ entry: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #3 -declare void @runtime.chanSend(ptr dereferenceable_or_null(32), ptr, ptr dereferenceable_or_null(24), ptr) #1 +declare void @runtime.chanSend(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #1 ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #3 ; Function Attrs: nounwind -define hidden void @main.chanIntRecv(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 { +define hidden void @main.chanIntRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 { entry: - %chan.blockedList = alloca %runtime.channelBlockedList, align 8 + %chan.op = alloca %runtime.channelOp, align 8 %chan.value = alloca i32, align 4 call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %chan.value) - call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList) - %0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.blockedList, ptr undef) #4 + call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) + %0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #4 call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %chan.value) - call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList) + call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) ret void } -declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(32), ptr, ptr dereferenceable_or_null(24), ptr) #1 +declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #1 ; Function Attrs: nounwind -define hidden void @main.chanZeroSend(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 { +define hidden void @main.chanZeroSend(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 { entry: - %chan.blockedList = alloca %runtime.channelBlockedList, align 8 - call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList) - call void @runtime.chanSend(ptr %ch, ptr null, ptr nonnull %chan.blockedList, ptr undef) #4 - call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList) + %chan.op = alloca %runtime.channelOp, align 8 + call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) + call void @runtime.chanSend(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #4 + call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) ret void } ; Function Attrs: nounwind -define hidden void @main.chanZeroRecv(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 { +define hidden void @main.chanZeroRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 { entry: - %chan.blockedList = alloca %runtime.channelBlockedList, align 8 - call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList) - %0 = call i1 @runtime.chanRecv(ptr %ch, ptr null, ptr nonnull %chan.blockedList, ptr undef) #4 - call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList) + %chan.op = alloca %runtime.channelOp, align 8 + call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) + %0 = call i1 @runtime.chanRecv(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #4 + call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) ret void } ; Function Attrs: nounwind -define hidden void @main.selectZeroRecv(ptr dereferenceable_or_null(32) %ch1, ptr dereferenceable_or_null(32) %ch2, ptr %context) unnamed_addr #2 { +define hidden void @main.selectZeroRecv(ptr dereferenceable_or_null(36) %ch1, ptr dereferenceable_or_null(36) %ch2, ptr %context) unnamed_addr #2 { entry: %select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8 %select.send.value = alloca i32, align 4 @@ -88,7 +88,7 @@ entry: store ptr %ch2, ptr %0, align 4 %.repack3 = getelementptr inbounds [2 x %runtime.chanSelectState], ptr %select.states.alloca, i32 0, i32 1, i32 1 store ptr null, ptr %.repack3, align 4 - %select.result = call { i32, i1 } @runtime.tryChanSelect(ptr undef, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr undef) #4 + %select.result = call { i32, i1 } @runtime.chanSelect(ptr undef, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr null, i32 0, i32 0, ptr undef) #4 call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %select.states.alloca) %1 = extractvalue { i32, i1 } %select.result, 0 %2 = icmp eq i32 %1, 0 @@ -105,7 +105,7 @@ select.body: ; preds = %select.next br label %select.done } -declare { i32, i1 } @runtime.tryChanSelect(ptr, ptr, i32, i32, ptr) #1 +declare { i32, i1 } @runtime.chanSelect(ptr, ptr, i32, i32, ptr, i32, i32, ptr) #1 attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } diff --git a/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll b/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll index f149f3a0cf..a57bb20f36 100644 --- a/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll +++ b/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll @@ -135,13 +135,13 @@ entry: declare i32 @runtime.sliceCopy(ptr nocapture writeonly, ptr nocapture readonly, i32, i32, i32, ptr) #2 ; Function Attrs: nounwind -define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #1 { +define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 { entry: call void @runtime.chanClose(ptr %ch, ptr undef) #9 ret void } -declare void @runtime.chanClose(ptr dereferenceable_or_null(32), ptr) #2 +declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #2 ; Function Attrs: nounwind define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 { diff --git a/compiler/testdata/goroutine-wasm-asyncify.ll b/compiler/testdata/goroutine-wasm-asyncify.ll index 699b9f2057..c4af760371 100644 --- a/compiler/testdata/goroutine-wasm-asyncify.ll +++ b/compiler/testdata/goroutine-wasm-asyncify.ll @@ -144,13 +144,13 @@ entry: declare i32 @runtime.sliceCopy(ptr nocapture writeonly, ptr nocapture readonly, i32, i32, i32, ptr) #1 ; Function Attrs: nounwind -define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 { +define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 { entry: call void @runtime.chanClose(ptr %ch, ptr undef) #9 ret void } -declare void @runtime.chanClose(ptr dereferenceable_or_null(32), ptr) #1 +declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #1 ; Function Attrs: nounwind define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #2 { diff --git a/src/runtime/chan.go b/src/runtime/chan.go index 1f0d7ced8d..c62685700e 100644 --- a/src/runtime/chan.go +++ b/src/runtime/chan.go @@ -1,27 +1,45 @@ package runtime // This file implements the 'chan' type and send/receive/select operations. - -// A channel can be in one of the following states: -// empty: -// No goroutine is waiting on a send or receive operation. The 'blocked' -// member is nil. -// recv: -// A goroutine tries to receive from the channel. This goroutine is stored -// in the 'blocked' member. -// send: -// The reverse of send. A goroutine tries to send to the channel. This -// goroutine is stored in the 'blocked' member. -// closed: -// The channel is closed. Sends will panic, receives will get a zero value -// plus optionally the indication that the channel is zero (with the -// comma-ok value in the task). // -// A send/recv transmission is completed by copying from the data element of the -// sending task to the data element of the receiving task, and setting -// the 'comma-ok' value to true. -// A receive operation on a closed channel is completed by zeroing the data -// element of the receiving task and setting the 'comma-ok' value to false. +// Every channel has a list of senders and a list of receivers, and possibly a +// queue. There is no 'channel state', the state is inferred from the available +// senders/receivers and values in the buffer. +// +// - A sender will first try to send the value to a waiting receiver if there is +// one, but only if there is nothing in the queue (to keep the values flowing +// in the correct order). If it can't, it will add the value in the queue and +// possibly wait as a sender if there's no space available. +// - A receiver will first try to read a value from the queue, but if there is +// none it will try to read from a sender in the list. It will block if it +// can't proceed. +// +// State is kept in various ways: +// +// - The sender value is stored in the sender 'channelOp', which is really a +// queue entry. This works for both senders and select operations: a select +// operation has a separate value to send for each case. +// - The receiver value is stored inside Task.Ptr. This works for receivers, and +// importantly also works for select which has a single buffer for every +// receive operation. +// - The `Task.Data` value stores how the channel operation proceeded. For +// normal send/receive operations, it starts at chanOperationWaiting and then +// is changed to chanOperationOk or chanOperationClosed depending on whether +// the send/receive proceeded normally or because it was closed. For a select +// operation, it also stores the 'case' index in the upper bits (zero for +// non-select operations) so that the select operation knows which case did +// proceed. +// The value is at the same time also a way that goroutines can be the first +// (and only) goroutine to 'take' a channel operation to change it from +// 'waiting' to any other value. This is important for the select statement +// because multiple goroutines could try to let different channels in the +// select statement proceed at the same time. By using Task.Data, only a +// single channel operation in the select statement can proceed. +// - It is possible for the channel queues to contain already-processed senders +// or receivers. This can happen when the select statement managed to proceed +// but the goroutine doing the select has not yet cleaned up the stale queue +// entries before returning. This should therefore only happen for a short +// period. import ( "internal/task" @@ -29,490 +47,283 @@ import ( "unsafe" ) -func chanDebug(ch *channel) { - if schedulerDebug { - if ch.bufSize > 0 { - println("--- channel update:", ch, ch.state.String(), ch.bufSize, ch.bufUsed) - } else { - println("--- channel update:", ch, ch.state.String()) - } - } +// The runtime implementation of the Go 'chan' type. +type channel struct { + closed bool + elementSize uintptr + bufCap uintptr // 'cap' + bufLen uintptr // 'len' + bufHead uintptr + bufTail uintptr + senders chanQueue + receivers chanQueue + buf unsafe.Pointer } -// channelBlockedList is a list of channel operations on a specific channel which are currently blocked. -type channelBlockedList struct { - // next is a pointer to the next blocked channel operation on the same channel. - next *channelBlockedList - - // t is the task associated with this channel operation. - // If this channel operation is not part of a select, then the pointer field of the state holds the data buffer. - // If this channel operation is part of a select, then the pointer field of the state holds the receive buffer. - // If this channel operation is a receive, then the data field should be set to zero when resuming due to channel closure. - t *task.Task - - // s is a pointer to the channel select state corresponding to this operation. - // This will be nil if and only if this channel operation is not part of a select statement. - // If this is a send operation, then the send buffer can be found in this select state. - s *chanSelectState - - // allSelectOps is a slice containing all of the channel operations involved with this select statement. - // Before resuming the task, all other channel operations on this select statement should be canceled by removing them from their corresponding lists. - allSelectOps []channelBlockedList +const ( + chanOperationWaiting = 0b00 // waiting for a send/receive operation to continue + chanOperationOk = 0b01 // successfully sent or received (not closed) + chanOperationClosed = 0b10 // channel was closed, the value has been zeroed + chanOperationMask = 0b11 +) + +type chanQueue struct { + first *channelOp } -// remove takes the current list of blocked channel operations and removes the specified operation. -// This returns the resulting list, or nil if the resulting list is empty. -// A nil receiver is treated as an empty list. -func (b *channelBlockedList) remove(old *channelBlockedList) *channelBlockedList { - if b == old { - return b.next - } - c := b - for ; c != nil && c.next != old; c = c.next { - } - if c != nil { - c.next = old.next - } - return b +// Pus the next channel operation to the queue. All appropriate fields must have +// been initialized already. +// This function must be called with interrupts disabled. +func (q *chanQueue) push(node *channelOp) { + node.next = q.first + q.first = node } -// detach removes all other channel operations that are part of the same select statement. -// If the input is not part of a select statement, this is a no-op. -// This must be called before resuming any task blocked on a channel operation in order to ensure that it is not placed on the runqueue twice. -func (b *channelBlockedList) detach() { - if b.allSelectOps == nil { - // nothing to do - return - } - for i, v := range b.allSelectOps { - // cancel all other channel operations that are part of this select statement - switch { - case &b.allSelectOps[i] == b: - // This entry is the one that was already detached. - continue - case v.t == nil: - // This entry is not used (nil channel). - continue +// Pop the next waiting channel from the queue. Channels that are no longer +// waiting (for example, when they're part of a select operation) will be +// skipped. +// This function must be called with interrupts disabled. +func (q *chanQueue) pop(chanOp uint64) *channelOp { + for { + if q.first == nil { + return nil } - v.s.ch.blocked = v.s.ch.blocked.remove(&b.allSelectOps[i]) - if v.s.ch.blocked == nil { - if v.s.value == nil { - // recv operation - if v.s.ch.state != chanStateClosed { - v.s.ch.state = chanStateEmpty - } - } else { - // send operation - if v.s.ch.bufUsed == 0 { - // unbuffered channel - v.s.ch.state = chanStateEmpty - } else { - // buffered channel - v.s.ch.state = chanStateBuf - } - } + + // Pop next from the queue. + popped := q.first + q.first = q.first.next + + // The new value for the 'data' field will be a combination of the + // channel operation and the select index. (The select index is 0 for + // non-select channel operations). + newDataValue := chanOp | uint64(popped.index<<2) + + // Try to be the first to proceed with this goroutine. + if popped.task.Data == chanOperationWaiting { + popped.task.Data = newDataValue + return popped } - chanDebug(v.s.ch) } } -type channel struct { - elementSize uintptr // the size of one value in this channel - bufSize uintptr // size of buffer (in elements) - state chanState - blocked *channelBlockedList - bufHead uintptr // head index of buffer (next push index) - bufTail uintptr // tail index of buffer (next pop index) - bufUsed uintptr // number of elements currently in buffer - buf unsafe.Pointer // pointer to first element of buffer +// Remove the given to-be-removed node from the queue if it is part of the +// queue. If there are multiple, only one will be removed. +// This function must be called with interrupts disabled. +func (q *chanQueue) remove(remove *channelOp) { + n := &q.first + for *n != nil { + if *n == remove { + *n = (*n).next + return + } + n = &((*n).next) + } +} + +type channelOp struct { + next *channelOp + task *task.Task + index uintptr // select index, 0 for non-select operation + value unsafe.Pointer // if this is a sender, this is the value to send +} + +type chanSelectState struct { + ch *channel + value unsafe.Pointer } -// chanMake creates a new channel with the given element size and buffer length in number of elements. -// This is a compiler intrinsic. func chanMake(elementSize uintptr, bufSize uintptr) *channel { return &channel{ elementSize: elementSize, - bufSize: bufSize, + bufCap: bufSize, buf: alloc(elementSize*bufSize, nil), } } // Return the number of entries in this chan, called from the len builtin. // A nil chan is defined as having length 0. -// -//go:inline func chanLen(c *channel) int { if c == nil { return 0 } - return int(c.bufUsed) + return int(c.bufLen) } // Return the capacity of this chan, called from the cap builtin. // A nil chan is defined as having capacity 0. -// -//go:inline func chanCap(c *channel) int { if c == nil { return 0 } - return int(c.bufSize) + return int(c.bufCap) } -// resumeRX resumes the next receiver and returns the destination pointer. -// If the ok value is true, then the caller is expected to store a value into this pointer. -func (ch *channel) resumeRX(ok bool) unsafe.Pointer { - // pop a blocked goroutine off the stack - var b *channelBlockedList - b, ch.blocked = ch.blocked, ch.blocked.next - - // get destination pointer - dst := b.t.Ptr - - if !ok { - // the result value is zero - memzero(dst, ch.elementSize) - b.t.Data = 0 - } - - if b.s != nil { - // tell the select op which case resumed - b.t.Ptr = unsafe.Pointer(b.s) - - // detach associated operations - b.detach() - } - - scheduleTask(b.t) - - return dst -} - -// resumeTX resumes the next sender and returns the source pointer. -// The caller is expected to read from the value in this pointer before yielding. -func (ch *channel) resumeTX() unsafe.Pointer { - // pop a blocked goroutine off the stack - var b *channelBlockedList - b, ch.blocked = ch.blocked, ch.blocked.next - - // get source pointer - src := b.t.Ptr - - if b.s != nil { - // use state's source pointer - src = b.s.value - - // tell the select op which case resumed - b.t.Ptr = unsafe.Pointer(b.s) - - // detach associated operations - b.detach() - } - - scheduleTask(b.t) - - return src -} - -// push value to end of channel if space is available -// returns whether there was space for the value in the buffer -func (ch *channel) push(value unsafe.Pointer) bool { - // immediately return false if the channel is not buffered - if ch.bufSize == 0 { - return false - } - - // ensure space is available - if ch.bufUsed == ch.bufSize { - return false - } - - // copy value to buffer - memcpy( - unsafe.Add(ch.buf, // pointer to the base of the buffer + offset = pointer to destination element - ch.elementSize*ch.bufHead), // element size * equivalent slice index = offset - value, - ch.elementSize, - ) - - // update buffer state - ch.bufUsed++ +// Push the value to the channel buffer array, for a send operation. +// This function may only be called when interrupts are disabled and it is known +// there is space available in the buffer. +func (ch *channel) bufferPush(value unsafe.Pointer) { + elemAddr := unsafe.Add(ch.buf, ch.bufHead*ch.elementSize) + ch.bufLen++ ch.bufHead++ - if ch.bufHead == ch.bufSize { + if ch.bufHead == ch.bufCap { ch.bufHead = 0 } - return true + memcpy(elemAddr, value, ch.elementSize) } -// pop value from channel buffer if one is available -// returns whether a value was popped or not -// result is stored into value pointer -func (ch *channel) pop(value unsafe.Pointer) bool { - // channel is empty - if ch.bufUsed == 0 { - return false - } - - // compute address of source - addr := unsafe.Add(ch.buf, (ch.elementSize * ch.bufTail)) - - // copy value from buffer - memcpy( - value, - addr, - ch.elementSize, - ) - - // zero buffer element to allow garbage collection of value - memzero( - addr, - ch.elementSize, - ) - - // update buffer state - ch.bufUsed-- - - // move tail up +// Pop a value from the channel buffer and store it in the 'value' pointer, for +// a receive operation. +// This function may only be called when interrupts are disabled and it is known +// there is at least one value available in the buffer. +func (ch *channel) bufferPop(value unsafe.Pointer) { + elemAddr := unsafe.Add(ch.buf, ch.bufTail*ch.elementSize) + ch.bufLen-- ch.bufTail++ - if ch.bufTail == ch.bufSize { + if ch.bufTail == ch.bufCap { ch.bufTail = 0 } - return true + memcpy(value, elemAddr, ch.elementSize) + + // Zero the value to allow the GC to collect it. + memzero(elemAddr, ch.elementSize) } -// try to send a value to a channel, without actually blocking -// returns whether the value was sent -// will panic if channel is closed +// Try to proceed with this send operation without blocking, and return whether +// the send succeeded. Interrupts must be disabled when calling this function. func (ch *channel) trySend(value unsafe.Pointer) bool { - if ch == nil { - // send to nil channel blocks forever - // this is non-blocking, so just say no - return false + // To make sure we send values in the correct order, we can only send + // directly to a receiver when there are no values in the buffer. + + // Do not allow sending on a closed channel. + if ch.closed { + // Note: we cannot currently recover from this panic. + // There's some state in the select statement especially that would be + // corrupted if we allowed recovering from this panic. + runtimePanic("send on closed channel") } - i := interrupt.Disable() - - switch ch.state { - case chanStateEmpty, chanStateBuf: - // try to dump the value directly into the buffer - if ch.push(value) { - ch.state = chanStateBuf - interrupt.Restore(i) + // There is no value in the buffer and we have a receiver available. Copy + // the value directly into the receiver. + if ch.bufLen == 0 { + if receiver := ch.receivers.pop(chanOperationOk); receiver != nil { + memcpy(receiver.task.Ptr, value, ch.elementSize) + scheduleTask(receiver.task) return true } - interrupt.Restore(i) - return false - case chanStateRecv: - // unblock receiver - dst := ch.resumeRX(true) - - // copy value to receiver - memcpy(dst, value, ch.elementSize) - - // change state to empty if there are no more receivers - if ch.blocked == nil { - ch.state = chanStateEmpty - } + } - interrupt.Restore(i) + // If there is space in the buffer (if this is a buffered channel), we can + // store the value in the buffer and continue. + if ch.bufLen < ch.bufCap { + ch.bufferPush(value) return true - case chanStateSend: - // something else is already waiting to send - interrupt.Restore(i) - return false - case chanStateClosed: - interrupt.Restore(i) - runtimePanic("send on closed channel") - default: - interrupt.Restore(i) - runtimePanic("invalid channel state") } - - interrupt.Restore(i) return false } -// try to receive a value from a channel, without really blocking -// returns whether a value was received -// second return is the comma-ok value -func (ch *channel) tryRecv(value unsafe.Pointer) (bool, bool) { +func chanSend(ch *channel, value unsafe.Pointer, op *channelOp) { if ch == nil { - // receive from nil channel blocks forever - // this is non-blocking, so just say no - return false, false + // A nil channel blocks forever. Do not schedule this goroutine again. + deadlock() } - i := interrupt.Disable() - - switch ch.state { - case chanStateBuf, chanStateSend: - // try to pop the value directly from the buffer - if ch.pop(value) { - // unblock next sender if applicable - if ch.blocked != nil { - src := ch.resumeTX() - - // push sender's value into buffer - ch.push(src) - - if ch.blocked == nil { - // last sender unblocked - update state - ch.state = chanStateBuf - } - } - - if ch.bufUsed == 0 { - // channel empty - update state - ch.state = chanStateEmpty - } + mask := interrupt.Disable() - interrupt.Restore(i) - return true, true - } else if ch.blocked != nil { - // unblock next sender if applicable - src := ch.resumeTX() + // See whether we can proceed immediately, and if so, return early. + if ch.trySend(value) { + interrupt.Restore(mask) + return + } - // copy sender's value - memcpy(value, src, ch.elementSize) + // Can't proceed. Add us to the list of senders and wait until we're awoken. + t := task.Current() + t.Data = chanOperationWaiting + op.task = t + op.index = 0 + op.value = value + ch.senders.push(op) + interrupt.Restore(mask) + + // Wait until this goroutine is resumed. + task.Pause() - if ch.blocked == nil { - // last sender unblocked - update state - ch.state = chanStateEmpty - } + // Check whether the sent happened normally (not because the channel was + // closed while sending). + if t.Data == chanOperationClosed { + // Oops, this channel was closed while sending! + runtimePanic("send on closed channel") + } +} - interrupt.Restore(i) - return true, true - } - interrupt.Restore(i) - return false, false - case chanStateRecv, chanStateEmpty: - // something else is already waiting to receive - interrupt.Restore(i) - return false, false - case chanStateClosed: - if ch.pop(value) { - interrupt.Restore(i) - return true, true +// Try to proceed with this receive operation without blocking, and return +// whether the receive operation succeeded. Interrupts must be disabled when +// calling this function. +func (ch *channel) tryRecv(value unsafe.Pointer) (received, ok bool) { + // To make sure we keep the values in the channel in the correct order, we + // first have to read values from the buffer before we can look at the + // senders. + + // If there is a value available in the buffer, we can pull it out and + // proceed immediately. + if ch.bufLen > 0 { + ch.bufferPop(value) + + // Check for the next sender available and push it to the buffer. + if sender := ch.senders.pop(chanOperationOk); sender != nil { + ch.bufferPush(sender.value) + scheduleTask(sender.task) } - // channel closed - nothing to receive + return true, true + } + + if ch.closed { + // Channel is closed, so proceed immediately. memzero(value, ch.elementSize) - interrupt.Restore(i) return true, false - default: - runtimePanic("invalid channel state") } - runtimePanic("unreachable") - return false, false -} - -type chanState uint8 - -const ( - chanStateEmpty chanState = iota // nothing in channel, no senders/receivers - chanStateRecv // nothing in channel, receivers waiting - chanStateSend // senders waiting, buffer full if present - chanStateBuf // buffer not empty, no senders waiting - chanStateClosed // channel closed -) - -func (s chanState) String() string { - switch s { - case chanStateEmpty: - return "empty" - case chanStateRecv: - return "recv" - case chanStateSend: - return "send" - case chanStateBuf: - return "buffered" - case chanStateClosed: - return "closed" - default: - return "invalid" + // If there is a sender, we can proceed with the channel operation + // immediately. + if sender := ch.senders.pop(chanOperationOk); sender != nil { + memcpy(value, sender.value, ch.elementSize) + scheduleTask(sender.task) + return true, true } -} -// chanSelectState is a single channel operation (send/recv) in a select -// statement. The value pointer is either nil (for receives) or points to the -// value to send (for sends). -type chanSelectState struct { - ch *channel - value unsafe.Pointer + return false, false } -// chanSend sends a single value over the channel. -// This operation will block unless a value is immediately available. -// May panic if the channel is closed. -func chanSend(ch *channel, value unsafe.Pointer, blockedlist *channelBlockedList) { - i := interrupt.Disable() - - if ch.trySend(value) { - // value immediately sent - chanDebug(ch) - interrupt.Restore(i) - return - } - +func chanRecv(ch *channel, value unsafe.Pointer, op *channelOp) bool { if ch == nil { // A nil channel blocks forever. Do not schedule this goroutine again. - interrupt.Restore(i) deadlock() } - // wait for receiver - sender := task.Current() - ch.state = chanStateSend - sender.Ptr = value - *blockedlist = channelBlockedList{ - next: ch.blocked, - t: sender, - } - ch.blocked = blockedlist - chanDebug(ch) - interrupt.Restore(i) - task.Pause() - sender.Ptr = nil -} + mask := interrupt.Disable() -// chanRecv receives a single value over a channel. -// It blocks if there is no available value to receive. -// The received value is copied into the value pointer. -// Returns the comma-ok value. -func chanRecv(ch *channel, value unsafe.Pointer, blockedlist *channelBlockedList) bool { - i := interrupt.Disable() - - if rx, ok := ch.tryRecv(value); rx { - // value immediately available - chanDebug(ch) - interrupt.Restore(i) + if received, ok := ch.tryRecv(value); received { + interrupt.Restore(mask) return ok } - if ch == nil { - // A nil channel blocks forever. Do not schedule this goroutine again. - interrupt.Restore(i) - deadlock() - } - - // wait for a value - receiver := task.Current() - ch.state = chanStateRecv - receiver.Ptr, receiver.Data = value, 1 - *blockedlist = channelBlockedList{ - next: ch.blocked, - t: receiver, - } - ch.blocked = blockedlist - chanDebug(ch) - interrupt.Restore(i) + // We can't proceed, so we add ourselves to the list of receivers and wait + // until we're awoken. + t := task.Current() + t.Ptr = value + t.Data = chanOperationWaiting + op.task = t + op.index = 0 + ch.receivers.push(op) + interrupt.Restore(mask) + + // Wait until the goroutine is resumed. task.Pause() - ok := receiver.Data == 1 - receiver.Ptr, receiver.Data = nil, 0 - return ok + + // Return whether the receive happened from a closed channel. + return t.Data != chanOperationClosed } // chanClose closes the given channel. If this channel has a receiver or is @@ -522,128 +333,135 @@ func chanClose(ch *channel) { // Not allowed by the language spec. runtimePanic("close of nil channel") } - i := interrupt.Disable() - switch ch.state { - case chanStateClosed: + + mask := interrupt.Disable() + + if ch.closed { // Not allowed by the language spec. - interrupt.Restore(i) + interrupt.Restore(mask) runtimePanic("close of closed channel") - case chanStateSend: - // This panic should ideally on the sending side, not in this goroutine. - // But when a goroutine tries to send while the channel is being closed, - // that is clearly invalid: the send should have been completed already - // before the close. - interrupt.Restore(i) - runtimePanic("close channel during send") - case chanStateRecv: - // unblock all receivers with the zero value - ch.state = chanStateClosed - for ch.blocked != nil { - ch.resumeRX(false) + } + + // Proceed all receiving operations that are blocked. + for { + receiver := ch.receivers.pop(chanOperationClosed) + if receiver == nil { + // Processed all receivers. + break } - case chanStateEmpty, chanStateBuf: - // Easy case. No available sender or receiver. + + // Zero the value that the receiver is getting. + memzero(receiver.task.Ptr, ch.elementSize) + + // Wake up the receiving goroutine. + scheduleTask(receiver.task) } - ch.state = chanStateClosed - interrupt.Restore(i) - chanDebug(ch) -} -// chanSelect is the runtime implementation of the select statement. This is -// perhaps the most complicated statement in the Go spec. It returns the -// selected index and the 'comma-ok' value. -// -// TODO: do this in a round-robin fashion (as specified in the Go spec) instead -// of picking the first one that can proceed. -func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelBlockedList) (uintptr, bool) { - istate := interrupt.Disable() - - if selected, ok := tryChanSelect(recvbuf, states); selected != ^uintptr(0) { - // one channel was immediately ready - interrupt.Restore(istate) - return selected, ok + // Let all senders panic. + for { + sender := ch.senders.pop(chanOperationClosed) + if sender == nil { + break // processed all senders + } + + // Wake up the sender. + scheduleTask(sender.task) } - // construct blocked operations - for i, v := range states { - if v.ch == nil { - // A nil channel receive will never complete. - // A nil channel send would have panicked during tryChanSelect. - ops[i] = channelBlockedList{} + ch.closed = true + + interrupt.Restore(mask) +} + +// chanSelect implements blocking or non-blocking select operations. +// The 'ops' slice must be set if (and only if) this is a blocking select. +func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelOp) (uintptr, bool) { + mask := interrupt.Disable() + + const selectNoIndex = ^uintptr(0) + selectIndex := selectNoIndex + selectOk := true + + // Iterate over each state, and see if it can proceed. + // TODO: start from a random index. + for i, state := range states { + if state.ch == nil { + // A nil channel blocks forever, so it won't take part of the select + // operation. continue } - ops[i] = channelBlockedList{ - next: v.ch.blocked, - t: task.Current(), - s: &states[i], - allSelectOps: ops, - } - v.ch.blocked = &ops[i] - if v.value == nil { - // recv - switch v.ch.state { - case chanStateEmpty: - v.ch.state = chanStateRecv - case chanStateRecv: - // already in correct state - default: - interrupt.Restore(istate) - runtimePanic("invalid channel state") + if state.value == nil { // chan receive + if received, ok := state.ch.tryRecv(recvbuf); received { + selectIndex = uintptr(i) + selectOk = ok + break } - } else { - // send - switch v.ch.state { - case chanStateEmpty: - v.ch.state = chanStateSend - case chanStateSend: - // already in correct state - case chanStateBuf: - // already in correct state - default: - interrupt.Restore(istate) - runtimePanic("invalid channel state") + } else { // chan send + if state.ch.trySend(state.value) { + selectIndex = uintptr(i) + break } } - chanDebug(v.ch) } - // expose rx buffer + // If this select can immediately proceed, or is a non-blocking select, + // return early. + blocking := len(ops) != 0 + if selectIndex != selectNoIndex || !blocking { + interrupt.Restore(mask) + return selectIndex, selectOk + } + + // The select is blocking and no channel operation can proceed, so things + // become more complicated. + // We add ourselves as a sender/receiver to every channel, and wait for the + // first one to complete. Only one will successfully complete, because + // senders and receivers will check t.Data for the state so that only one + // will be able to "take" this select operation. t := task.Current() t.Ptr = recvbuf - t.Data = 1 + t.Data = chanOperationWaiting + for i, state := range states { + if state.ch == nil { + continue + } + op := &ops[i] + op.task = t + op.index = uintptr(i) + if state.value == nil { // chan receive + state.ch.receivers.push(op) + } else { // chan send + op.value = state.value + state.ch.senders.push(op) + } + } - // wait for one case to fire - interrupt.Restore(istate) + // Now we wait until one of the send/receive operations can proceed. + interrupt.Restore(mask) task.Pause() - // figure out which one fired and return the ok value - return (uintptr(t.Ptr) - uintptr(unsafe.Pointer(&states[0]))) / unsafe.Sizeof(chanSelectState{}), t.Data != 0 -} - -// tryChanSelect is like chanSelect, but it does a non-blocking select operation. -func tryChanSelect(recvbuf unsafe.Pointer, states []chanSelectState) (uintptr, bool) { - istate := interrupt.Disable() + // Resumed, so one channel operation must have progressed. - // See whether we can receive from one of the channels. + // Make sure all channel ops are removed from the senders/receivers + // queue before we return and the memory of them becomes invalid. for i, state := range states { + if state.ch == nil { + continue + } + op := &ops[i] + mask := interrupt.Disable() if state.value == nil { - // A receive operation. - if rx, ok := state.ch.tryRecv(recvbuf); rx { - chanDebug(state.ch) - interrupt.Restore(istate) - return uintptr(i), ok - } + state.ch.receivers.remove(op) } else { - // A send operation: state.value is not nil. - if state.ch.trySend(state.value) { - chanDebug(state.ch) - interrupt.Restore(istate) - return uintptr(i), true - } + state.ch.senders.remove(op) } + interrupt.Restore(mask) } - interrupt.Restore(istate) - return ^uintptr(0), false + // Pull the return values out of t.Data (which contains two bitfields). + selectIndex = uintptr(t.Data) >> 2 + selectOk = t.Data&chanOperationMask != chanOperationClosed + + return selectIndex, selectOk } From 6601c1b1a69838218fe870c88d28216d766d13b4 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 20 Nov 2024 16:57:31 +0100 Subject: [PATCH 289/444] fix: add updated test output to match recent changes Signed-off-by: deadprogram --- compiler/testdata/pragma.ll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/testdata/pragma.ll b/compiler/testdata/pragma.ll index fa8015a90d..31b9fc658a 100644 --- a/compiler/testdata/pragma.ll +++ b/compiler/testdata/pragma.ll @@ -85,10 +85,10 @@ entry: declare void @main.undefinedFunctionNotInSection(ptr) #1 -declare void @main.doesNotEscapeParam(ptr nocapture dereferenceable_or_null(4), ptr nocapture, i32, i32, ptr nocapture dereferenceable_or_null(32), ptr nocapture, ptr) #1 +declare void @main.doesNotEscapeParam(ptr nocapture dereferenceable_or_null(4), ptr nocapture, i32, i32, ptr nocapture dereferenceable_or_null(36), ptr nocapture, ptr) #1 ; Function Attrs: nounwind -define hidden void @main.stillEscapes(ptr dereferenceable_or_null(4) %a, ptr %b.data, i32 %b.len, i32 %b.cap, ptr dereferenceable_or_null(32) %c, ptr %d, ptr %context) unnamed_addr #2 { +define hidden void @main.stillEscapes(ptr dereferenceable_or_null(4) %a, ptr %b.data, i32 %b.len, i32 %b.cap, ptr dereferenceable_or_null(36) %c, ptr %d, ptr %context) unnamed_addr #2 { entry: ret void } From 95671469c75cb56e7bdee9a061bc20114b4c2dc0 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 7 Nov 2024 14:34:11 +0100 Subject: [PATCH 290/444] runtime: use SA_RESTART when registering a signal This really is the only sane way to register a signal. If this flag is not set, many syscalls will return EINTR (and not complete their operation) which will be a massive source of hard-to-debug bugs. --- src/runtime/signal.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/runtime/signal.c b/src/runtime/signal.c index a462518c13..ba4338a6d2 100644 --- a/src/runtime/signal.c +++ b/src/runtime/signal.c @@ -15,6 +15,7 @@ void tinygo_signal_handler(int sig); void tinygo_signal_enable(uint32_t sig) { struct sigaction act = { 0 }; act.sa_handler = &tinygo_signal_handler; + act.sa_flags = SA_RESTART; sigaction(sig, &act, NULL); } From dd1ebbd31bd23474148686bd1d7a7298fe85038d Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 7 Nov 2024 14:38:31 +0100 Subject: [PATCH 291/444] runtime: implement race-free signals using futexes This requires an API introduced in MacOS 11. I think that's fine, since the version before that (MacOS 10.15) is EOL since 2022. Though if needed, we could certainly work around it by using an older and slightly less nice API. --- GNUmakefile | 1 + builder/musl.go | 1 + compileopts/target.go | 2 + lib/macos-minimal-sdk | 2 +- loader/goroot.go | 1 + src/internal/futex/futex.go | 72 +++++++++++ src/internal/futex/futex_darwin.c | 49 +++++++ src/internal/futex/futex_linux.c | 33 +++++ src/runtime/runtime_unix.go | 208 +++++++++++++----------------- src/runtime/signal.c | 58 --------- 10 files changed, 248 insertions(+), 179 deletions(-) create mode 100644 src/internal/futex/futex.go create mode 100644 src/internal/futex/futex_darwin.c create mode 100644 src/internal/futex/futex_linux.c diff --git a/GNUmakefile b/GNUmakefile index 2bf023c627..5f82599e79 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -935,6 +935,7 @@ endif @cp -rp lib/musl/src/malloc build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/mman build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/math build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/misc build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/multibyte build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/signal build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/stdio build/release/tinygo/lib/musl/src diff --git a/builder/musl.go b/builder/musl.go index b156430d4f..3c79c7c43a 100644 --- a/builder/musl.go +++ b/builder/musl.go @@ -128,6 +128,7 @@ var libMusl = Library{ "malloc/mallocng/*.c", "mman/*.c", "math/*.c", + "misc/*.c", "multibyte/*.c", "signal/" + arch + "/*.s", "signal/*.c", diff --git a/compileopts/target.go b/compileopts/target.go index 3dc8af02f6..f60ee30972 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -390,6 +390,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) { "-platform_version", "macos", platformVersion, platformVersion, ) spec.ExtraFiles = append(spec.ExtraFiles, + "src/internal/futex/futex_darwin.c", "src/runtime/os_darwin.c", "src/runtime/runtime_unix.c", "src/runtime/signal.c") @@ -413,6 +414,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) { spec.CFlags = append(spec.CFlags, "-mno-outline-atomics") } spec.ExtraFiles = append(spec.ExtraFiles, + "src/internal/futex/futex_linux.c", "src/runtime/runtime_unix.c", "src/runtime/signal.c") case "windows": diff --git a/lib/macos-minimal-sdk b/lib/macos-minimal-sdk index 4e4113e3b1..9b69407cb5 160000 --- a/lib/macos-minimal-sdk +++ b/lib/macos-minimal-sdk @@ -1 +1 @@ -Subproject commit 4e4113e3b1244b8fdc5e1486577f25e22d63f36e +Subproject commit 9b69407cb59f8ccbb674bb77b358df7befcbb42b diff --git a/loader/goroot.go b/loader/goroot.go index 631cc6e7e6..5442df9b5d 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -243,6 +243,7 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { "internal/binary/": false, "internal/bytealg/": false, "internal/cm/": false, + "internal/futex/": false, "internal/fuzz/": false, "internal/reflectlite/": false, "internal/gclayout": false, diff --git a/src/internal/futex/futex.go b/src/internal/futex/futex.go new file mode 100644 index 0000000000..5ecdd79c28 --- /dev/null +++ b/src/internal/futex/futex.go @@ -0,0 +1,72 @@ +package futex + +// Cross platform futex implementation. +// Futexes are supported on all major operating systems and on WebAssembly. +// +// For more information, see: https://outerproduct.net/futex-dictionary.html + +import ( + "sync/atomic" + "unsafe" +) + +// A futex is a way for userspace to wait with the pointer as the key, and for +// another thread to wake one or all waiting threads keyed on the same pointer. +// +// A futex does not change the underlying value, it only reads it before going +// to sleep (atomically) to prevent lost wake-ups. +type Futex struct { + atomic.Uint32 +} + +// Atomically check for cmp to still be equal to the futex value and if so, go +// to sleep. Return true if we were definitely awoken by a call to Wake or +// WakeAll, and false if we can't be sure of that. +func (f *Futex) Wait(cmp uint32) bool { + tinygo_futex_wait((*uint32)(unsafe.Pointer(&f.Uint32)), cmp) + + // We *could* detect a zero return value from the futex system call which + // would indicate we got awoken by a Wake or WakeAll call. However, this is + // what the manual page has to say: + // + // > Note that a wake-up can also be caused by common futex usage patterns + // > in unrelated code that happened to have previously used the futex + // > word's memory location (e.g., typical futex-based implementations of + // > Pthreads mutexes can cause this under some conditions). Therefore, + // > callers should always conservatively assume that a return value of 0 + // > can mean a spurious wake-up, and use the futex word's value (i.e., the + // > user-space synchronization scheme) to decide whether to continue to + // > block or not. + // + // I'm not sure whether we do anything like pthread does, so to be on the + // safe side we say we don't know whether the wakeup was spurious or not and + // return false. + return false +} + +// Like Wait, but times out after the number of nanoseconds in timeout. +func (f *Futex) WaitUntil(cmp uint32, timeout uint64) { + tinygo_futex_wait_timeout((*uint32)(unsafe.Pointer(&f.Uint32)), cmp, timeout) +} + +// Wake a single waiter. +func (f *Futex) Wake() { + tinygo_futex_wake((*uint32)(unsafe.Pointer(&f.Uint32))) +} + +// Wake all waiters. +func (f *Futex) WakeAll() { + tinygo_futex_wake_all((*uint32)(unsafe.Pointer(&f.Uint32))) +} + +//export tinygo_futex_wait +func tinygo_futex_wait(addr *uint32, cmp uint32) + +//export tinygo_futex_wait_timeout +func tinygo_futex_wait_timeout(addr *uint32, cmp uint32, timeout uint64) + +//export tinygo_futex_wake +func tinygo_futex_wake(addr *uint32) + +//export tinygo_futex_wake_all +func tinygo_futex_wake_all(addr *uint32) diff --git a/src/internal/futex/futex_darwin.c b/src/internal/futex/futex_darwin.c new file mode 100644 index 0000000000..358a87655f --- /dev/null +++ b/src/internal/futex/futex_darwin.c @@ -0,0 +1,49 @@ +//go:build none + +// This file is manually included, to avoid CGo which would cause a circular +// import. + +#include + +// This API isn't documented by Apple, but it is used by LLVM libc++ (so should +// be stable) and has been documented extensively here: +// https://outerproduct.net/futex-dictionary.html + +int __ulock_wait(uint32_t operation, void *addr, uint64_t value, uint32_t timeout_us); +int __ulock_wait2(uint32_t operation, void *addr, uint64_t value, uint64_t timeout_ns, uint64_t value2); +int __ulock_wake(uint32_t operation, void *addr, uint64_t wake_value); + +// Operation code. +#define UL_COMPARE_AND_WAIT 1 + +// Flags to the operation value. +#define ULF_WAKE_ALL 0x00000100 +#define ULF_NO_ERRNO 0x01000000 + +void tinygo_futex_wait(uint32_t *addr, uint32_t cmp) { + __ulock_wait(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO, addr, (uint64_t)cmp, 0); +} + +void tinygo_futex_wait_timeout(uint32_t *addr, uint32_t cmp, uint64_t timeout) { + // Make sure that an accidental use of a zero timeout is not treated as an + // infinite timeout. Return if it's zero since it wouldn't be waiting for + // any significant time anyway. + // Probably unnecessary, but guards against potential bugs. + if (timeout == 0) { + return; + } + + // Note: __ulock_wait2 is available since MacOS 11. + // I think that's fine, since the version before that (MacOS 10.15) is EOL + // since 2022. Though if needed, we could certainly use __ulock_wait instead + // and deal with the smaller timeout value. + __ulock_wait2(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO, addr, (uint64_t)cmp, timeout, 0); +} + +void tinygo_futex_wake(uint32_t *addr) { + __ulock_wake(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO, addr, 0); +} + +void tinygo_futex_wake_all(uint32_t *addr) { + __ulock_wake(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO|ULF_WAKE_ALL, addr, 0); +} diff --git a/src/internal/futex/futex_linux.c b/src/internal/futex/futex_linux.c new file mode 100644 index 0000000000..ffefc97e49 --- /dev/null +++ b/src/internal/futex/futex_linux.c @@ -0,0 +1,33 @@ +//go:build none + +// This file is manually included, to avoid CGo which would cause a circular +// import. + +#include +#include +#include +#include +#include + +#define FUTEX_WAIT 0 +#define FUTEX_WAKE 1 +#define FUTEX_PRIVATE_FLAG 128 + +void tinygo_futex_wait(uint32_t *addr, uint32_t cmp) { + syscall(SYS_futex, addr, FUTEX_WAIT|FUTEX_PRIVATE_FLAG, cmp, NULL, NULL, 0); +} + +void tinygo_futex_wait_timeout(uint32_t *addr, uint32_t cmp, uint64_t timeout) { + struct timespec ts = {0}; + ts.tv_sec = timeout / 1000000000; + ts.tv_nsec = timeout % 1000000000; + syscall(SYS_futex, addr, FUTEX_WAIT|FUTEX_PRIVATE_FLAG, cmp, &ts, NULL, 0); +} + +void tinygo_futex_wake(uint32_t *addr) { + syscall(SYS_futex, addr, FUTEX_WAKE|FUTEX_PRIVATE_FLAG, 1, NULL, NULL, 0); +} + +void tinygo_futex_wake_all(uint32_t *addr) { + syscall(SYS_futex, addr, FUTEX_WAKE|FUTEX_PRIVATE_FLAG, INT_MAX, NULL, NULL, 0); +} diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index 3b20330e20..fc577066ec 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -3,6 +3,8 @@ package runtime import ( + "internal/futex" + "internal/task" "math/bits" "sync/atomic" "tinygo" @@ -223,46 +225,30 @@ func nanosecondsToTicks(ns int64) timeUnit { } func sleepTicks(d timeUnit) { - // When there are no signal handlers present, we can simply go to sleep. - if !hasSignals { - // timeUnit is in nanoseconds, so need to convert to microseconds here. - usleep(uint(d) / 1000) - return - } + until := ticks() + d - if GOOS == "darwin" { - // Check for incoming signals. - if checkSignals() { - // Received a signal, so there's probably at least one goroutine - // that's runnable again. - return + for { + // Sleep for the given amount of time. + // If a signal arrived before going to sleep, or during the sleep, the + // sleep will exit early. + signalFutex.WaitUntil(0, uint64(ticksToNanoseconds(d))) + + // Check whether there was a signal before or during the call to + // WaitUntil. + if signalFutex.Swap(0) != 0 { + if checkSignals() && hasScheduler { + // We got a signal, so return to the scheduler. + // (If there is no scheduler, there is no other goroutine that + // might need to run now). + return + } } - // WARNING: there is a race condition here. If a signal arrives between - // checkSignals() and usleep(), the usleep() call will not exit early so - // the signal is delayed until usleep finishes or another signal - // arrives. - // There doesn't appear to be a simple way to fix this on MacOS. - - // timeUnit is in nanoseconds, so need to convert to microseconds here. - result := usleep(uint(d) / 1000) - if result != 0 { - checkSignals() - } - } else { - // Linux (and various other POSIX systems) implement sigtimedwait so we - // can do this in a non-racy way. - tinygo_wfi_mask(activeSignals) - if checkSignals() { - tinygo_wfi_unmask() + // Set duration (in next loop iteration) to the remaining time. + d = until - ticks() + if d <= 0 { return } - signal := tinygo_wfi_sleep(activeSignals, uint64(d)) - if signal >= 0 { - tinygo_signal_handler(signal) - checkSignals() - } - tinygo_wfi_unmask() } } @@ -353,21 +339,21 @@ func growHeap() bool { return true } -func init() { - // Set up a channel to receive signals into. - signalChan = make(chan uint32, 1) -} - -var signalChan chan uint32 - // Indicate whether signals have been registered. var hasSignals bool +// Futex for the signal handler. +// The value is 0 when there are no new signals, or 1 when there are unhandled +// signals and the main thread doesn't know about it yet. +// When a signal arrives, the futex value is changed to 1 and if it was 0 +// before, all waiters are awoken. +// When a wait exits, the value is changed to 0 and if it wasn't 0 before, the +// signals are checked. +var signalFutex futex.Futex + // Mask of signals that have been received. The signal handler atomically ORs // signals into this value. -var receivedSignals uint32 - -var activeSignals uint32 +var receivedSignals atomic.Uint32 //go:linkname signal_enable os/signal.signal_enable func signal_enable(s uint32) { @@ -377,7 +363,6 @@ func signal_enable(s uint32) { runtimePanicAt(returnAddress(0), "unsupported signal number") } hasSignals = true - activeSignals |= 1 << s // It's easier to implement this function in C. tinygo_signal_enable(s) } @@ -389,7 +374,6 @@ func signal_ignore(s uint32) { // receivedSignals into a uint32 array. runtimePanicAt(returnAddress(0), "unsupported signal number") } - activeSignals &^= 1 << s tinygo_signal_ignore(s) } @@ -400,20 +384,13 @@ func signal_disable(s uint32) { // receivedSignals into a uint32 array. runtimePanicAt(returnAddress(0), "unsupported signal number") } - activeSignals &^= 1 << s tinygo_signal_disable(s) } //go:linkname signal_waitUntilIdle os/signal.signalWaitUntilIdle func signal_waitUntilIdle() { - // Make sure all signals are sent on the channel. - for atomic.LoadUint32(&receivedSignals) != 0 { - checkSignals() - Gosched() - } - - // Make sure all signals are processed. - for len(signalChan) != 0 { + // Wait until signal_recv has processed all signals. + for receivedSignals.Load() != 0 { Gosched() } } @@ -431,102 +408,93 @@ func tinygo_signal_disable(s uint32) // //export tinygo_signal_handler func tinygo_signal_handler(s int32) { - // This loop is essentially the atomic equivalent of the following: + // The following loop is equivalent to the following: // - // receivedSignals |= 1 << s + // receivedSignals.Or(uint32(1) << uint32(s)) // - // TODO: use atomic.Uint32.And once we drop support for Go 1.22 instead of - // this loop. + // TODO: use this instead of a loop once we drop support for Go 1.22. for { mask := uint32(1) << uint32(s) - val := atomic.LoadUint32(&receivedSignals) - swapped := atomic.CompareAndSwapUint32(&receivedSignals, val, val|mask) + val := receivedSignals.Load() + swapped := receivedSignals.CompareAndSwap(val, val|mask) if swapped { break } } + + // Notify the main thread that there was a signal. + // This will exit the call to Wait or WaitUntil early. + if signalFutex.Swap(1) == 0 { + // Changed from 0 to 1, so there may have been a waiting goroutine. + // This could be optimized to avoid a syscall when there are no waiting + // goroutines. + signalFutex.WakeAll() + } } +// Task waiting for a signal to arrive, or nil if it is running or there are no +// signals. +var signalRecvWaiter *task.Task + //go:linkname signal_recv os/signal.signal_recv func signal_recv() uint32 { // Function called from os/signal to get the next received signal. - val := <-signalChan - checkSignals() - return val -} - -// Atomically find a signal that previously occured and send it into the -// signalChan channel. Return true if at least one signal was delivered this -// way, false otherwise. -func checkSignals() bool { - gotSignals := false for { - // Extract the lowest numbered signal number from receivedSignals. - val := atomic.LoadUint32(&receivedSignals) + val := receivedSignals.Load() if val == 0 { - // There is no signal ready to be received by the program (common - // case). - return gotSignals + // There are no signals to receive. Sleep until there are. + signalRecvWaiter = task.Current() + task.Pause() + continue } - num := uint32(bits.TrailingZeros32(val)) - // Do a non-blocking send on signalChan. - select { - case signalChan <- num: - // There was room free in the channel, so remove the signal number - // from the receivedSignals mask. - gotSignals = true - default: - // Could not send the signal number on the channel. This means - // there's still a signal pending. In that case, let it be received - // at which point checkSignals is called again to put the next one - // in the channel buffer. - return gotSignals - } + // Extract the lowest numbered signal number from receivedSignals. + num := uint32(bits.TrailingZeros32(val)) // Atomically clear the signal number from receivedSignals. - // TODO: use atomic.Uint32.Or once we drop support for Go 1.22 instead - // of this loop. + // TODO: use atomic.Uint32.And once we drop support for Go 1.22 instead + // of this loop, like so: + // + // receivedSignals.And(^(uint32(1) << num)) + // for { newVal := val &^ (1 << num) - swapped := atomic.CompareAndSwapUint32(&receivedSignals, val, newVal) + swapped := receivedSignals.CompareAndSwap(val, newVal) if swapped { break } - val = atomic.LoadUint32(&receivedSignals) + val = receivedSignals.Load() } + + return num } } -//export tinygo_wfi_mask -func tinygo_wfi_mask(active uint32) - -//export tinygo_wfi_sleep -func tinygo_wfi_sleep(active uint32, timeout uint64) int32 - -//export tinygo_wfi_wait -func tinygo_wfi_wait(active uint32) int32 - -//export tinygo_wfi_unmask -func tinygo_wfi_unmask() +// Reactivate the goroutine waiting for signals, if there are any. +// Return true if it was reactivated (and therefore the scheduler should run +// again), and false otherwise. +func checkSignals() bool { + if receivedSignals.Load() != 0 && signalRecvWaiter != nil { + runqueuePushBack(signalRecvWaiter) + signalRecvWaiter = nil + return true + } + return false +} func waitForEvents() { if hasSignals { - // We could have used pause() here, but that function is impossible to - // use in a race-free way: - // https://www.cipht.net/2023/11/30/perils-of-pause.html - // Therefore we need something better. - // Note: this is unsafe with multithreading, because sigprocmask is only - // defined for single-threaded applictions. - tinygo_wfi_mask(activeSignals) - if checkSignals() { - tinygo_wfi_unmask() - return + // Wait as long as the futex value is 0. + // This can happen either before or during the call to Wait. + // This can be optimized: if the value is nonzero we don't need to do a + // futex wait syscall and can instead immediately call checkSignals. + signalFutex.Wait(0) + + // Check for signals that arrived before or during the call to Wait. + // If there are any signals, the value is 0. + if signalFutex.Swap(0) != 0 { + checkSignals() } - signal := tinygo_wfi_wait(activeSignals) - tinygo_signal_handler(signal) - checkSignals() - tinygo_wfi_unmask() } else { // The program doesn't use signals, so this is a deadlock. runtimePanic("deadlocked: no event source") diff --git a/src/runtime/signal.c b/src/runtime/signal.c index ba4338a6d2..87af43011e 100644 --- a/src/runtime/signal.c +++ b/src/runtime/signal.c @@ -30,61 +30,3 @@ void tinygo_signal_disable(uint32_t sig) { act.sa_handler = SIG_DFL; sigaction(sig, &act, NULL); } - -// Implement waitForEvents and sleep with signals. -// Warning: sigprocmask is not defined in a multithreaded program so will need -// to be replaced with something else once we implement threading on POSIX. - -// Signals active before a call to tinygo_wfi_mask. -static sigset_t active_signals; - -static void tinygo_set_signals(sigset_t *mask, uint32_t signals) { - sigemptyset(mask); - for (int i=0; i<32; i++) { - if ((signals & (1< Date: Thu, 21 Nov 2024 08:16:04 +0100 Subject: [PATCH 292/444] runtime: fix regression introduced by merging conflicting PRs --- src/runtime/runtime_unix.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index fc577066ec..d9cf7e2c0c 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -475,7 +475,7 @@ func signal_recv() uint32 { // again), and false otherwise. func checkSignals() bool { if receivedSignals.Load() != 0 && signalRecvWaiter != nil { - runqueuePushBack(signalRecvWaiter) + scheduleTask(signalRecvWaiter) signalRecvWaiter = nil return true } From ecc6d16f223f78ace8c71850b1a2b5cfce8c324c Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 20 Nov 2024 13:15:21 +0100 Subject: [PATCH 293/444] interp: align created globals Use the alignment from the align attribute of the runtime.alloc call. This is going to be a more accurate alignment, and is typically smaller than the default. --- builder/sizes_test.go | 2 +- interp/interpreter.go | 9 +++++++++ interp/memory.go | 15 +++++++++------ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/builder/sizes_test.go b/builder/sizes_test.go index 650a5fbb59..bda5e07602 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -43,7 +43,7 @@ func TestBinarySize(t *testing.T) { // microcontrollers {"hifive1b", "examples/echo", 4600, 280, 0, 2268}, {"microbit", "examples/serial", 2908, 388, 8, 2272}, - {"wioterminal", "examples/pininterrupt", 6140, 1484, 116, 6832}, + {"wioterminal", "examples/pininterrupt", 6140, 1484, 116, 6824}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the diff --git a/interp/interpreter.go b/interp/interpreter.go index 512d93eb74..20d56c69e0 100644 --- a/interp/interpreter.go +++ b/interp/interpreter.go @@ -287,9 +287,17 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // Get the object layout, if it is available. llvmLayoutType := r.getLLVMTypeFromLayout(operands[2]) + // Get the alignment of the memory to be allocated. + alignment := 0 // use default alignment if unset + alignAttr := inst.llvmInst.GetCallSiteEnumAttribute(0, llvm.AttributeKindID("align")) + if !alignAttr.IsNil() { + alignment = int(alignAttr.GetEnumValue()) + } + // Create the object. alloc := object{ globalName: r.pkgName + "$alloc", + align: alignment, llvmLayoutType: llvmLayoutType, buffer: newRawValue(uint32(size)), size: uint32(size), @@ -646,6 +654,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent globalName: r.pkgName + "$alloca", buffer: newRawValue(uint32(size)), size: uint32(size), + align: inst.llvmInst.Alignment(), } index := len(r.objects) r.objects = append(r.objects, alloca) diff --git a/interp/memory.go b/interp/memory.go index 176228fdc1..3b36a80b31 100644 --- a/interp/memory.go +++ b/interp/memory.go @@ -42,6 +42,7 @@ type object struct { globalName string // name, if not yet created (not guaranteed to be the final name) buffer value // buffer with value as given by interp, nil if external size uint32 // must match buffer.len(), if available + align int // alignment of the object (may be 0 if unknown) constant bool // true if this is a constant global marked uint8 // 0 means unmarked, 1 means external read, 2 means external write } @@ -593,6 +594,12 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val // runtime.alloc. // First allocate a new global for this object. obj := mem.get(v.index()) + alignment := obj.align + if alignment == 0 { + // Unknown alignment, perhaps from a direct call to runtime.alloc in + // the runtime. Use a conservative default instead. + alignment = mem.r.maxAlign + } if obj.llvmType.IsNil() && obj.llvmLayoutType.IsNil() { // Create an initializer without knowing the global type. // This is probably the result of a runtime.alloc call. @@ -603,7 +610,7 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val globalType := initializer.Type() llvmValue = llvm.AddGlobal(mem.r.mod, globalType, obj.globalName) llvmValue.SetInitializer(initializer) - llvmValue.SetAlignment(mem.r.maxAlign) + llvmValue.SetAlignment(alignment) obj.llvmGlobal = llvmValue mem.put(v.index(), obj) } else { @@ -642,11 +649,7 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val return llvm.Value{}, errors.New("interp: allocated value does not match allocated type") } llvmValue.SetInitializer(initializer) - if obj.llvmType.IsNil() { - // The exact type isn't known (only the layout), so use the - // alignment that would normally be expected from runtime.alloc. - llvmValue.SetAlignment(mem.r.maxAlign) - } + llvmValue.SetAlignment(alignment) } // It should be included in r.globals because otherwise markExternal From f75187392d511ae37c857e2c10a3d59136fbab69 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 28 Oct 2024 15:58:46 +0100 Subject: [PATCH 294/444] internal/task: implement PMutex PMutex is a mutex when threading is possible, and a dummy mutex-like object (that doesn't do anything) otherwise. --- src/internal/task/pmutex-cooperative.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/internal/task/pmutex-cooperative.go diff --git a/src/internal/task/pmutex-cooperative.go b/src/internal/task/pmutex-cooperative.go new file mode 100644 index 0000000000..ae2aa4bad8 --- /dev/null +++ b/src/internal/task/pmutex-cooperative.go @@ -0,0 +1,16 @@ +package task + +// PMutex is a real mutex on systems that can be either preemptive or threaded, +// and a dummy lock on other (purely cooperative) systems. +// +// It is mainly useful for short operations that need a lock when threading may +// be involved, but which do not need a lock with a purely cooperative +// scheduler. +type PMutex struct { +} + +func (m *PMutex) Lock() { +} + +func (m *PMutex) Unlock() { +} From 8d048216392c17a13833f621f3c9760dc302ff36 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 18 Nov 2024 09:24:08 +0100 Subject: [PATCH 295/444] runtime: prepare the leaking GC for concurrent operations This uses the task.PMutex parallel-only-mutex type to make the leaking GC parallelism safe. The task.PMutex type is currently a no-op but will become a real mutex once we add true parallelism. --- src/runtime/gc_leaking.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/runtime/gc_leaking.go b/src/runtime/gc_leaking.go index a40602a98a..9e84dc2a53 100644 --- a/src/runtime/gc_leaking.go +++ b/src/runtime/gc_leaking.go @@ -7,6 +7,7 @@ package runtime // may be the only memory allocator possible. import ( + "internal/task" "unsafe" ) @@ -19,6 +20,9 @@ var gcTotalAlloc uint64 // Total number of calls to alloc() var gcMallocs uint64 +// Heap lock for parallel goroutines. No-op when single threaded. +var gcLock task.PMutex + // Total number of objected freed; for leaking collector this stays 0 const gcFrees = 0 @@ -30,6 +34,7 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { // TODO: this can be optimized by not casting between pointers and ints so // much. And by using platform-native data types (e.g. *uint8 for 8-bit // systems). + gcLock.Lock() size = align(size) addr := heapptr gcTotalAlloc += uint64(size) @@ -43,6 +48,8 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { // Failed to make the heap bigger, so we must really be out of memory. runtimePanic("out of memory") } + gcLock.Unlock() + pointer := unsafe.Pointer(addr) zero_new_alloc(pointer, size) return pointer @@ -69,6 +76,8 @@ func free(ptr unsafe.Pointer) { // The returned memory statistics are up to date as of the // call to ReadMemStats. This would not do GC implicitly for you. func ReadMemStats(m *MemStats) { + gcLock.Lock() + m.HeapIdle = 0 m.HeapInuse = gcTotalAlloc m.HeapReleased = 0 // always 0, we don't currently release memory back to the OS. @@ -82,6 +91,8 @@ func ReadMemStats(m *MemStats) { // no free -- current in use heap is the total allocated m.HeapAlloc = gcTotalAlloc m.Alloc = m.HeapAlloc + + gcLock.Unlock() } func GC() { From 79164dae71d5af32d316a5fc1dc5c996f04cc0f1 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 28 Oct 2024 16:02:14 +0100 Subject: [PATCH 296/444] sync: only use a lock in the Map implementation when needed --- src/sync/map.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sync/map.go b/src/sync/map.go index f27450ce5e..cd8a1967d6 100644 --- a/src/sync/map.go +++ b/src/sync/map.go @@ -1,10 +1,12 @@ package sync +import "internal/task" + // This file implements just enough of sync.Map to get packages to compile. It // is no more efficient than a map with a lock. type Map struct { - lock Mutex + lock task.PMutex m map[interface{}]interface{} } From 51504bfd2ec3c295b0bc370befe6bc37ad782c7d Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 28 Oct 2024 16:07:43 +0100 Subject: [PATCH 297/444] sync: make Pool thread-safe Make sure the object is locked when trying to modify it. Binary size seems unaffected when not using threading. --- src/sync/pool.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/sync/pool.go b/src/sync/pool.go index 41f06a65ce..5a29afebda 100644 --- a/src/sync/pool.go +++ b/src/sync/pool.go @@ -1,18 +1,24 @@ package sync +import "internal/task" + // Pool is a very simple implementation of sync.Pool. type Pool struct { + lock task.PMutex New func() interface{} items []interface{} } // Get returns an item in the pool, or the value of calling Pool.New() if there are no items. func (p *Pool) Get() interface{} { + p.lock.Lock() if len(p.items) > 0 { x := p.items[len(p.items)-1] p.items = p.items[:len(p.items)-1] + p.lock.Unlock() return x } + p.lock.Unlock() if p.New == nil { return nil } @@ -21,5 +27,7 @@ func (p *Pool) Get() interface{} { // Put adds a value back into the pool. func (p *Pool) Put(x interface{}) { + p.lock.Lock() p.items = append(p.items, x) + p.lock.Unlock() } From 19736e5be2e95cac57d24f387525ddce29da3020 Mon Sep 17 00:00:00 2001 From: Matt Mets Date: Thu, 21 Nov 2024 23:11:14 +0100 Subject: [PATCH 298/444] Update cmsis-svd library This updates the version of the cmsis-svd library, to include a version that supports rp2350. This is in support of #4452 --- lib/cmsis-svd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cmsis-svd b/lib/cmsis-svd index 40327a4d2d..05a9562ec5 160000 --- a/lib/cmsis-svd +++ b/lib/cmsis-svd @@ -1 +1 @@ -Subproject commit 40327a4d2dff0992682be2872aaa6e096f35d2f4 +Subproject commit 05a9562ec59b87945a8d7177a4b08b7aa2f2fd58 From 7847f4ea8ed1873b53f6b68e05cec94bea1f452f Mon Sep 17 00:00:00 2001 From: Matt Mets Date: Thu, 21 Nov 2024 23:13:06 +0100 Subject: [PATCH 299/444] Fix invalid assembler syntax from gen-device-svd This addresses #4608 --- tools/gen-device-svd/gen-device-svd.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index 0c49986ab9..c547af586f 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -445,6 +445,13 @@ func readSVD(path, sourceURL string) (*Device, error) { return interruptList[i].PeripheralIndex < interruptList[j].PeripheralIndex }) + // Properly format the description, with comments. + description := "" + if text := device.Description; text != "" { + description = "// " + strings.ReplaceAll(text, "\n", "\n// ") + description = regexp.MustCompile(`\s+\n`).ReplaceAllString(description, "\n") + } + // Properly format the license block, with comments. licenseBlock := "" if text := formatText(device.LicenseText); text != "" { @@ -460,7 +467,7 @@ func readSVD(path, sourceURL string) (*Device, error) { DescriptorSource: sourceURL, Name: device.Name, NameLower: nameLower, - Description: strings.TrimSpace(device.Description), + Description: description, LicenseBlock: licenseBlock, } if device.CPU != nil { @@ -902,7 +909,7 @@ func writeGo(outdir string, device *Device, interruptSystem string) error { //go:build {{.pkgName}} && {{.device.Metadata.NameLower}} -// {{.device.Metadata.Description}} +{{.device.Metadata.Description}} // {{.device.Metadata.LicenseBlock}} package {{.pkgName}} @@ -1350,7 +1357,7 @@ func writeAsm(outdir string, device *Device) error { t := template.Must(template.New("go").Parse(`// Automatically generated file. DO NOT EDIT. // Generated by gen-device-svd.go from {{.File}}, see {{.DescriptorSource}} -// {{.Description}} +{{.Description}} // {{.LicenseBlock}} From 1b83d43cfa62f5c11872770fec12e7948e2a2ebb Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 22 Nov 2024 12:51:57 +0100 Subject: [PATCH 300/444] cgo: fix build warnings on Windows ARM Fix the warning, and also remove tinygo_clang_enum_visitor which was unused. See: https://github.com/golang/go/issues/49721 --- cgo/libclang.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/cgo/libclang.go b/cgo/libclang.go index 54ff9f53fb..4da77ff6e7 100644 --- a/cgo/libclang.go +++ b/cgo/libclang.go @@ -65,9 +65,22 @@ unsigned tinygo_clang_Cursor_isAnonymous(GoCXCursor c); unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c); unsigned tinygo_clang_Cursor_isMacroFunctionLike(GoCXCursor c); +// Fix some warnings on Windows ARM. Without the __declspec(dllexport), it gives warnings like this: +// In file included from _cgo_export.c:4: +// cgo-gcc-export-header-prolog:49:34: warning: redeclaration of 'tinygo_clang_globals_visitor' should not add 'dllexport' attribute [-Wdll-attribute-on-redeclaration] +// libclang.go:68:5: note: previous declaration is here +// See: https://github.com/golang/go/issues/49721 +#if defined(_WIN32) +#define CGO_DECL // __declspec(dllexport) +#else +#define CGO_DECL +#endif + +CGO_DECL int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); +CGO_DECL int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); -int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); +CGO_DECL void tinygo_clang_inclusion_visitor(CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data); */ import "C" From 9172cc15d2f0d5bd125925fa56d1b173b31fb552 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 20 Nov 2024 09:00:05 +0100 Subject: [PATCH 301/444] builder: fix cache paths in -size=full output This fixes long paths from the TinyGo cached GOROOT, as can be seen here: code rodata data bss | flash ram | package ------------------------------- | --------------- | ------- 0 5 0 5 | 5 5 | (padding) 148 0 0 5 | 148 5 | (unknown) 76 0 0 0 | 76 0 | /home/ayke/.cache/tinygo/goroot-ce8827882be9dc201bed279a631881177ae124ea064510684a3cf4bb66436e1a/src/device/arm 4 0 0 0 | 4 0 | /home/ayke/.cache/tinygo/goroot-ce8827882be9dc201bed279a631881177ae124ea064510684a3cf4bb66436e1a/src/internal/task They're now attributed to the correct package instead (device/arm and internal/task). --- builder/build.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/build.go b/builder/build.go index 4c49fe13cd..64a1fde61d 100644 --- a/builder/build.go +++ b/builder/build.go @@ -690,7 +690,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe for _, pkg := range lprogram.Sorted() { pkg := pkg for _, filename := range pkg.CFiles { - abspath := filepath.Join(pkg.Dir, filename) + abspath := filepath.Join(pkg.OriginalDir(), filename) job := &compileJob{ description: "compile CGo file " + abspath, run: func(job *compileJob) error { From b76ea2952039efff710acdb11bf2daaef46e4b42 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 22 Nov 2024 12:33:14 +0100 Subject: [PATCH 302/444] builder: work around bug in DWARF paths in Clang See bug: https://github.com/llvm/llvm-project/issues/117317 --- builder/sizes.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/builder/sizes.go b/builder/sizes.go index caa3ca33f4..7863bbae0f 100644 --- a/builder/sizes.go +++ b/builder/sizes.go @@ -12,6 +12,7 @@ import ( "os" "path/filepath" "regexp" + "runtime" "sort" "strings" @@ -194,11 +195,17 @@ func readProgramSizeFromDWARF(data *dwarf.Data, codeOffset, codeAlignment uint64 if !prevLineEntry.EndSequence { // The chunk describes the code from prevLineEntry to // lineEntry. + path := prevLineEntry.File.Name + if runtime.GOOS == "windows" { + // Work around a Clang bug on Windows: + // https://github.com/llvm/llvm-project/issues/117317 + path = strings.ReplaceAll(path, "\\\\", "\\") + } line := addressLine{ Address: prevLineEntry.Address + codeOffset, Length: lineEntry.Address - prevLineEntry.Address, Align: codeAlignment, - File: prevLineEntry.File.Name, + File: path, } if line.Length != 0 { addresses = append(addresses, line) From ca80c52df19855074b8503165acf04bbe64e26bf Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 26 Nov 2024 11:12:07 +0100 Subject: [PATCH 303/444] builder: fix wasi-libc path names on Windows with -size=full Without this fix, wasi-libc is listed as follows: 65 0 0 0 | 65 0 | C:\Users\Ayke\src\tinygo\tinygo\lib\wasi-libc\libc-bottom-half\sources 14 0 0 0 | 14 0 | C:\Users\Ayke\src\tinygo\tinygo\lib\wasi-libc\libc-top-half\musl\src\exit 1398 0 0 0 | 1398 0 | C:\Users\Ayke\src\tinygo\tinygo\lib\wasi-libc\libc-top-half\musl\src\string 1525 0 0 0 | 1525 0 | C:\Users\Ayke\src\tinygo\tinygo\lib\wasi-libc\libc-top-half\sources With this fix, it's identified as the wasi-libc C library: 3002 0 0 0 | 3002 0 | C wasi-libc --- builder/sizes.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/builder/sizes.go b/builder/sizes.go index 7863bbae0f..3f6cc4518c 100644 --- a/builder/sizes.go +++ b/builder/sizes.go @@ -200,6 +200,11 @@ func readProgramSizeFromDWARF(data *dwarf.Data, codeOffset, codeAlignment uint64 // Work around a Clang bug on Windows: // https://github.com/llvm/llvm-project/issues/117317 path = strings.ReplaceAll(path, "\\\\", "\\") + + // wasi-libc likes to use forward slashes, but we + // canonicalize everything to use backwards slashes as + // is common on Windows. + path = strings.ReplaceAll(path, "/", "\\") } line := addressLine{ Address: prevLineEntry.Address + codeOffset, From ee6fcd76f461543c67849a3b5b6cd5f8d16914d8 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 20 Nov 2024 09:04:29 +0100 Subject: [PATCH 304/444] builder: add testing for -size=full This helps to make sure this feature continues to work and we won't accidentally introduce regressions. --- builder/build.go | 16 +++++--- builder/sizes_test.go | 88 +++++++++++++++++++++++++++++++++---------- 2 files changed, 79 insertions(+), 25 deletions(-) diff --git a/builder/build.go b/builder/build.go index 64a1fde61d..57b67ed455 100644 --- a/builder/build.go +++ b/builder/build.go @@ -61,6 +61,10 @@ type BuildResult struct { // correctly printing test results: the import path isn't always the same as // the path listed on the command line. ImportPath string + + // Map from path to package name. It is needed to attribute binary size to + // the right Go package. + PackagePathMap map[string]string } // packageAction is the struct that is serialized to JSON and hashed, to work as @@ -242,6 +246,12 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe return result, err } + // Store which filesystem paths map to which package name. + result.PackagePathMap = make(map[string]string, len(lprogram.Packages)) + for _, pkg := range lprogram.Sorted() { + result.PackagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path() + } + // Create the *ssa.Program. This does not yet build the entire SSA of the // program so it's pretty fast and doesn't need to be parallelized. program := lprogram.LoadSSA() @@ -916,11 +926,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe // Print code size if requested. if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" { - packagePathMap := make(map[string]string, len(lprogram.Packages)) - for _, pkg := range lprogram.Sorted() { - packagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path() - } - sizes, err := loadProgramSize(result.Executable, packagePathMap) + sizes, err := loadProgramSize(result.Executable, result.PackagePathMap) if err != nil { return err } diff --git a/builder/sizes_test.go b/builder/sizes_test.go index bda5e07602..a1be28f274 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -1,6 +1,7 @@ package builder import ( + "regexp" "runtime" "testing" "time" @@ -55,26 +56,7 @@ func TestBinarySize(t *testing.T) { t.Parallel() // Build the binary. - options := compileopts.Options{ - Target: tc.target, - Opt: "z", - Semaphore: sema, - InterpTimeout: 60 * time.Second, - Debug: true, - VerifyIR: true, - } - target, err := compileopts.LoadTarget(&options) - if err != nil { - t.Fatal("could not load target:", err) - } - config := &compileopts.Config{ - Options: &options, - Target: target, - } - result, err := Build(tc.path, "", t.TempDir(), config) - if err != nil { - t.Fatal("could not build:", err) - } + result := buildBinary(t, tc.target, tc.path) // Check whether the size of the binary matches the expected size. sizes, err := loadProgramSize(result.Executable, nil) @@ -90,3 +72,69 @@ func TestBinarySize(t *testing.T) { }) } } + +// Check that the -size=full flag attributes binary size to the correct package +// without filesystem paths and things like that. +func TestSizeFull(t *testing.T) { + tests := []string{ + "microbit", + "wasip1", + } + + libMatch := regexp.MustCompile(`^C [a-z -]+$`) // example: "C interrupt vector" + pkgMatch := regexp.MustCompile(`^[a-z/]+$`) // example: "internal/task" + + for _, target := range tests { + target := target + t.Run(target, func(t *testing.T) { + t.Parallel() + + // Build the binary. + result := buildBinary(t, target, "examples/serial") + + // Check whether the binary doesn't contain any unexpected package + // names. + sizes, err := loadProgramSize(result.Executable, result.PackagePathMap) + if err != nil { + t.Fatal("could not read program size:", err) + } + for _, pkg := range sizes.sortedPackageNames() { + if pkg == "(padding)" || pkg == "(unknown)" { + // TODO: correctly attribute all unknown binary size. + continue + } + if libMatch.MatchString(pkg) { + continue + } + if pkgMatch.MatchString(pkg) { + continue + } + t.Error("unexpected package name in size output:", pkg) + } + }) + } +} + +func buildBinary(t *testing.T, targetString, pkgName string) BuildResult { + options := compileopts.Options{ + Target: targetString, + Opt: "z", + Semaphore: sema, + InterpTimeout: 60 * time.Second, + Debug: true, + VerifyIR: true, + } + target, err := compileopts.LoadTarget(&options) + if err != nil { + t.Fatal("could not load target:", err) + } + config := &compileopts.Config{ + Options: &options, + Target: target, + } + result, err := Build(pkgName, "", t.TempDir(), config) + if err != nil { + t.Fatal("could not build:", err) + } + return result +} From 392a709b77c82bfa98ce669ae6f781cf93835967 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 27 Nov 2024 11:15:15 +0100 Subject: [PATCH 305/444] runtime: use uint32 for the channel state and select index This uses uint32 instead of uint64. The reason for this is that uint64 atomic operations aren't universally available (especially on 32-bit architectures). We could also use uintptr, but that seems needlessly complicated: it's unlikely real-world programs will use more than a billion select states (2^30). --- compiler/channel.go | 16 ++++++++++++++++ src/internal/task/task.go | 12 ++++++++++++ src/runtime/chan.go | 34 +++++++++++++++++----------------- 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/compiler/channel.go b/compiler/channel.go index 7e867c2789..0ff2ab7f32 100644 --- a/compiler/channel.go +++ b/compiler/channel.go @@ -4,7 +4,9 @@ package compiler // or pseudo-operations that are lowered during goroutine lowering. import ( + "fmt" "go/types" + "math" "github.com/tinygo-org/tinygo/compiler/llvmutil" "golang.org/x/tools/go/ssa" @@ -124,6 +126,20 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value { } } + const maxSelectStates = math.MaxUint32 >> 2 + if len(expr.States) > maxSelectStates { + // The runtime code assumes that the number of state must fit in 30 bits + // (so the select index can be stored in a uint32 with two bits reserved + // for other purposes). It seems unlikely that a real program would have + // that many states, but we check for this case anyway to be sure. + // We use a uint32 (and not a uintptr or uint64) to avoid 64-bit atomic + // operations which aren't available everywhere. + b.addError(expr.Pos(), fmt.Sprintf("too many select states: got %d but the maximum supported number is %d", len(expr.States), maxSelectStates)) + + // Continue as usual (we'll generate broken code but the error will + // prevent the compilation to complete). + } + // This code create a (stack-allocated) slice containing all the select // cases and then calls runtime.chanSelect to perform the actual select // statement. diff --git a/src/internal/task/task.go b/src/internal/task/task.go index bce65b582e..587c675267 100644 --- a/src/internal/task/task.go +++ b/src/internal/task/task.go @@ -26,6 +26,18 @@ type Task struct { DeferFrame unsafe.Pointer } +// DataUint32 returns the Data field as a uint32. The value is only valid after +// setting it through SetDataUint32. +func (t *Task) DataUint32() uint32 { + return *(*uint32)(unsafe.Pointer(&t.Data)) +} + +// SetDataUint32 updates the uint32 portion of the Data field (which could be +// the first 4 or last 4 bytes depending on the architecture endianness). +func (t *Task) SetDataUint32(val uint32) { + *(*uint32)(unsafe.Pointer(&t.Data)) = val +} + // getGoroutineStackSize is a compiler intrinsic that returns the stack size for // the given function and falls back to the default stack size. It is replaced // with a load from a special section just before codegen. diff --git a/src/runtime/chan.go b/src/runtime/chan.go index c62685700e..0d0cbf06aa 100644 --- a/src/runtime/chan.go +++ b/src/runtime/chan.go @@ -83,7 +83,7 @@ func (q *chanQueue) push(node *channelOp) { // waiting (for example, when they're part of a select operation) will be // skipped. // This function must be called with interrupts disabled. -func (q *chanQueue) pop(chanOp uint64) *channelOp { +func (q *chanQueue) pop(chanOp uint32) *channelOp { for { if q.first == nil { return nil @@ -96,11 +96,11 @@ func (q *chanQueue) pop(chanOp uint64) *channelOp { // The new value for the 'data' field will be a combination of the // channel operation and the select index. (The select index is 0 for // non-select channel operations). - newDataValue := chanOp | uint64(popped.index<<2) + newDataValue := chanOp | popped.index<<2 // Try to be the first to proceed with this goroutine. - if popped.task.Data == chanOperationWaiting { - popped.task.Data = newDataValue + if popped.task.DataUint32() == chanOperationWaiting { + popped.task.SetDataUint32(newDataValue) return popped } } @@ -123,7 +123,7 @@ func (q *chanQueue) remove(remove *channelOp) { type channelOp struct { next *channelOp task *task.Task - index uintptr // select index, 0 for non-select operation + index uint32 // select index, 0 for non-select operation value unsafe.Pointer // if this is a sender, this is the value to send } @@ -239,7 +239,7 @@ func chanSend(ch *channel, value unsafe.Pointer, op *channelOp) { // Can't proceed. Add us to the list of senders and wait until we're awoken. t := task.Current() - t.Data = chanOperationWaiting + t.SetDataUint32(chanOperationWaiting) op.task = t op.index = 0 op.value = value @@ -251,7 +251,7 @@ func chanSend(ch *channel, value unsafe.Pointer, op *channelOp) { // Check whether the sent happened normally (not because the channel was // closed while sending). - if t.Data == chanOperationClosed { + if t.DataUint32() == chanOperationClosed { // Oops, this channel was closed while sending! runtimePanic("send on closed channel") } @@ -313,7 +313,7 @@ func chanRecv(ch *channel, value unsafe.Pointer, op *channelOp) bool { // until we're awoken. t := task.Current() t.Ptr = value - t.Data = chanOperationWaiting + t.SetDataUint32(chanOperationWaiting) op.task = t op.index = 0 ch.receivers.push(op) @@ -323,7 +323,7 @@ func chanRecv(ch *channel, value unsafe.Pointer, op *channelOp) bool { task.Pause() // Return whether the receive happened from a closed channel. - return t.Data != chanOperationClosed + return t.DataUint32() != chanOperationClosed } // chanClose closes the given channel. If this channel has a receiver or is @@ -375,10 +375,10 @@ func chanClose(ch *channel) { // chanSelect implements blocking or non-blocking select operations. // The 'ops' slice must be set if (and only if) this is a blocking select. -func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelOp) (uintptr, bool) { +func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelOp) (uint32, bool) { mask := interrupt.Disable() - const selectNoIndex = ^uintptr(0) + const selectNoIndex = ^uint32(0) selectIndex := selectNoIndex selectOk := true @@ -393,13 +393,13 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelO if state.value == nil { // chan receive if received, ok := state.ch.tryRecv(recvbuf); received { - selectIndex = uintptr(i) + selectIndex = uint32(i) selectOk = ok break } } else { // chan send if state.ch.trySend(state.value) { - selectIndex = uintptr(i) + selectIndex = uint32(i) break } } @@ -421,14 +421,14 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelO // will be able to "take" this select operation. t := task.Current() t.Ptr = recvbuf - t.Data = chanOperationWaiting + t.SetDataUint32(chanOperationWaiting) for i, state := range states { if state.ch == nil { continue } op := &ops[i] op.task = t - op.index = uintptr(i) + op.index = uint32(i) if state.value == nil { // chan receive state.ch.receivers.push(op) } else { // chan send @@ -460,8 +460,8 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelO } // Pull the return values out of t.Data (which contains two bitfields). - selectIndex = uintptr(t.Data) >> 2 - selectOk = t.Data&chanOperationMask != chanOperationClosed + selectIndex = t.DataUint32() >> 2 + selectOk = t.DataUint32()&chanOperationMask != chanOperationClosed return selectIndex, selectOk } From 65b085a5d5abbbfc3c9ed6fa3461c5032f45e281 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 27 Nov 2024 18:44:30 +0100 Subject: [PATCH 306/444] examples: use default UART settings in echo example Signed-off-by: deadprogram --- src/examples/echo/echo.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/examples/echo/echo.go b/src/examples/echo/echo.go index be129dd093..a917b809ff 100644 --- a/src/examples/echo/echo.go +++ b/src/examples/echo/echo.go @@ -7,15 +7,13 @@ import ( "time" ) -// change these to test a different UART or pins if available var ( uart = machine.Serial - tx = machine.UART_TX_PIN - rx = machine.UART_RX_PIN ) func main() { - uart.Configure(machine.UARTConfig{TX: tx, RX: rx}) + // use default settings for UART + uart.Configure(machine.UARTConfig{}) uart.Write([]byte("Echo console enabled. Type something then press enter:\r\n")) input := make([]byte, 64) From aed555d858c163fd96c12db3d5ec376cc5261e73 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 21 Nov 2024 09:20:14 +0100 Subject: [PATCH 307/444] runtime: implement Goexit This is needed for full support for the testing package --- compiler/testdata/defer-cortex-m-qemu.ll | 2 +- src/runtime/panic.go | 36 +++++++++++++++++++----- src/runtime/scheduler.go | 7 +---- testdata/recover.go | 19 +++++++++++++ testdata/recover.txt | 3 ++ 5 files changed, 53 insertions(+), 14 deletions(-) diff --git a/compiler/testdata/defer-cortex-m-qemu.ll b/compiler/testdata/defer-cortex-m-qemu.ll index 52a3bfbabf..cdcfd0ea44 100644 --- a/compiler/testdata/defer-cortex-m-qemu.ll +++ b/compiler/testdata/defer-cortex-m-qemu.ll @@ -3,7 +3,7 @@ source_filename = "defer.go" target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64" target triple = "thumbv7m-unknown-unknown-eabi" -%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i1, %runtime._interface } +%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i8, %runtime._interface } %runtime._interface = type { ptr, ptr } %runtime._defer = type { i32, ptr } diff --git a/src/runtime/panic.go b/src/runtime/panic.go index d429171ec6..ec33a4469c 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -38,12 +38,24 @@ type deferFrame struct { JumpPC unsafe.Pointer // pc to return to ExtraRegs [deferExtraRegs]unsafe.Pointer // extra registers (depending on the architecture) Previous *deferFrame // previous recover buffer pointer - Panicking bool // true iff this defer frame is panicking + Panicking panicState // not panicking, panicking, or in Goexit PanicValue interface{} // panic value, might be nil for panic(nil) for example } +type panicState uint8 + +const ( + panicFalse panicState = iota + panicTrue + panicGoexit +) + // Builtin function panic(msg), used as a compiler intrinsic. func _panic(message interface{}) { + panicOrGoexit(message, panicTrue) +} + +func panicOrGoexit(message interface{}, panicking panicState) { if panicStrategy() == tinygo.PanicStrategyTrap { trap() } @@ -53,11 +65,16 @@ func _panic(message interface{}) { frame := (*deferFrame)(task.Current().DeferFrame) if frame != nil { frame.PanicValue = message - frame.Panicking = true + frame.Panicking = panicking tinygo_longjmp(frame) // unreachable } } + if panicking == panicGoexit { + // Call to Goexit() instead of a panic. + // Exit the goroutine instead of printing a panic message. + deadlock() + } printstring("panic: ") printitf(message) printnl() @@ -103,7 +120,7 @@ func setupDeferFrame(frame *deferFrame, jumpSP unsafe.Pointer) { currentTask := task.Current() frame.Previous = (*deferFrame)(currentTask.DeferFrame) frame.JumpSP = jumpSP - frame.Panicking = false + frame.Panicking = panicFalse currentTask.DeferFrame = unsafe.Pointer(frame) } @@ -115,10 +132,10 @@ func setupDeferFrame(frame *deferFrame, jumpSP unsafe.Pointer) { //go:nobounds func destroyDeferFrame(frame *deferFrame) { task.Current().DeferFrame = unsafe.Pointer(frame.Previous) - if frame.Panicking { + if frame.Panicking != panicFalse { // We're still panicking! // Re-raise the panic now. - _panic(frame.PanicValue) + panicOrGoexit(frame.PanicValue, frame.Panicking) } } @@ -143,10 +160,15 @@ func _recover(useParentFrame bool) interface{} { // already), but instead from the previous frame. frame = frame.Previous } - if frame != nil && frame.Panicking { + if frame != nil && frame.Panicking != panicFalse { + if frame.Panicking == panicGoexit { + // Special value that indicates we're exiting the goroutine using + // Goexit(). Therefore, make this recover call a no-op. + return nil + } // Only the first call to recover returns the panic value. It also stops // the panicking sequence, hence setting panicking to false. - frame.Panicking = false + frame.Panicking = panicFalse return frame.PanicValue } // Not panicking, so return a nil interface. diff --git a/src/runtime/scheduler.go b/src/runtime/scheduler.go index d84ccf3e0e..727c7f5f2c 100644 --- a/src/runtime/scheduler.go +++ b/src/runtime/scheduler.go @@ -28,11 +28,6 @@ func scheduleLogChan(msg string, ch *channel, t *task.Task) { } // Goexit terminates the currently running goroutine. No other goroutines are affected. -// -// Unlike the main Go implementation, no deferred calls will be run. -// -//go:inline func Goexit() { - // TODO: run deferred functions - deadlock() + panicOrGoexit(nil, panicGoexit) } diff --git a/testdata/recover.go b/testdata/recover.go index c7c02c94a4..260bf91bdd 100644 --- a/testdata/recover.go +++ b/testdata/recover.go @@ -1,5 +1,10 @@ package main +import ( + "runtime" + "time" +) + func main() { println("# simple recover") recoverSimple() @@ -22,6 +27,9 @@ func main() { println("\n# defer panic") deferPanic() + + println("\n# runtime.Goexit") + runtimeGoexit() } func recoverSimple() { @@ -104,6 +112,17 @@ func deferPanic() { println("defer panic") } +func runtimeGoexit() { + go func() { + defer func() { + println("Goexit deferred function, recover is nil:", recover() == nil) + }() + + runtime.Goexit() + }() + time.Sleep(time.Millisecond) +} + func printitf(msg string, itf interface{}) { switch itf := itf.(type) { case string: diff --git a/testdata/recover.txt b/testdata/recover.txt index 3575058812..87e4ba5d17 100644 --- a/testdata/recover.txt +++ b/testdata/recover.txt @@ -27,3 +27,6 @@ recovered: panic 2 # defer panic defer panic recovered from deferred call: deferred panic + +# runtime.Goexit +Goexit deferred function, recover is nil: true From 09a22ac4b48b5a008cedc527c70cf41117a6b338 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 30 Nov 2024 09:24:22 +0100 Subject: [PATCH 308/444] runtime: make signals parallelism-safe --- src/runtime/runtime_unix.go | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index d9cf7e2c0c..e6f81778de 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -362,7 +362,12 @@ func signal_enable(s uint32) { // receivedSignals into a uint32 array. runtimePanicAt(returnAddress(0), "unsupported signal number") } + + // This is intentonally a non-atomic store. This is safe, since hasSignals + // is only used in waitForEvents which is only called when there's a + // scheduler (and therefore there is no parallelism). hasSignals = true + // It's easier to implement this function in C. tinygo_signal_enable(s) } @@ -391,6 +396,9 @@ func signal_disable(s uint32) { func signal_waitUntilIdle() { // Wait until signal_recv has processed all signals. for receivedSignals.Load() != 0 { + // TODO: this becomes a busy loop when using threads. + // We might want to pause until signal_recv has no more incoming signals + // to process. Gosched() } } @@ -434,7 +442,7 @@ func tinygo_signal_handler(s int32) { // Task waiting for a signal to arrive, or nil if it is running or there are no // signals. -var signalRecvWaiter *task.Task +var signalRecvWaiter atomic.Pointer[task.Task] //go:linkname signal_recv os/signal.signal_recv func signal_recv() uint32 { @@ -443,7 +451,10 @@ func signal_recv() uint32 { val := receivedSignals.Load() if val == 0 { // There are no signals to receive. Sleep until there are. - signalRecvWaiter = task.Current() + if signalRecvWaiter.Swap(task.Current()) != nil { + // We expect only a single goroutine to call signal_recv. + runtimePanic("signal_recv called concurrently") + } task.Pause() continue } @@ -474,10 +485,11 @@ func signal_recv() uint32 { // Return true if it was reactivated (and therefore the scheduler should run // again), and false otherwise. func checkSignals() bool { - if receivedSignals.Load() != 0 && signalRecvWaiter != nil { - scheduleTask(signalRecvWaiter) - signalRecvWaiter = nil - return true + if receivedSignals.Load() != 0 { + if waiter := signalRecvWaiter.Swap(nil); waiter != nil { + scheduleTask(waiter) + return true + } } return false } From 26c36d0a2e11ba6b31300881b8dd729b1ad8cfe6 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 7 Nov 2024 13:38:28 +0100 Subject: [PATCH 309/444] runtime: lock output in print/println This ensures that calls to print/println happening in different threads are not interleaved. It's a task.PMutex, so this should only change things when threading is used. This matches the Go compiler, which does the same thing: https://godbolt.org/z/na5KzE7en The locks are not recursive, which means that we need to be careful to not call `print` or `println` inside a runtime.print* implementation, inside putchar (recursively), and inside signal handlers. Making them recursive might be useful to do in the future, but it's not really necessary. --- compiler/compiler.go | 2 + compiler/testdata/defer-cortex-m-qemu.ll | 10 +++ .../testdata/goroutine-cortex-m-qemu-tasks.ll | 6 ++ compiler/testdata/goroutine-wasm-asyncify.ll | 6 ++ src/runtime/print.go | 78 +++++++++++++------ testdata/print.go | 6 ++ testdata/print.txt | 6 ++ 7 files changed, 90 insertions(+), 24 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index 8b5f0d1cb2..28ec312dd0 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1686,6 +1686,7 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c b.createRuntimeInvoke("_panic", argValues, "") return llvm.Value{}, nil case "print", "println": + b.createRuntimeCall("printlock", nil, "") for i, value := range argValues { if i >= 1 && callName == "println" { b.createRuntimeCall("printspace", nil, "") @@ -1746,6 +1747,7 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c if callName == "println" { b.createRuntimeCall("printnl", nil, "") } + b.createRuntimeCall("printunlock", nil, "") return llvm.Value{}, nil // print() or println() returns void case "real": cplx := argValues[0] diff --git a/compiler/testdata/defer-cortex-m-qemu.ll b/compiler/testdata/defer-cortex-m-qemu.ll index cdcfd0ea44..f1fbad7f81 100644 --- a/compiler/testdata/defer-cortex-m-qemu.ll +++ b/compiler/testdata/defer-cortex-m-qemu.ll @@ -122,12 +122,18 @@ declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(24), ptr) #2 ; Function Attrs: nounwind define internal void @"main.deferSimple$1"(ptr %context) unnamed_addr #1 { entry: + call void @runtime.printlock(ptr undef) #4 call void @runtime.printint32(i32 3, ptr undef) #4 + call void @runtime.printunlock(ptr undef) #4 ret void } +declare void @runtime.printlock(ptr) #2 + declare void @runtime.printint32(i32, ptr) #2 +declare void @runtime.printunlock(ptr) #2 + ; Function Attrs: nounwind define hidden void @main.deferMultiple(ptr %context) unnamed_addr #1 { entry: @@ -250,14 +256,18 @@ rundefers.end7: ; preds = %rundefers.loophead1 ; Function Attrs: nounwind define internal void @"main.deferMultiple$1"(ptr %context) unnamed_addr #1 { entry: + call void @runtime.printlock(ptr undef) #4 call void @runtime.printint32(i32 3, ptr undef) #4 + call void @runtime.printunlock(ptr undef) #4 ret void } ; Function Attrs: nounwind define internal void @"main.deferMultiple$2"(ptr %context) unnamed_addr #1 { entry: + call void @runtime.printlock(ptr undef) #4 call void @runtime.printint32(i32 5, ptr undef) #4 + call void @runtime.printunlock(ptr undef) #4 ret void } diff --git a/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll b/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll index a57bb20f36..819f01adbe 100644 --- a/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll +++ b/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll @@ -70,7 +70,9 @@ entry: %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr undef) #9 call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #9 %2 = load i32, ptr %n, align 4 + call void @runtime.printlock(ptr undef) #9 call void @runtime.printint32(i32 %2, ptr undef) #9 + call void @runtime.printunlock(ptr undef) #9 ret void } @@ -91,8 +93,12 @@ entry: ret void } +declare void @runtime.printlock(ptr) #2 + declare void @runtime.printint32(i32, ptr) #2 +declare void @runtime.printunlock(ptr) #2 + ; Function Attrs: nounwind define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #1 { entry: diff --git a/compiler/testdata/goroutine-wasm-asyncify.ll b/compiler/testdata/goroutine-wasm-asyncify.ll index c4af760371..87c7381f25 100644 --- a/compiler/testdata/goroutine-wasm-asyncify.ll +++ b/compiler/testdata/goroutine-wasm-asyncify.ll @@ -76,7 +76,9 @@ entry: store ptr %n, ptr %1, align 4 call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #9 %2 = load i32, ptr %n, align 4 + call void @runtime.printlock(ptr undef) #9 call void @runtime.printint32(i32 %2, ptr undef) #9 + call void @runtime.printunlock(ptr undef) #9 ret void } @@ -98,8 +100,12 @@ entry: unreachable } +declare void @runtime.printlock(ptr) #1 + declare void @runtime.printint32(i32, ptr) #1 +declare void @runtime.printunlock(ptr) #1 + ; Function Attrs: nounwind define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #2 { entry: diff --git a/src/runtime/print.go b/src/runtime/print.go index ef9117ff3e..a4de460253 100644 --- a/src/runtime/print.go +++ b/src/runtime/print.go @@ -1,6 +1,7 @@ package runtime import ( + "internal/task" "unsafe" ) @@ -8,6 +9,18 @@ type stringer interface { String() string } +// Lock to make sure print calls do not interleave. +// This is a no-op lock on systems that do not have parallelism. +var printLock task.PMutex + +func printlock() { + printLock.Lock() +} + +func printunlock() { + printLock.Unlock() +} + //go:nobounds func printstring(s string) { for i := 0; i < len(s); i++ { @@ -293,67 +306,84 @@ func printnl() { func printitf(msg interface{}) { switch msg := msg.(type) { case bool: - print(msg) + printbool(msg) case int: - print(msg) + switch unsafe.Sizeof(msg) { + case 8: + printint64(int64(msg)) + case 4: + printint32(int32(msg)) + } case int8: - print(msg) + printint8(msg) case int16: - print(msg) + printint16(msg) case int32: - print(msg) + printint32(msg) case int64: - print(msg) + printint64(msg) case uint: - print(msg) + switch unsafe.Sizeof(msg) { + case 8: + printuint64(uint64(msg)) + case 4: + printuint32(uint32(msg)) + } case uint8: - print(msg) + printuint8(msg) case uint16: - print(msg) + printuint16(msg) case uint32: - print(msg) + printuint32(msg) case uint64: - print(msg) + printuint64(msg) case uintptr: - print(msg) + printuintptr(msg) case float32: - print(msg) + printfloat32(msg) case float64: - print(msg) + printfloat64(msg) case complex64: - print(msg) + printcomplex64(msg) case complex128: - print(msg) + printcomplex128(msg) case string: - print(msg) + printstring(msg) case error: - print(msg.Error()) + printstring(msg.Error()) case stringer: - print(msg.String()) + printstring(msg.String()) default: // cast to underlying type itf := *(*_interface)(unsafe.Pointer(&msg)) putchar('(') printuintptr(uintptr(itf.typecode)) putchar(':') - print(itf.value) + printptr(uintptr(itf.value)) putchar(')') } } func printmap(m *hashmap) { - print("map[") + printstring("map[") if m == nil { - print("nil") + printstring("nil") } else { - print(uint(m.count)) + switch unsafe.Sizeof(m.count) { + case 8: + printuint64(uint64(m.count)) + case 4: + printuint32(uint32(m.count)) + case 2: + printuint16(uint16(m.count)) + } } putchar(']') } func printptr(ptr uintptr) { if ptr == 0 { - print("nil") + printstring("nil") return } putchar('0') diff --git a/testdata/print.go b/testdata/print.go index 7f7f843c4c..5156ad58e0 100644 --- a/testdata/print.go +++ b/testdata/print.go @@ -37,6 +37,12 @@ func main() { // print interface println(interface{}(nil)) + println(interface{}(true)) + println(interface{}("foobar")) + println(interface{}(int64(-3))) + println(interface{}(uint64(3))) + println(interface{}(int(-3))) + println(interface{}(uint(3))) // print map println(map[string]int{"three": 3, "five": 5}) diff --git a/testdata/print.txt b/testdata/print.txt index 116de945df..3a88cf91e0 100644 --- a/testdata/print.txt +++ b/testdata/print.txt @@ -19,6 +19,12 @@ a b c +3.140000e+000 (+5.000000e+000+1.234500e+000i) (0:nil) +true +foobar +-3 +3 +-3 +3 map[2] true false [0/0]nil From 3b8062170c51524fc450cbf53f8f4eb817347f17 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 1 Dec 2024 09:32:31 +0100 Subject: [PATCH 310/444] mips: fix a bug when scanning the stack Previously the assembler was reordering this code: jal tinygo_scanstack move $a0, $sp Into this: jal tinygo_scanstack nop move $a0, $sp So it was "helpfully" inserting a branch delay slot, even though this was already being taken care of. Somehow this didn't break, but it does break in the WIP threading branch (https://github.com/tinygo-org/tinygo/pull/4559) where this bug leads to a crash. --- src/internal/task/task_stack_mipsx.S | 4 ++++ src/runtime/asm_mipsx.S | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/internal/task/task_stack_mipsx.S b/src/internal/task/task_stack_mipsx.S index 903a847c74..018c63d935 100644 --- a/src/internal/task/task_stack_mipsx.S +++ b/src/internal/task/task_stack_mipsx.S @@ -1,3 +1,7 @@ +// Do not reorder instructions to insert a branch delay slot. +// We know what we're doing, and will manually fill the branch delay slot. +.set noreorder + .section .text.tinygo_startTask .global tinygo_startTask .type tinygo_startTask, %function diff --git a/src/runtime/asm_mipsx.S b/src/runtime/asm_mipsx.S index e380643645..f2e81bd941 100644 --- a/src/runtime/asm_mipsx.S +++ b/src/runtime/asm_mipsx.S @@ -1,3 +1,7 @@ +// Do not reorder instructions to insert a branch delay slot. +// We know what we're doing, and will manually fill the branch delay slot. +.set noreorder + .section .text.tinygo_scanCurrentStack .global tinygo_scanCurrentStack .type tinygo_scanCurrentStack, %function From d3810ecd482ec8dc070e4aebb98d68f9b13fc3f7 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 19 Nov 2024 11:28:17 +0100 Subject: [PATCH 311/444] ci: cache the Go cache across builds This should hopefully make the build slightly faster. --- .github/workflows/windows.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 0994d47a70..e81c2b4d46 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -100,6 +100,13 @@ jobs: - name: Build wasi-libc if: steps.cache-wasi-libc.outputs.cache-hit != 'true' run: make wasi-libc + - name: Cache Go cache + uses: actions/cache@v4 + with: + key: go-cache-windows-v1-${{ hashFiles('go.mod') }} + path: | + C:/Users/runneradmin/AppData/Local/go-build + C:/Users/runneradmin/go/pkg/mod - name: Install wasmtime run: | scoop install wasmtime@14.0.4 From b5a8931fb57ca93fdfcf9c4e443833cd0def2a8d Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 1 Dec 2024 13:16:42 +0100 Subject: [PATCH 312/444] runtime: remove Cond I don't think this is used anywhere right now, and it would need to be updated to work with multithreading. So instead of fixing it, I think we can remove it. My original intention was to have something like this that could be used in the machine package, but since this is in the runtime package (and the runtime package imports the machine package on baremetal) it can't actually be used that way. I checked the TinyGo repo and the drivers repo, and `runtime.Cond` isn't used anywhere except in that one test. --- src/runtime/cond.go | 90 ------------------------------------- src/runtime/cond_nosched.go | 38 ---------------- testdata/goroutines.go | 44 ------------------ 3 files changed, 172 deletions(-) delete mode 100644 src/runtime/cond.go delete mode 100644 src/runtime/cond_nosched.go diff --git a/src/runtime/cond.go b/src/runtime/cond.go deleted file mode 100644 index b27ab08abc..0000000000 --- a/src/runtime/cond.go +++ /dev/null @@ -1,90 +0,0 @@ -//go:build !scheduler.none - -package runtime - -import ( - "internal/task" - "sync/atomic" - "unsafe" -) - -// notifiedPlaceholder is a placeholder task which is used to indicate that the condition variable has been notified. -var notifiedPlaceholder task.Task - -// Cond is a simplified condition variable, useful for notifying goroutines of interrupts. -type Cond struct { - t *task.Task -} - -// Notify sends a notification. -// If the condition variable already has a pending notification, this returns false. -func (c *Cond) Notify() bool { - for { - t := (*task.Task)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)))) - switch t { - case nil: - // Nothing is waiting yet. - // Apply the notification placeholder. - if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), unsafe.Pointer(t), unsafe.Pointer(¬ifiedPlaceholder)) { - return true - } - case ¬ifiedPlaceholder: - // The condition variable has already been notified. - return false - default: - // Unblock the waiting task. - if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), unsafe.Pointer(t), nil) { - scheduleTask(t) - return true - } - } - } -} - -// Poll checks for a notification. -// If a notification is found, it is cleared and this returns true. -func (c *Cond) Poll() bool { - for { - t := (*task.Task)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)))) - switch t { - case nil: - // No notifications are present. - return false - case ¬ifiedPlaceholder: - // A notification arrived and there is no waiting goroutine. - // Clear the notification and return. - if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), unsafe.Pointer(t), nil) { - return true - } - default: - // A task is blocked on the condition variable, which means it has not been notified. - return false - } - } -} - -// Wait for a notification. -// If the condition variable was previously notified, this returns immediately. -func (c *Cond) Wait() { - cur := task.Current() - for { - t := (*task.Task)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)))) - switch t { - case nil: - // Condition variable has not been notified. - // Block the current task on the condition variable. - if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), nil, unsafe.Pointer(cur)) { - task.Pause() - return - } - case ¬ifiedPlaceholder: - // A notification arrived and there is no waiting goroutine. - // Clear the notification and return. - if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), unsafe.Pointer(t), nil) { - return - } - default: - panic("interrupt.Cond: condition variable in use by another goroutine") - } - } -} diff --git a/src/runtime/cond_nosched.go b/src/runtime/cond_nosched.go deleted file mode 100644 index ff57f41468..0000000000 --- a/src/runtime/cond_nosched.go +++ /dev/null @@ -1,38 +0,0 @@ -//go:build scheduler.none - -package runtime - -import "runtime/interrupt" - -// Cond is a simplified condition variable, useful for notifying goroutines of interrupts. -type Cond struct { - notified bool -} - -// Notify sends a notification. -// If the condition variable already has a pending notification, this returns false. -func (c *Cond) Notify() bool { - i := interrupt.Disable() - prev := c.notified - c.notified = true - interrupt.Restore(i) - return !prev -} - -// Poll checks for a notification. -// If a notification is found, it is cleared and this returns true. -func (c *Cond) Poll() bool { - i := interrupt.Disable() - notified := c.notified - c.notified = false - interrupt.Restore(i) - return notified -} - -// Wait for a notification. -// If the condition variable was previously notified, this returns immediately. -func (c *Cond) Wait() { - for !c.Poll() { - waitForEvents() - } -} diff --git a/testdata/goroutines.go b/testdata/goroutines.go index 5ac3b73187..fe4347df94 100644 --- a/testdata/goroutines.go +++ b/testdata/goroutines.go @@ -1,7 +1,6 @@ package main import ( - "runtime" "sync" "time" ) @@ -83,8 +82,6 @@ func main() { testGoOnInterface(Foo(0)) - testCond() - testIssue1790() done := make(chan int) @@ -172,47 +169,6 @@ func testGoOnBuiltins() { } } -func testCond() { - var cond runtime.Cond - go func() { - // Wait for the caller to wait on the cond. - time.Sleep(time.Millisecond) - - // Notify the caller. - ok := cond.Notify() - if !ok { - panic("notification not sent") - } - - // This notification will be buffered inside the cond. - ok = cond.Notify() - if !ok { - panic("notification not queued") - } - - // This notification should fail, since there is already one buffered. - ok = cond.Notify() - if ok { - panic("notification double-sent") - } - }() - - // Verify that the cond has no pending notifications. - ok := cond.Poll() - if ok { - panic("unexpected early notification") - } - - // Wait for the goroutine spawned earlier to send a notification. - cond.Wait() - - // The goroutine should have also queued a notification in the cond. - ok = cond.Poll() - if !ok { - panic("missing queued notification") - } -} - var once sync.Once func testGoOnInterface(f Itf) { From 72f564555e0aac17de6c5e4ef30be8feaa3b4028 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 28 Oct 2024 11:02:14 +0100 Subject: [PATCH 313/444] internal/task: add non-atomic atomic operations This adds some non-atomic types that have the same interface as the ones in sync/atomic. We currently don't need these to be atomic (because the scheduler is entirely cooperative), but once we add support for a scheduler with multiple threads and/or preemptive scheduling we can trivially add some type aliases under a different build tag in the future when real atomicity is needed for threading. --- src/internal/task/atomic-cooperative.go | 41 +++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/internal/task/atomic-cooperative.go diff --git a/src/internal/task/atomic-cooperative.go b/src/internal/task/atomic-cooperative.go new file mode 100644 index 0000000000..60eb917a8e --- /dev/null +++ b/src/internal/task/atomic-cooperative.go @@ -0,0 +1,41 @@ +package task + +// Atomics implementation for cooperative systems. The atomic types here aren't +// actually atomic, they assume that accesses cannot be interrupted by a +// different goroutine or interrupt happening at the same time. + +type atomicIntegerType interface { + uintptr | uint32 | uint64 +} + +type pseudoAtomic[T atomicIntegerType] struct { + v T +} + +func (x *pseudoAtomic[T]) Add(delta T) T { x.v += delta; return x.v } +func (x *pseudoAtomic[T]) Load() T { return x.v } +func (x *pseudoAtomic[T]) Store(val T) { x.v = val } +func (x *pseudoAtomic[T]) CompareAndSwap(old, new T) (swapped bool) { + if x.v != old { + return false + } + x.v = new + return true +} +func (x *pseudoAtomic[T]) Swap(new T) (old T) { + old = x.v + x.v = new + return +} + +// Uintptr is an atomic uintptr when multithreading is enabled, and a plain old +// uintptr otherwise. +type Uintptr = pseudoAtomic[uintptr] + +// Uint32 is an atomic uint32 when multithreading is enabled, and a plain old +// uint32 otherwise. +type Uint32 = pseudoAtomic[uint32] + +// Uint64 is an atomic uint64 when multithreading is enabled, and a plain old +// uint64 otherwise. +type Uint64 = pseudoAtomic[uint64] From 2588bf7fa047ad3d4fa02ef3e23eb21b1bb45677 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 28 Oct 2024 10:55:37 +0100 Subject: [PATCH 314/444] internal/task: add cooperative implementation of Futex See the code comments for details. But in short, this implements a futex for the cooperative scheduler (that is single threaded and non-reentrant). Similar implementations can be made for basically every other operating system, and even WebAssembly with the threading (actually: atomics) proposal. Using this basic futex implementation means we can use the same implementation for synchronisation primitives on cooperative and multicore systems. For more information on futex across operating systems: https://outerproduct.net/futex-dictionary.html --- src/internal/task/futex-cooperative.go | 44 ++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/internal/task/futex-cooperative.go diff --git a/src/internal/task/futex-cooperative.go b/src/internal/task/futex-cooperative.go new file mode 100644 index 0000000000..8351f88774 --- /dev/null +++ b/src/internal/task/futex-cooperative.go @@ -0,0 +1,44 @@ +package task + +// A futex is a way for userspace to wait with the pointer as the key, and for +// another thread to wake one or all waiting threads keyed on the same pointer. +// +// A futex does not change the underlying value, it only reads it before to prevent +// lost wake-ups. +type Futex struct { + Uint32 + waiters Stack +} + +// Atomically check for cmp to still be equal to the futex value and if so, go +// to sleep. Return true if we were definitely awoken by a call to Wake or +// WakeAll, and false if we can't be sure of that. +func (f *Futex) Wait(cmp uint32) (awoken bool) { + if f.Uint32.v != cmp { + return false + } + + // Push the current goroutine onto the waiter stack. + f.waiters.Push(Current()) + + // Pause until the waiters are awoken by Wake/WakeAll. + Pause() + + // We were awoken by a call to Wake or WakeAll. There is no chance for + // spurious wakeups. + return true +} + +// Wake a single waiter. +func (f *Futex) Wake() { + if t := f.waiters.Pop(); t != nil { + scheduleTask(t) + } +} + +// Wake all waiters. +func (f *Futex) WakeAll() { + for t := f.waiters.Pop(); t != nil; t = f.waiters.Pop() { + scheduleTask(t) + } +} From 4aac3cd7b1ca59e339fcb0953e7641b8aac27cd2 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 31 Oct 2024 11:20:09 +0100 Subject: [PATCH 315/444] sync: implement WaitGroup using a futex This prepares sync.WaitGroup for multithreading. Code size for the cooperative scheduler is nearly unchanged. --- src/sync/waitgroup.go | 91 +++++++++++++++++++++++++++++-------------- 1 file changed, 61 insertions(+), 30 deletions(-) diff --git a/src/sync/waitgroup.go b/src/sync/waitgroup.go index 72ef24c809..40306d9327 100644 --- a/src/sync/waitgroup.go +++ b/src/sync/waitgroup.go @@ -3,35 +3,65 @@ package sync import "internal/task" type WaitGroup struct { - counter uint - waiters task.Stack + futex task.Futex } func (wg *WaitGroup) Add(delta int) { - if delta > 0 { - // Check for overflow. - if uint(delta) > (^uint(0))-wg.counter { - panic("sync: WaitGroup counter overflowed") - } + switch { + case delta > 0: + // Delta is positive. + for { + // Check for overflow. + counter := wg.futex.Load() + if uint32(delta) > (^uint32(0))-counter { + panic("sync: WaitGroup counter overflowed") + } - // Add to the counter. - wg.counter += uint(delta) - } else { - // Check for underflow. - if uint(-delta) > wg.counter { - panic("sync: negative WaitGroup counter") + // Add to the counter. + if wg.futex.CompareAndSwap(counter, counter+uint32(delta)) { + // Successfully added. + return + } } + default: + // Delta is negative (or zero). + for { + counter := wg.futex.Load() - // Subtract from the counter. - wg.counter -= uint(-delta) + // Check for underflow. + if uint32(-delta) > counter { + panic("sync: negative WaitGroup counter") + } + + // Subtract from the counter. + if !wg.futex.CompareAndSwap(counter, counter-uint32(-delta)) { + // Could not swap, trying again. + continue + } - // If the counter is zero, everything is done and the waiters should be resumed. - // This code assumes that the waiters cannot wake up until after this function returns. - // In the current implementation, this is always correct. - if wg.counter == 0 { - for t := wg.waiters.Pop(); t != nil; t = wg.waiters.Pop() { - scheduleTask(t) + // If the counter is zero, everything is done and the waiters should + // be resumed. + // When there are multiple thread, there is a chance for the counter + // to go to zero, WakeAll to be called, and then the counter to be + // incremented again before a waiting goroutine has a chance to + // check the new (zero) value. However the last increment is + // explicitly given in the docs as something that should not be + // done: + // + // > Note that calls with a positive delta that occur when the + // > counter is zero must happen before a Wait. + // + // So we're fine here. + if counter-uint32(-delta) == 0 { + // TODO: this is not the most efficient implementation possible + // because we wake up all waiters unconditionally, even if there + // might be none. Though since the common usage is for this to + // be called with at least one waiter, it's probably fine. + wg.futex.WakeAll() } + + // Successfully swapped (and woken all waiting tasks if needed). + return } } } @@ -41,14 +71,15 @@ func (wg *WaitGroup) Done() { } func (wg *WaitGroup) Wait() { - if wg.counter == 0 { - // Everything already finished. - return - } - - // Push the current goroutine onto the waiter stack. - wg.waiters.Push(task.Current()) + for { + counter := wg.futex.Load() + if counter == 0 { + return // everything already finished + } - // Pause until the waiters are awoken by Add/Done. - task.Pause() + if wg.futex.Wait(counter) { + // Successfully woken by WakeAll (in wg.Add). + break + } + } } From 3eee686932d9b04534ea83bdbed7a7faf6f6b910 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Mon, 2 Dec 2024 13:48:52 +0100 Subject: [PATCH 316/444] fix: allow nintendoswitch target to compile Signed-off-by: deadprogram --- src/os/dir_other.go | 2 +- src/os/dir_unix.go | 2 +- src/os/dirent_linux.go | 2 +- src/os/exec_linux.go | 2 +- src/os/exec_other.go | 2 +- src/os/file_anyos.go | 2 +- src/os/file_other.go | 2 +- src/os/file_unix.go | 2 +- src/os/osexec.go | 2 +- src/os/removeall_noat.go | 2 +- src/os/removeall_other.go | 2 +- src/os/stat_linuxlike.go | 2 +- src/os/stat_other.go | 2 +- src/os/stat_unix.go | 2 +- src/os/types_anyos.go | 2 +- src/os/types_unix.go | 2 +- src/runtime/nonhosted.go | 2 +- src/runtime/runtime_nintendoswitch.go | 21 +++++++++++++++++++++ src/runtime/runtime_unix.go | 2 +- src/syscall/proc_emulated.go | 2 +- src/syscall/proc_hosted.go | 2 +- 21 files changed, 41 insertions(+), 20 deletions(-) diff --git a/src/os/dir_other.go b/src/os/dir_other.go index 60cd9f8e6a..0f32a12e4d 100644 --- a/src/os/dir_other.go +++ b/src/os/dir_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || js || windows || wasm_unknown +//go:build baremetal || js || windows || wasm_unknown || nintendoswitch // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/dir_unix.go b/src/os/dir_unix.go index 03624e9cff..7ff92d2318 100644 --- a/src/os/dir_unix.go +++ b/src/os/dir_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build linux && !baremetal && !wasip1 && !wasip2 && !wasm_unknown +//go:build linux && !baremetal && !wasip1 && !wasip2 && !wasm_unknown && !nintendoswitch package os diff --git a/src/os/dirent_linux.go b/src/os/dirent_linux.go index 92e2b3a65b..9c2f611089 100644 --- a/src/os/dirent_linux.go +++ b/src/os/dirent_linux.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !js && !wasip1 && !wasip2 && !wasm_unknown +//go:build !baremetal && !js && !wasip1 && !wasip2 && !wasm_unknown && !nintendoswitch // Copyright 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/exec_linux.go b/src/os/exec_linux.go index 58ee79cc81..6914a2c285 100644 --- a/src/os/exec_linux.go +++ b/src/os/exec_linux.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build linux && !baremetal && !tinygo.wasm +//go:build linux && !baremetal && !tinygo.wasm && !nintendoswitch package os diff --git a/src/os/exec_other.go b/src/os/exec_other.go index 5494f08968..b05e2830db 100644 --- a/src/os/exec_other.go +++ b/src/os/exec_other.go @@ -1,4 +1,4 @@ -//go:build (!aix && !android && !freebsd && !linux && !netbsd && !openbsd && !plan9 && !solaris) || baremetal || tinygo.wasm +//go:build (!aix && !android && !freebsd && !linux && !netbsd && !openbsd && !plan9 && !solaris) || baremetal || tinygo.wasm || nintendoswitch package os diff --git a/src/os/file_anyos.go b/src/os/file_anyos.go index da70d72847..593512cb5a 100644 --- a/src/os/file_anyos.go +++ b/src/os/file_anyos.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !js && !wasm_unknown +//go:build !baremetal && !js && !wasm_unknown && !nintendoswitch // Portions copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/file_other.go b/src/os/file_other.go index 977fcac12d..4e8f859b7a 100644 --- a/src/os/file_other.go +++ b/src/os/file_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || (tinygo.wasm && !wasip1 && !wasip2) +//go:build baremetal || (tinygo.wasm && !wasip1 && !wasip2) || nintendoswitch package os diff --git a/src/os/file_unix.go b/src/os/file_unix.go index 76d35a62cc..d94b80cc3d 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal && !wasm_unknown) || wasip1 || wasip2 +//go:build darwin || (linux && !baremetal && !wasm_unknown && !nintendoswitch) || wasip1 || wasip2 // target wasi sets GOOS=linux and thus the +linux build tag, // even though it doesn't show up in "tinygo info target -wasi" diff --git a/src/os/osexec.go b/src/os/osexec.go index 65e3ab6952..57139d1b64 100644 --- a/src/os/osexec.go +++ b/src/os/osexec.go @@ -1,4 +1,4 @@ -//go:build linux && !baremetal && !tinygo.wasm +//go:build linux && !baremetal && !tinygo.wasm && !nintendoswitch package os diff --git a/src/os/removeall_noat.go b/src/os/removeall_noat.go index 40fc801379..4b37b3bab6 100644 --- a/src/os/removeall_noat.go +++ b/src/os/removeall_noat.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !baremetal && !js && !wasip1 && !wasip2 && !wasm_unknown +//go:build !baremetal && !js && !wasip1 && !wasip2 && !wasm_unknown && !nintendoswitch package os diff --git a/src/os/removeall_other.go b/src/os/removeall_other.go index dc65aaab20..bf3265dee8 100644 --- a/src/os/removeall_other.go +++ b/src/os/removeall_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || js || wasip1 || wasip2 || wasm_unknown +//go:build baremetal || js || wasip1 || wasip2 || wasm_unknown || nintendoswitch // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/stat_linuxlike.go b/src/os/stat_linuxlike.go index 7c512ec049..c9cfd396ff 100644 --- a/src/os/stat_linuxlike.go +++ b/src/os/stat_linuxlike.go @@ -1,4 +1,4 @@ -//go:build (linux && !baremetal && !wasm_unknown) || wasip1 || wasip2 +//go:build (linux && !baremetal && !wasm_unknown && !nintendoswitch) || wasip1 || wasip2 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/stat_other.go b/src/os/stat_other.go index 234c59db21..59331bc510 100644 --- a/src/os/stat_other.go +++ b/src/os/stat_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || (tinygo.wasm && !wasip1 && !wasip2) +//go:build baremetal || (tinygo.wasm && !wasip1 && !wasip2) || nintendoswitch // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/stat_unix.go b/src/os/stat_unix.go index af5110cf8e..9882608d65 100644 --- a/src/os/stat_unix.go +++ b/src/os/stat_unix.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal && !wasm_unknown) || wasip1 || wasip2 +//go:build darwin || (linux && !baremetal && !wasm_unknown && !nintendoswitch) || wasip1 || wasip2 // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/types_anyos.go b/src/os/types_anyos.go index 939c00967c..9109fa6177 100644 --- a/src/os/types_anyos.go +++ b/src/os/types_anyos.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !js && !wasm_unknown +//go:build !baremetal && !js && !wasm_unknown && !nintendoswitch // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/types_unix.go b/src/os/types_unix.go index 0629b3bbcf..54c5c4969b 100644 --- a/src/os/types_unix.go +++ b/src/os/types_unix.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal && !wasm_unknown) || wasip1 || wasip2 +//go:build darwin || (linux && !baremetal && !wasm_unknown && !nintendoswitch) || wasip1 || wasip2 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/runtime/nonhosted.go b/src/runtime/nonhosted.go index ca5ab4c3c8..9f01a7621a 100644 --- a/src/runtime/nonhosted.go +++ b/src/runtime/nonhosted.go @@ -1,4 +1,4 @@ -//go:build baremetal || js || wasm_unknown +//go:build baremetal || js || wasm_unknown || nintendoswitch package runtime diff --git a/src/runtime/runtime_nintendoswitch.go b/src/runtime/runtime_nintendoswitch.go index d2567b1ccf..2d3677bf05 100644 --- a/src/runtime/runtime_nintendoswitch.go +++ b/src/runtime/runtime_nintendoswitch.go @@ -84,6 +84,19 @@ func ticks() timeUnit { return timeUnit(ticksToNanoseconds(timeUnit(getArmSystemTick()))) } +// timeOffset is how long the monotonic clock started after the Unix epoch. It +// should be a positive integer under normal operation or zero when it has not +// been set. +var timeOffset int64 + +//go:linkname now time.now +func now() (sec int64, nsec int32, mono int64) { + mono = nanotime() + sec = (mono + timeOffset) / (1000 * 1000 * 1000) + nsec = int32((mono + timeOffset) - sec*(1000*1000*1000)) + return +} + var stdoutBuffer = make([]byte, 120) var position = 0 @@ -98,6 +111,14 @@ func putchar(c byte) { position++ } +func buffered() int { + return 0 +} + +func getchar() byte { + return 0 +} + func abort() { for { exit(1) diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index e6f81778de..08e3e74269 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -1,4 +1,4 @@ -//go:build (darwin || (linux && !baremetal && !wasip1 && !wasm_unknown && !wasip2)) && !nintendoswitch +//go:build darwin || (linux && !baremetal && !wasip1 && !wasm_unknown && !wasip2 && !nintendoswitch) package runtime diff --git a/src/syscall/proc_emulated.go b/src/syscall/proc_emulated.go index d8e7ff7a92..bcf8eabe94 100644 --- a/src/syscall/proc_emulated.go +++ b/src/syscall/proc_emulated.go @@ -1,4 +1,4 @@ -//go:build baremetal || tinygo.wasm +//go:build baremetal || tinygo.wasm || nintendoswitch // This file emulates some process-related functions that are only available // under a real operating system. diff --git a/src/syscall/proc_hosted.go b/src/syscall/proc_hosted.go index 05c509e6ff..8b8f1fafd5 100644 --- a/src/syscall/proc_hosted.go +++ b/src/syscall/proc_hosted.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !tinygo.wasm +//go:build !baremetal && !tinygo.wasm && !nintendoswitch // This file assumes there is a libc available that runs on a real operating // system. From edb2f2a41734a028fbc17f404e4480e2fa343bdd Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 4 Dec 2024 13:59:36 +0100 Subject: [PATCH 317/444] make: modify smoketest for nintendoswitch target to build something that includes the 'os' package Signed-off-by: deadprogram --- GNUmakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GNUmakefile b/GNUmakefile index 5f82599e79..8daad25fb9 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -867,7 +867,7 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 -serial=rtt examples/echo @$(MD5SUM) test.hex - $(TINYGO) build -o test.nro -target=nintendoswitch examples/serial + $(TINYGO) build -o test.nro -target=nintendoswitch examples/echo2 @$(MD5SUM) test.nro $(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go @$(MD5SUM) test.hex From 6faf36fc6420809d442aa7d0efdb5e3b524d0289 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 4 Nov 2024 09:41:26 +0100 Subject: [PATCH 318/444] sync: make Cond MT-safe This actually simplifies the code and avoids a heap allocation in the call to Wait. Instead, it uses the Data field of the task to store information on whether a task was signalled early. --- src/sync/cond.go | 81 ++++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/src/sync/cond.go b/src/sync/cond.go index e65e86ed1b..fb5f224927 100644 --- a/src/sync/cond.go +++ b/src/sync/cond.go @@ -1,19 +1,26 @@ package sync -import "internal/task" +import ( + "internal/task" + "unsafe" +) + +// Condition variable. +// A goroutine that called Wait() can be in one of a few states depending on the +// Task.Data field: +// - When entering Wait, and before going to sleep, the data field is 0. +// - When the goroutine that calls Wait changes its data value from 0 to 1, it +// is going to sleep. It has not been awoken early. +// - When instead a call to Signal or Broadcast can change the data field from 0 +// to 1, it will _not_ go to sleep but be signalled early. +// This can happen when a concurrent call to Signal happens, or the Unlock +// function calls Signal for some reason. type Cond struct { L Locker - unlocking *earlySignal - blocked task.Stack -} - -// earlySignal is a type used to implement a stack for signalling waiters while they are unlocking. -type earlySignal struct { - next *earlySignal - - signaled bool + blocked task.Stack + lock task.PMutex } func NewCond(l Locker) *Cond { @@ -24,14 +31,14 @@ func (c *Cond) trySignal() bool { // Pop a blocked task off of the stack, and schedule it if applicable. t := c.blocked.Pop() if t != nil { - scheduleTask(t) - return true - } - - // If there any tasks which are currently unlocking, signal one. - if c.unlocking != nil { - c.unlocking.signaled = true - c.unlocking = c.unlocking.next + dataPtr := (*task.Uint32)(unsafe.Pointer(&t.Data)) + + // The data value is 0 when the task is not yet sleeping, and 1 when it is. + if dataPtr.Swap(1) != 0 { + // The value was already 1, so the task went to sleep (or is about to go + // to sleep). Schedule the task to be resumed. + scheduleTask(t) + } return true } @@ -40,21 +47,29 @@ func (c *Cond) trySignal() bool { } func (c *Cond) Signal() { + c.lock.Lock() c.trySignal() + c.lock.Unlock() } func (c *Cond) Broadcast() { // Signal everything. + c.lock.Lock() for c.trySignal() { } + c.lock.Unlock() } func (c *Cond) Wait() { - // Add an earlySignal frame to the stack so we can be signalled while unlocking. - early := earlySignal{ - next: c.unlocking, - } - c.unlocking = &early + // Mark us as not yet signalled or sleeping. + t := task.Current() + dataPtr := (*task.Uint32)(unsafe.Pointer(&t.Data)) + dataPtr.Store(0) + + // Add us to the list of waiting goroutines. + c.lock.Lock() + c.blocked.Push(t) + c.lock.Unlock() // Temporarily unlock L. c.L.Unlock() @@ -63,22 +78,14 @@ func (c *Cond) Wait() { defer c.L.Lock() // If we were signaled while unlocking, immediately complete. - if early.signaled { + if dataPtr.Swap(1) != 0 { + // The data value was already 1, so we got a signal already (and weren't + // scheduled because trySignal was the first to change the value). return } - // Remove the earlySignal frame. - prev := c.unlocking - for prev != nil && prev.next != &early { - prev = prev.next - } - if prev != nil { - prev.next = early.next - } else { - c.unlocking = early.next - } - - // Wait for a signal. - c.blocked.Push(task.Current()) + // We were the first to change the value from 0 to 1, meaning we did not get + // a signal during the call to Unlock(). So we wait until we do get a + // signal. task.Pause() } From 31f72141561dafe3f0323599ca0307b34c3fc1da Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 4 Nov 2024 10:16:35 +0100 Subject: [PATCH 319/444] test: make tests deterministic with -scheduler=threads --- testdata/channel.go | 27 ++++++++++++++++++++++----- testdata/channel.txt | 4 +--- testdata/goroutines.go | 2 +- testdata/goroutines.txt | 2 +- testdata/recover.go | 8 ++++++-- 5 files changed, 31 insertions(+), 12 deletions(-) diff --git a/testdata/channel.go b/testdata/channel.go index a7d0e99e4b..9c0fee5b73 100644 --- a/testdata/channel.go +++ b/testdata/channel.go @@ -3,6 +3,7 @@ package main import ( "runtime" "sync" + "sync/atomic" "time" ) @@ -70,11 +71,13 @@ func main() { // Test multi-receiver. ch = make(chan int) wg.Add(3) - go fastreceiver(ch) - go fastreceiver(ch) - go fastreceiver(ch) + var result atomic.Uint32 + go fastreceiveradd(ch, &result) + go fastreceiveradd(ch, &result) + go fastreceiveradd(ch, &result) slowsender(ch) wg.Wait() + println("sum of sums:", result.Load()) // Test iterator style channel. ch = make(chan int) @@ -88,7 +91,10 @@ func main() { println("sum(100):", sum) // Test simple selects. - go selectDeadlock() // cannot use waitGroup here - never terminates + wg.Add(1) + go selectDeadlock() + wg.Wait() + wg.Add(1) go selectNoOp() wg.Wait() @@ -244,7 +250,7 @@ func receive(ch <-chan int) { func sender(ch chan int) { for i := 1; i <= 8; i++ { if i == 4 { - time.Sleep(time.Microsecond) + time.Sleep(time.Millisecond) println("slept") } ch <- i @@ -290,6 +296,16 @@ func fastreceiver(ch chan int) { wg.Done() } +func fastreceiveradd(ch chan int, result *atomic.Uint32) { + sum := 0 + for i := 0; i < 2; i++ { + n := <-ch + sum += n + } + result.Add(uint32(sum)) + wg.Done() +} + func iterator(ch chan int, top int) { for i := 0; i < top; i++ { ch <- i @@ -300,6 +316,7 @@ func iterator(ch chan int, top int) { func selectDeadlock() { println("deadlocking") + wg.Done() select {} println("unreachable") } diff --git a/testdata/channel.txt b/testdata/channel.txt index bd3a4419d2..44cda5ef7b 100644 --- a/testdata/channel.txt +++ b/testdata/channel.txt @@ -12,9 +12,7 @@ received num: 8 recv from closed channel: 0 false complex128: (+7.000000e+000+1.050000e+001i) sum of n: 149 -sum: 25 -sum: 29 -sum: 33 +sum of sums: 87 sum(100): 4950 deadlocking select no-op diff --git a/testdata/goroutines.go b/testdata/goroutines.go index fe4347df94..cf19cc3ca0 100644 --- a/testdata/goroutines.go +++ b/testdata/goroutines.go @@ -93,8 +93,8 @@ func acquire(m *sync.Mutex) { m.Lock() println("acquired mutex from goroutine") time.Sleep(2 * time.Millisecond) + println("releasing mutex from goroutine") m.Unlock() - println("released mutex from goroutine") } func sub() { diff --git a/testdata/goroutines.txt b/testdata/goroutines.txt index 1430ee0a20..f1e4fc1e76 100644 --- a/testdata/goroutines.txt +++ b/testdata/goroutines.txt @@ -19,7 +19,7 @@ closure go call result: 1 pre-acquired mutex releasing mutex acquired mutex from goroutine -released mutex from goroutine +releasing mutex from goroutine re-acquired mutex done called: Foo.Nowait diff --git a/testdata/recover.go b/testdata/recover.go index 260bf91bdd..6fdf282e7b 100644 --- a/testdata/recover.go +++ b/testdata/recover.go @@ -2,9 +2,11 @@ package main import ( "runtime" - "time" + "sync" ) +var wg sync.WaitGroup + func main() { println("# simple recover") recoverSimple() @@ -113,14 +115,16 @@ func deferPanic() { } func runtimeGoexit() { + wg.Add(1) go func() { defer func() { println("Goexit deferred function, recover is nil:", recover() == nil) + wg.Done() }() runtime.Goexit() }() - time.Sleep(time.Millisecond) + wg.Wait() } func printitf(msg string, itf interface{}) { From ec3f387f21ed97b504eaa4e7cee6bed8cf13bdbb Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sun, 8 Dec 2024 19:08:16 +0100 Subject: [PATCH 320/444] fix: specify ubuntu-22.04 during GH actions transition period to ubuntu-24.04 See https://github.com/actions/runner-images/issues/10636 Signed-off-by: deadprogram --- .github/workflows/linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 206e6b7670..8d5976777a 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -169,7 +169,7 @@ jobs: assert-test-linux: # Run all tests that can run on Linux, with LLVM assertions enabled to catch # potential bugs. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 From f2465992533adcf9f98dfea359aba9171a80902f Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 14 Dec 2024 12:36:17 +0100 Subject: [PATCH 321/444] compiler: report error instead of crashing on missing function body This can happen with generic functions, see: https://github.com/tinygo-org/tinygo/issues/4486 --- compiler/symbol.go | 15 +++++++++++++-- testdata/errors/compiler.go | 11 +++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/compiler/symbol.go b/compiler/symbol.go index 93c27803e2..ff7ef05508 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -231,6 +231,15 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) } } + // Build the function if needed. + c.maybeCreateSyntheticFunction(fn, llvmFn) + + return fnType, llvmFn +} + +// If this is a synthetic function (such as a generic function or a wrapper), +// create it now. +func (c *compilerContext) maybeCreateSyntheticFunction(fn *ssa.Function, llvmFn llvm.Value) { // Synthetic functions are functions that do not appear in the source code, // they are artificially constructed. Usually they are wrapper functions // that are not referenced anywhere except in a SSA call instruction so @@ -238,6 +247,10 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) // 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 len(fn.Blocks) == 0 { + c.addError(fn.Pos(), "missing function body") + return + } irbuilder := c.ctx.NewBuilder() b := newBuilder(c, irbuilder, fn) b.createFunction() @@ -245,8 +258,6 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) llvmFn.SetLinkage(llvm.LinkOnceODRLinkage) llvmFn.SetUnnamedAddr(true) } - - return fnType, llvmFn } // getFunctionInfo returns information about a function that is not directly diff --git a/testdata/errors/compiler.go b/testdata/errors/compiler.go index 3332c7c2f2..88559103fa 100644 --- a/testdata/errors/compiler.go +++ b/testdata/errors/compiler.go @@ -7,6 +7,17 @@ func foo() { //go:align 7 var global int +// Test for https://github.com/tinygo-org/tinygo/issues/4486 +type genericType[T any] struct{} + +func (genericType[T]) methodWithoutBody() + +func callMethodWithoutBody() { + msg := &genericType[int]{} + msg.methodWithoutBody() +} + // ERROR: # command-line-arguments // ERROR: compiler.go:4:6: can only use //go:wasmimport on declarations // ERROR: compiler.go:8:5: global variable alignment must be a positive power of two +// ERROR: compiler.go:13:23: missing function body From 17302ca762d7f8948dcb21cc675070eea445271f Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Sat, 14 Dec 2024 14:26:03 +0100 Subject: [PATCH 322/444] targets: add implementation for Tillitis TKey device (#4631) * initial implementation for Tillitis TKey device * add UART implementation for TKey * add Pin interface implementation for TKey touch sensor * add RNG interface implementation for TKey * add helpful machine package functions to return identifiers such as name and version for TKey * use built-in timer for sleep timing on TKey * modify UART implementation for TKey to implement Serialer interface * implement BLAKE2s ROM function call for TKey device * handle abort by triggering TKey device fault using illegal instruction to halt CPU * simplify TKey implementation by inheriting from existing riscv32 target * return error for trying to configure invalid baudrates on UART * add tkey to builder test * be very specific for features passed to LLVM for specific config in use for TKey * handle feedback items from TKey device code review Signed-off-by: deadprogram --- GNUmakefile | 2 + builder/builder_test.go | 1 + src/crypto/rand/rand_baremetal.go | 2 +- src/device/tkey/tkey.go | 139 +++++++++++++++ src/machine/machine_tkey.go | 234 ++++++++++++++++++++++++++ src/machine/machine_tkey_rom.go | 59 +++++++ src/runtime/rand_hwrng.go | 2 +- src/runtime/rand_norng.go | 2 +- src/runtime/runtime_tkey.go | 64 +++++++ src/runtime/runtime_tkey_baremetal.go | 24 +++ targets/tkey.json | 13 ++ targets/tkey.ld | 11 ++ 12 files changed, 550 insertions(+), 3 deletions(-) create mode 100644 src/device/tkey/tkey.go create mode 100644 src/machine/machine_tkey.go create mode 100644 src/machine/machine_tkey_rom.go create mode 100644 src/runtime/runtime_tkey.go create mode 100644 src/runtime/runtime_tkey_baremetal.go create mode 100644 targets/tkey.json create mode 100644 targets/tkey.ld diff --git a/GNUmakefile b/GNUmakefile index 8daad25fb9..254f734940 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -853,6 +853,8 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=maixbit examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=tkey examples/blinky1 + @$(MD5SUM) test.hex ifneq ($(WASM), 0) $(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/export $(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/main diff --git a/builder/builder_test.go b/builder/builder_test.go index 3d714808fa..1d4584c347 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -33,6 +33,7 @@ func TestClangAttributes(t *testing.T) { "k210", "nintendoswitch", "riscv-qemu", + "tkey", "wasip1", "wasip2", "wasm", diff --git a/src/crypto/rand/rand_baremetal.go b/src/crypto/rand/rand_baremetal.go index 15fc916ca6..30b2c3b230 100644 --- a/src/crypto/rand/rand_baremetal.go +++ b/src/crypto/rand/rand_baremetal.go @@ -1,4 +1,4 @@ -//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 +//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey // If you update the above build constraint, you'll probably also need to update // src/runtime/rand_hwrng.go. diff --git a/src/device/tkey/tkey.go b/src/device/tkey/tkey.go new file mode 100644 index 0000000000..89a370414e --- /dev/null +++ b/src/device/tkey/tkey.go @@ -0,0 +1,139 @@ +//go:build tkey + +// Hand written file based on https://github.com/tillitis/tkey-libs/blob/main/include/tkey/tk1_mem.h + +package tkey + +import ( + "runtime/volatile" + "unsafe" +) + +// Peripherals +var ( + TRNG = (*TRNG_Type)(unsafe.Pointer(TK1_MMIO_TRNG_BASE)) + + TIMER = (*TIMER_Type)(unsafe.Pointer(TK1_MMIO_TIMER_BASE)) + + UDS = (*UDS_Type)(unsafe.Pointer(TK1_MMIO_UDS_BASE)) + + UART = (*UART_Type)(unsafe.Pointer(TK1_MMIO_UART_BASE)) + + TOUCH = (*TOUCH_Type)(unsafe.Pointer(TK1_MMIO_TOUCH_BASE)) + + TK1 = (*TK1_Type)(unsafe.Pointer(TK1_MMIO_TK1_BASE)) +) + +// Memory sections +const ( + TK1_ROM_BASE uintptr = 0x00000000 + + TK1_RAM_BASE uintptr = 0x40000000 + + TK1_MMIO_BASE uintptr = 0xc0000000 + + TK1_MMIO_TRNG_BASE uintptr = 0xc0000000 + + TK1_MMIO_TIMER_BASE uintptr = 0xc1000000 + + TK1_MMIO_UDS_BASE uintptr = 0xc2000000 + + TK1_MMIO_UART_BASE uintptr = 0xc3000000 + + TK1_MMIO_TOUCH_BASE uintptr = 0xc4000000 + + TK1_MMIO_FW_RAM_BASE uintptr = 0xd0000000 + + TK1_MMIO_TK1_BASE uintptr = 0xff000000 +) + +// Memory section sizes +const ( + TK1_RAM_SIZE uintptr = 0x20000 + + TK1_MMIO_SIZE uintptr = 0x3fffffff +) + +type TRNG_Type struct { + _ [36]byte + STATUS volatile.Register32 + _ [88]byte + ENTROPY volatile.Register32 +} + +type TIMER_Type struct { + _ [32]byte + CTRL volatile.Register32 + STATUS volatile.Register32 + PRESCALER volatile.Register32 + TIMER volatile.Register32 +} + +type UDS_Type struct { + _ [64]byte + DATA [8]volatile.Register32 +} + +type UART_Type struct { + _ [128]byte + RX_STATUS volatile.Register32 + RX_DATA volatile.Register32 + RX_BYTES volatile.Register32 + _ [116]byte + TX_STATUS volatile.Register32 + TX_DATA volatile.Register32 +} + +type TOUCH_Type struct { + _ [36]byte + STATUS volatile.Register32 +} + +type TK1_Type struct { + NAME0 volatile.Register32 + NAME1 volatile.Register32 + VERSION volatile.Register32 + _ [16]byte + SWITCH_APP volatile.Register32 + _ [4]byte + LED volatile.Register32 + GPIO volatile.Register16 + APP_ADDR volatile.Register32 + APP_SIZE volatile.Register32 + BLAKE2S volatile.Register32 + _ [72]byte + CDI_FIRST [8]volatile.Register32 + _ [32]byte + UDI_FIRST [2]volatile.Register32 + _ [62]byte + RAM_ADDR_RAND volatile.Register16 + _ [2]byte + RAM_DATA_RAND volatile.Register16 + _ [126]byte + CPU_MON_CTRL volatile.Register16 + _ [2]byte + CPU_MON_FIRST volatile.Register32 + CPU_MON_LAST volatile.Register32 + _ [60]byte + SYSTEM_RESET volatile.Register16 + _ [66]byte + SPI_EN volatile.Register32 + SPI_XFER volatile.Register32 + SPI_DATA volatile.Register32 +} + +const ( + TK1_MMIO_TIMER_CTRL_START_BIT = 0 + TK1_MMIO_TIMER_CTRL_STOP_BIT = 1 + TK1_MMIO_TIMER_CTRL_START = 1 << TK1_MMIO_TIMER_CTRL_START_BIT + TK1_MMIO_TIMER_CTRL_STOP = 1 << TK1_MMIO_TIMER_CTRL_STOP_BIT + + TK1_MMIO_TK1_LED_R_BIT = 2 + TK1_MMIO_TK1_LED_G_BIT = 1 + TK1_MMIO_TK1_LED_B_BIT = 0 + + TK1_MMIO_TK1_GPIO1_BIT = 0 + TK1_MMIO_TK1_GPIO2_BIT = 1 + TK1_MMIO_TK1_GPIO3_BIT = 2 + TK1_MMIO_TK1_GPIO4_BIT = 3 +) diff --git a/src/machine/machine_tkey.go b/src/machine/machine_tkey.go new file mode 100644 index 0000000000..78863d845c --- /dev/null +++ b/src/machine/machine_tkey.go @@ -0,0 +1,234 @@ +//go:build tkey + +package machine + +import ( + "device/tkey" + "errors" + "strconv" +) + +const deviceName = "TKey" + +// GPIO pins modes are only here to match the Pin interface. +// The actual configuration is fixed in the hardware. +const ( + PinOutput PinMode = iota + PinInput + PinInputPullup + PinInputPulldown +) + +const ( + LED_BLUE = Pin(tkey.TK1_MMIO_TK1_LED_B_BIT) + LED_GREEN = Pin(tkey.TK1_MMIO_TK1_LED_G_BIT) + LED_RED = Pin(tkey.TK1_MMIO_TK1_LED_R_BIT) + + LED = LED_GREEN + + TKEY_TOUCH = Pin(3) // 3 is unused, but we need a value here to match the Pin interface. + BUTTON = TKEY_TOUCH + + GPIO1 = Pin(tkey.TK1_MMIO_TK1_GPIO1_BIT + 8) + GPIO2 = Pin(tkey.TK1_MMIO_TK1_GPIO2_BIT + 8) + GPIO3 = Pin(tkey.TK1_MMIO_TK1_GPIO3_BIT + 8) + GPIO4 = Pin(tkey.TK1_MMIO_TK1_GPIO4_BIT + 8) +) + +var touchConfig, gpio1Config, gpio2Config PinConfig + +// No config needed for TKey, just to match the Pin interface. +func (p Pin) Configure(config PinConfig) { + switch p { + case BUTTON: + touchConfig = config + + // Clear any pending touch events. + tkey.TOUCH.STATUS.Set(0) + case GPIO1: + gpio1Config = config + case GPIO2: + gpio2Config = config + } +} + +// Set pin to high or low. +func (p Pin) Set(high bool) { + switch p { + case LED_BLUE, LED_GREEN, LED_RED: + if high { + tkey.TK1.LED.SetBits(1 << uint(p)) + } else { + tkey.TK1.LED.ClearBits(1 << uint(p)) + } + case GPIO3, GPIO4: + if high { + tkey.TK1.GPIO.SetBits(1 << uint(p-8)) + } else { + tkey.TK1.GPIO.ClearBits(1 << uint(p-8)) + } + } +} + +// Get returns the current value of a pin. +func (p Pin) Get() bool { + pushed := false + mode := PinInput + + switch p { + case BUTTON: + mode = touchConfig.Mode + if tkey.TOUCH.STATUS.HasBits(1) { + tkey.TOUCH.STATUS.Set(0) + pushed = true + } + case GPIO1: + mode = gpio1Config.Mode + pushed = tkey.TK1.GPIO.HasBits(1 << uint(p-8)) + case GPIO2: + mode = gpio2Config.Mode + pushed = tkey.TK1.GPIO.HasBits(1 << uint(p-8)) + case GPIO3, GPIO4: + mode = PinOutput + pushed = tkey.TK1.GPIO.HasBits(1 << uint(p-8)) + case LED_BLUE, LED_GREEN, LED_RED: + mode = PinOutput + pushed = tkey.TK1.LED.HasBits(1 << uint(p)) + } + + switch mode { + case PinInputPullup: + return !pushed + case PinInput, PinInputPulldown, PinOutput: + return pushed + } + + return false +} + +type UART struct { + Bus *tkey.UART_Type +} + +var ( + DefaultUART = UART0 + UART0 = &_UART0 + _UART0 = UART{Bus: tkey.UART} +) + +// The TKey UART is fixed at 62500 baud, 8N1. +func (uart *UART) Configure(config UARTConfig) error { + if !(config.BaudRate == 62500 || config.BaudRate == 0) { + return errors.New("uart: only 62500 baud rate is supported") + } + + return nil +} + +// Write a slice of data bytes to the UART. +func (uart *UART) Write(data []byte) (n int, err error) { + for _, c := range data { + if err := uart.WriteByte(c); err != nil { + return n, err + } + } + return len(data), nil +} + +// WriteByte writes a byte of data to the UART. +func (uart *UART) WriteByte(c byte) error { + for uart.Bus.TX_STATUS.Get() == 0 { + } + + uart.Bus.TX_DATA.Set(uint32(c)) + + return nil +} + +// Buffered returns the number of bytes buffered in the UART. +func (uart *UART) Buffered() int { + return int(uart.Bus.RX_BYTES.Get()) +} + +// ReadByte reads a byte of data from the UART. +func (uart *UART) ReadByte() (byte, error) { + for uart.Bus.RX_STATUS.Get() == 0 { + } + + return byte(uart.Bus.RX_DATA.Get()), nil +} + +// DTR is not available on the TKey. +func (uart *UART) DTR() bool { + return false +} + +// RTS is not available on the TKey. +func (uart *UART) RTS() bool { + return false +} + +// GetRNG returns 32 bits of cryptographically secure random data +func GetRNG() (uint32, error) { + for tkey.TRNG.STATUS.Get() == 0 { + } + + return uint32(tkey.TRNG.ENTROPY.Get()), nil +} + +// DesignName returns the FPGA design name. +func DesignName() (string, string) { + n0 := tkey.TK1.NAME0.Get() + name0 := string([]byte{byte(n0 >> 24), byte(n0 >> 16), byte(n0 >> 8), byte(n0)}) + n1 := tkey.TK1.NAME1.Get() + name1 := string([]byte{byte(n1 >> 24), byte(n1 >> 16), byte(n1 >> 8), byte(n1)}) + + return name0, name1 +} + +// DesignVersion returns the FPGA design version. +func DesignVersion() string { + version := tkey.TK1.VERSION.Get() + + return strconv.Itoa(int(version)) +} + +// CDI returns 8 words of Compound Device Identifier (CDI) generated and written by the firmware when the application is loaded. +func CDI() []byte { + cdi := make([]byte, 32) + for i := 0; i < 8; i++ { + c := tkey.TK1.CDI_FIRST[i].Get() + cdi[i*4] = byte(c >> 24) + cdi[i*4+1] = byte(c >> 16) + cdi[i*4+2] = byte(c >> 8) + cdi[i*4+3] = byte(c) + } + return cdi +} + +// UDI returns 2 words of Unique Device Identifier (UDI). Only available in firmware mode. +func UDI() []byte { + udi := make([]byte, 8) + for i := 0; i < 2; i++ { + c := tkey.TK1.UDI_FIRST[i].Get() + udi[i*4] = byte(c >> 24) + udi[i*4+1] = byte(c >> 16) + udi[i*4+2] = byte(c >> 8) + udi[i*4+3] = byte(c) + } + return udi +} + +// UDS returns 8 words of Unique Device Secret. Part of the FPGA design, changed when provisioning a TKey. +// Only available in firmware mode. UDS is only readable once per power cycle. +func UDS() []byte { + uds := make([]byte, 32) + for i := 0; i < 8; i++ { + c := tkey.UDS.DATA[i].Get() + uds[i*4] = byte(c >> 24) + uds[i*4+1] = byte(c >> 16) + uds[i*4+2] = byte(c >> 8) + uds[i*4+3] = byte(c) + } + return uds +} diff --git a/src/machine/machine_tkey_rom.go b/src/machine/machine_tkey_rom.go new file mode 100644 index 0000000000..bc7162e047 --- /dev/null +++ b/src/machine/machine_tkey_rom.go @@ -0,0 +1,59 @@ +//go:build tkey + +package machine + +/* + #define TK1_MMIO_TK1_BLAKE2S 0xff000040 + + typedef unsigned char uint8_t; + typedef unsigned long uint32_t; + typedef unsigned long size_t; + + // blake2s state context + typedef struct { + uint8_t b[64]; // input buffer + uint32_t h[8]; // chained state + uint32_t t[2]; // total number of bytes + size_t c; // pointer for b[] + size_t outlen; // digest size + } blake2s_ctx; + + typedef int (*fw_blake2s_p)(void *out, unsigned long outlen, const void *key, + unsigned long keylen, const void *in, + unsigned long inlen, blake2s_ctx *ctx); + + int blake2s(void *out, unsigned long outlen, const void *key, unsigned long keylen, const void *in, unsigned long inlen) + { + fw_blake2s_p const fw_blake2s = + (fw_blake2s_p) * (volatile uint32_t *)TK1_MMIO_TK1_BLAKE2S; + blake2s_ctx ctx; + + return fw_blake2s(out, outlen, key, keylen, in, inlen, &ctx); + } +*/ +import "C" +import ( + "errors" + "unsafe" +) + +var ( + ErrBLAKE2sInvalid = errors.New("invalid params for call to BLAKE2s") + ErrBLAKE2sFailed = errors.New("call to BLAKE2s failed") +) + +func BLAKE2s(output []byte, key []byte, input []byte) error { + if len(output) == 0 || len(input) == 0 { + return ErrBLAKE2sInvalid + } + + op := unsafe.Pointer(&output[0]) + kp := unsafe.Pointer(&key[0]) + ip := unsafe.Pointer(&input[0]) + + if res := C.blake2s(op, C.size_t(len(output)), kp, C.size_t(len(key)), ip, C.size_t(len(input))); res != 0 { + return ErrBLAKE2sFailed + } + + return nil +} diff --git a/src/runtime/rand_hwrng.go b/src/runtime/rand_hwrng.go index 7154ffd79c..2c690b4490 100644 --- a/src/runtime/rand_hwrng.go +++ b/src/runtime/rand_hwrng.go @@ -1,4 +1,4 @@ -//go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3) +//go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey) // If you update the above build constraint, you'll probably also need to update // src/crypto/rand/rand_baremetal.go. diff --git a/src/runtime/rand_norng.go b/src/runtime/rand_norng.go index e6045008e4..a86cdc5429 100644 --- a/src/runtime/rand_norng.go +++ b/src/runtime/rand_norng.go @@ -1,4 +1,4 @@ -//go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3) +//go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey) package runtime diff --git a/src/runtime/runtime_tkey.go b/src/runtime/runtime_tkey.go new file mode 100644 index 0000000000..ba8c5e944f --- /dev/null +++ b/src/runtime/runtime_tkey.go @@ -0,0 +1,64 @@ +//go:build tkey + +// This file implements target-specific things for the TKey. + +package runtime + +import ( + "device/tkey" + "machine" + "runtime/volatile" +) + +type timeUnit int64 + +//export main +func main() { + preinit() + initPeripherals() + run() + exit(0) +} + +// initPeripherals configures peripherals the way the runtime expects them. +func initPeripherals() { + // prescaler value that results in 0.00001-second timer-ticks. + // given an 18 MHz processor, a millisecond is about 18,000 cycles. + tkey.TIMER.PRESCALER.Set(18 * machine.MHz / 100000) + + machine.InitSerial() +} + +func putchar(c byte) { + machine.Serial.WriteByte(c) +} + +func getchar() byte { + for machine.Serial.Buffered() == 0 { + Gosched() + } + v, _ := machine.Serial.ReadByte() + return v +} + +func buffered() int { + return machine.Serial.Buffered() +} + +var timestamp volatile.Register32 + +// ticks returns the current value of the timer in ticks. +func ticks() timeUnit { + return timeUnit(timestamp.Get()) +} + +// sleepTicks sleeps for at least the duration d. +func sleepTicks(d timeUnit) { + target := uint32(ticks() + d) + + tkey.TIMER.TIMER.Set(uint32(d)) + tkey.TIMER.CTRL.SetBits(tkey.TK1_MMIO_TIMER_CTRL_START) + for tkey.TIMER.STATUS.Get() != 0 { + } + timestamp.Set(target) +} diff --git a/src/runtime/runtime_tkey_baremetal.go b/src/runtime/runtime_tkey_baremetal.go new file mode 100644 index 0000000000..a83bd4408d --- /dev/null +++ b/src/runtime/runtime_tkey_baremetal.go @@ -0,0 +1,24 @@ +//go:build tkey && !qemu + +package runtime + +import "device/riscv" + +// ticksToNanoseconds converts ticks (at 18MHz) to 10 µs. +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) * 10000 +} + +// nanosecondsToTicks converts 10 µs to ticks (at 18MHz). +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns / 10000) +} + +func exit(code int) { + abort() +} + +func abort() { + // Force illegal instruction to halt CPU + riscv.Asm("unimp") +} diff --git a/targets/tkey.json b/targets/tkey.json new file mode 100644 index 0000000000..f450e6a1b1 --- /dev/null +++ b/targets/tkey.json @@ -0,0 +1,13 @@ +{ + "inherits": ["riscv32"], + "build-tags": ["tkey"], + "features": "+32bit,+c,+zmmul,-a,-d,-e,-experimental-zacas,-experimental-zcmop,-experimental-zfbfmin,-experimental-zicfilp,-experimental-zicfiss,-experimental-zimop,-experimental-ztso,-experimental-zvfbfmin,-experimental-zvfbfwma,-f,-h,-m,-relax,-smaia,-smepmp,-ssaia,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-za128rs,-za64rs,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmp,-zcmt,-zdinx,-zfa,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-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", + "cflags": [ + "-march=rv32iczmmul" + ], + "linkerscript": "targets/tkey.ld", + "scheduler": "none", + "gc": "leaking", + "flash-command": "tkey-runapp {bin}", + "serial": "uart" +} diff --git a/targets/tkey.ld b/targets/tkey.ld new file mode 100644 index 0000000000..09becf4036 --- /dev/null +++ b/targets/tkey.ld @@ -0,0 +1,11 @@ + +MEMORY +{ + RAM (rwx) : ORIGIN = 0x40000000, LENGTH = 0x20000 /* 128 KB */ +} + +REGION_ALIAS("FLASH_TEXT", RAM); + +_stack_size = 2K; + +INCLUDE "targets/riscv.ld" From 6110f0bc1b5cb807fa2ac01b12b5aeffcf3308c6 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 27 Nov 2024 10:52:07 +0100 Subject: [PATCH 323/444] runtime: make channels parallelism-safe --- src/internal/task/task.go | 7 +- src/runtime/chan.go | 120 +++++++++++++++++++++------ src/runtime/scheduler_cooperative.go | 8 +- src/runtime/scheduler_none.go | 3 + 4 files changed, 108 insertions(+), 30 deletions(-) diff --git a/src/internal/task/task.go b/src/internal/task/task.go index 587c675267..546f5ba117 100644 --- a/src/internal/task/task.go +++ b/src/internal/task/task.go @@ -27,7 +27,7 @@ type Task struct { } // DataUint32 returns the Data field as a uint32. The value is only valid after -// setting it through SetDataUint32. +// setting it through SetDataUint32 or by storing to it using DataAtomicUint32. func (t *Task) DataUint32() uint32 { return *(*uint32)(unsafe.Pointer(&t.Data)) } @@ -38,6 +38,11 @@ func (t *Task) SetDataUint32(val uint32) { *(*uint32)(unsafe.Pointer(&t.Data)) = val } +// DataAtomicUint32 returns the Data field as an atomic-if-needed Uint32 value. +func (t *Task) DataAtomicUint32() *Uint32 { + return (*Uint32)(unsafe.Pointer(&t.Data)) +} + // getGoroutineStackSize is a compiler intrinsic that returns the stack size for // the given function and falls back to the default stack size. It is replaced // with a load from a special section just before codegen. diff --git a/src/runtime/chan.go b/src/runtime/chan.go index 0d0cbf06aa..e437798b09 100644 --- a/src/runtime/chan.go +++ b/src/runtime/chan.go @@ -30,11 +30,12 @@ package runtime // non-select operations) so that the select operation knows which case did // proceed. // The value is at the same time also a way that goroutines can be the first -// (and only) goroutine to 'take' a channel operation to change it from -// 'waiting' to any other value. This is important for the select statement -// because multiple goroutines could try to let different channels in the -// select statement proceed at the same time. By using Task.Data, only a -// single channel operation in the select statement can proceed. +// (and only) goroutine to 'take' a channel operation using an atomic CAS +// operation to change it from 'waiting' to any other value. This is important +// for the select statement because multiple goroutines could try to let +// different channels in the select statement proceed at the same time. By +// using Task.Data, only a single channel operation in the select statement +// can proceed. // - It is possible for the channel queues to contain already-processed senders // or receivers. This can happen when the select statement managed to proceed // but the goroutine doing the select has not yet cleaned up the stale queue @@ -49,15 +50,17 @@ import ( // The runtime implementation of the Go 'chan' type. type channel struct { - closed bool - elementSize uintptr - bufCap uintptr // 'cap' - bufLen uintptr // 'len' - bufHead uintptr - bufTail uintptr - senders chanQueue - receivers chanQueue - buf unsafe.Pointer + closed bool + selectLocked bool + elementSize uintptr + bufCap uintptr // 'cap' + bufLen uintptr // 'len' + bufHead uintptr + bufTail uintptr + senders chanQueue + receivers chanQueue + lock task.PMutex + buf unsafe.Pointer } const ( @@ -73,7 +76,8 @@ type chanQueue struct { // Pus the next channel operation to the queue. All appropriate fields must have // been initialized already. -// This function must be called with interrupts disabled. +// This function must be called with interrupts disabled and the channel lock +// held. func (q *chanQueue) push(node *channelOp) { node.next = q.first q.first = node @@ -99,8 +103,8 @@ func (q *chanQueue) pop(chanOp uint32) *channelOp { newDataValue := chanOp | popped.index<<2 // Try to be the first to proceed with this goroutine. - if popped.task.DataUint32() == chanOperationWaiting { - popped.task.SetDataUint32(newDataValue) + swapped := popped.task.DataAtomicUint32().CompareAndSwap(0, newDataValue) + if swapped { return popped } } @@ -108,7 +112,8 @@ func (q *chanQueue) pop(chanOp uint32) *channelOp { // Remove the given to-be-removed node from the queue if it is part of the // queue. If there are multiple, only one will be removed. -// This function must be called with interrupts disabled. +// This function must be called with interrupts disabled and the channel lock +// held. func (q *chanQueue) remove(remove *channelOp) { n := &q.first for *n != nil { @@ -159,8 +164,8 @@ func chanCap(c *channel) int { } // Push the value to the channel buffer array, for a send operation. -// This function may only be called when interrupts are disabled and it is known -// there is space available in the buffer. +// This function may only be called when interrupts are disabled, the channel is +// locked and it is known there is space available in the buffer. func (ch *channel) bufferPush(value unsafe.Pointer) { elemAddr := unsafe.Add(ch.buf, ch.bufHead*ch.elementSize) ch.bufLen++ @@ -174,8 +179,8 @@ func (ch *channel) bufferPush(value unsafe.Pointer) { // Pop a value from the channel buffer and store it in the 'value' pointer, for // a receive operation. -// This function may only be called when interrupts are disabled and it is known -// there is at least one value available in the buffer. +// This function may only be called when interrupts are disabled, the channel is +// locked and it is known there is at least one value available in the buffer. func (ch *channel) bufferPop(value unsafe.Pointer) { elemAddr := unsafe.Add(ch.buf, ch.bufTail*ch.elementSize) ch.bufLen-- @@ -191,7 +196,8 @@ func (ch *channel) bufferPop(value unsafe.Pointer) { } // Try to proceed with this send operation without blocking, and return whether -// the send succeeded. Interrupts must be disabled when calling this function. +// the send succeeded. Interrupts must be disabled and the lock must be held +// when calling this function. func (ch *channel) trySend(value unsafe.Pointer) bool { // To make sure we send values in the correct order, we can only send // directly to a receiver when there are no values in the buffer. @@ -230,9 +236,11 @@ func chanSend(ch *channel, value unsafe.Pointer, op *channelOp) { } mask := interrupt.Disable() + ch.lock.Lock() // See whether we can proceed immediately, and if so, return early. if ch.trySend(value) { + ch.lock.Unlock() interrupt.Restore(mask) return } @@ -244,9 +252,12 @@ func chanSend(ch *channel, value unsafe.Pointer, op *channelOp) { op.index = 0 op.value = value ch.senders.push(op) + ch.lock.Unlock() interrupt.Restore(mask) // Wait until this goroutine is resumed. + // It might be resumed after Unlock() and before Pause(). In that case, + // because we use semaphores, the Pause() will continue immediately. task.Pause() // Check whether the sent happened normally (not because the channel was @@ -258,8 +269,8 @@ func chanSend(ch *channel, value unsafe.Pointer, op *channelOp) { } // Try to proceed with this receive operation without blocking, and return -// whether the receive operation succeeded. Interrupts must be disabled when -// calling this function. +// whether the receive operation succeeded. Interrupts must be disabled and the +// lock must be held when calling this function. func (ch *channel) tryRecv(value unsafe.Pointer) (received, ok bool) { // To make sure we keep the values in the channel in the correct order, we // first have to read values from the buffer before we can look at the @@ -303,8 +314,10 @@ func chanRecv(ch *channel, value unsafe.Pointer, op *channelOp) bool { } mask := interrupt.Disable() + ch.lock.Lock() if received, ok := ch.tryRecv(value); received { + ch.lock.Unlock() interrupt.Restore(mask) return ok } @@ -317,6 +330,7 @@ func chanRecv(ch *channel, value unsafe.Pointer, op *channelOp) bool { op.task = t op.index = 0 ch.receivers.push(op) + ch.lock.Unlock() interrupt.Restore(mask) // Wait until the goroutine is resumed. @@ -335,9 +349,11 @@ func chanClose(ch *channel) { } mask := interrupt.Disable() + ch.lock.Lock() if ch.closed { // Not allowed by the language spec. + ch.lock.Unlock() interrupt.Restore(mask) runtimePanic("close of closed channel") } @@ -370,14 +386,56 @@ func chanClose(ch *channel) { ch.closed = true + ch.lock.Unlock() interrupt.Restore(mask) } +// We currently use a global select lock to avoid deadlocks while locking each +// individual channel in the select. Without this global lock, two select +// operations that have a different order of the same channels could end up in a +// deadlock. This global lock is inefficient if there are many select operations +// happening in parallel, but gets the job done. +// +// If this becomes a performance issue, we can see how the Go runtime does this. +// I think it does this by sorting all states by channel address and then +// locking them in that order to avoid this deadlock. +var chanSelectLock task.PMutex + +// Lock all channels (taking care to skip duplicate channels). +func lockAllStates(states []chanSelectState) { + if !hasParallelism { + return + } + for _, state := range states { + if state.ch != nil && !state.ch.selectLocked { + state.ch.lock.Lock() + state.ch.selectLocked = true + } + } +} + +// Unlock all channels (taking care to skip duplicate channels). +func unlockAllStates(states []chanSelectState) { + if !hasParallelism { + return + } + for _, state := range states { + if state.ch != nil && state.ch.selectLocked { + state.ch.lock.Unlock() + state.ch.selectLocked = false + } + } +} + // chanSelect implements blocking or non-blocking select operations. // The 'ops' slice must be set if (and only if) this is a blocking select. func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelOp) (uint32, bool) { mask := interrupt.Disable() + // Lock everything. + chanSelectLock.Lock() + lockAllStates(states) + const selectNoIndex = ^uint32(0) selectIndex := selectNoIndex selectOk := true @@ -409,6 +467,8 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelO // return early. blocking := len(ops) != 0 if selectIndex != selectNoIndex || !blocking { + unlockAllStates(states) + chanSelectLock.Unlock() interrupt.Restore(mask) return selectIndex, selectOk } @@ -417,8 +477,8 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelO // become more complicated. // We add ourselves as a sender/receiver to every channel, and wait for the // first one to complete. Only one will successfully complete, because - // senders and receivers will check t.Data for the state so that only one - // will be able to "take" this select operation. + // senders and receivers use a compare-and-exchange atomic operation on + // t.Data so that only one will be able to "take" this select operation. t := task.Current() t.Ptr = recvbuf t.SetDataUint32(chanOperationWaiting) @@ -438,6 +498,8 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelO } // Now we wait until one of the send/receive operations can proceed. + unlockAllStates(states) + chanSelectLock.Unlock() interrupt.Restore(mask) task.Pause() @@ -445,6 +507,8 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelO // Make sure all channel ops are removed from the senders/receivers // queue before we return and the memory of them becomes invalid. + chanSelectLock.Lock() + lockAllStates(states) for i, state := range states { if state.ch == nil { continue @@ -458,6 +522,8 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelO } interrupt.Restore(mask) } + unlockAllStates(states) + chanSelectLock.Unlock() // Pull the return values out of t.Data (which contains two bitfields). selectIndex = t.DataUint32() >> 2 diff --git a/src/runtime/scheduler_cooperative.go b/src/runtime/scheduler_cooperative.go index 5c4dfd5bf5..91ba86409f 100644 --- a/src/runtime/scheduler_cooperative.go +++ b/src/runtime/scheduler_cooperative.go @@ -21,6 +21,12 @@ import ( // queue a new scheduler invocation using setTimeout. const asyncScheduler = GOOS == "js" +const hasScheduler = true + +// Concurrency is not parallelism. While the cooperative scheduler has +// concurrency, it does not have parallelism. +const hasParallelism = false + // Queues used by the scheduler. var ( runqueue task.Queue @@ -248,5 +254,3 @@ func run() { }() scheduler(false) } - -const hasScheduler = true diff --git a/src/runtime/scheduler_none.go b/src/runtime/scheduler_none.go index 7775b360eb..a5acfd4309 100644 --- a/src/runtime/scheduler_none.go +++ b/src/runtime/scheduler_none.go @@ -6,6 +6,9 @@ import "internal/task" const hasScheduler = false +// No goroutines are allowed, so there's no parallelism anywhere. +const hasParallelism = false + // run is called by the program entry point to execute the go program. // With the "none" scheduler, init and the main function are invoked directly. func run() { From 5701bf81f30d06daa4bc093f716f496c16147e10 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sun, 8 Dec 2024 10:37:30 +0100 Subject: [PATCH 324/444] feature: modify i2s interface/implementation to better match specification Signed-off-by: deadprogram --- src/examples/i2s/i2s.go | 4 +- src/machine/board_arduino_mkr1000.go | 5 +- src/machine/board_arduino_mkrwifi1010.go | 3 +- src/machine/board_arduino_nano33.go | 3 +- src/machine/board_arduino_zero.go | 3 +- src/machine/board_circuitplay_express.go | 3 +- src/machine/board_feather-m0-express.go | 3 +- src/machine/board_feather-m0.go | 3 +- src/machine/board_gemma-m0.go | 3 +- src/machine/board_grandcentral-m4.go | 3 +- src/machine/board_itsybitsy-m0.go | 3 +- src/machine/board_p1am-100.go | 3 +- src/machine/board_qtpy.go | 3 +- src/machine/board_trinket.go | 3 +- src/machine/board_wioterminal.go | 8 ++ src/machine/board_xiao.go | 3 +- src/machine/i2s.go | 34 ++++- src/machine/machine_atsamd21.go | 153 +++++++++++++++-------- 18 files changed, 173 insertions(+), 70 deletions(-) diff --git a/src/examples/i2s/i2s.go b/src/examples/i2s/i2s.go index 4936d92ff5..06857c4882 100644 --- a/src/examples/i2s/i2s.go +++ b/src/examples/i2s/i2s.go @@ -13,11 +13,11 @@ func main() { Stereo: true, }) - data := make([]uint32, 64) + data := make([]uint16, 64) for { // get the next group of samples - machine.I2S0.Read(data) + machine.I2S0.ReadMono(data) println("data", data[0], data[1], data[2], data[4], "...") } diff --git a/src/machine/board_arduino_mkr1000.go b/src/machine/board_arduino_mkr1000.go index 2c9ae603f4..f5130120e7 100644 --- a/src/machine/board_arduino_mkr1000.go +++ b/src/machine/board_arduino_mkr1000.go @@ -74,8 +74,9 @@ const ( // I2S pins const ( I2S_SCK_PIN Pin = PA10 - I2S_SD_PIN Pin = PA07 - I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino Nano 33. + I2S_SDO_PIN Pin = PA07 + I2S_SDI_PIN = NoPin + I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino MKR1000 ) // USB CDC identifiers diff --git a/src/machine/board_arduino_mkrwifi1010.go b/src/machine/board_arduino_mkrwifi1010.go index 18330f37f7..c68da9b626 100644 --- a/src/machine/board_arduino_mkrwifi1010.go +++ b/src/machine/board_arduino_mkrwifi1010.go @@ -74,7 +74,8 @@ const ( // I2S pins const ( I2S_SCK_PIN Pin = PA10 - I2S_SD_PIN Pin = PA07 + I2S_SDO_PIN Pin = PA07 + I2S_SDI_PIN = NoPin I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino MKR WiFi 1010. ) diff --git a/src/machine/board_arduino_nano33.go b/src/machine/board_arduino_nano33.go index 17f255443e..9232d38190 100644 --- a/src/machine/board_arduino_nano33.go +++ b/src/machine/board_arduino_nano33.go @@ -118,7 +118,8 @@ const ( // I2S pins const ( I2S_SCK_PIN Pin = PA10 - I2S_SD_PIN Pin = PA08 + I2S_SDO_PIN Pin = PA08 + I2S_SDI_PIN = NoPin I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino Nano 33. ) diff --git a/src/machine/board_arduino_zero.go b/src/machine/board_arduino_zero.go index f09fb47c56..758fcb16e0 100644 --- a/src/machine/board_arduino_zero.go +++ b/src/machine/board_arduino_zero.go @@ -69,7 +69,8 @@ const ( // I2S pins - might not be exposed const ( I2S_SCK_PIN Pin = PA10 - I2S_SD_PIN Pin = PA07 + I2S_SDO_PIN Pin = PA07 + I2S_SDI_PIN = NoPin I2S_WS_PIN Pin = PA11 ) diff --git a/src/machine/board_circuitplay_express.go b/src/machine/board_circuitplay_express.go index 1601fcab32..ce1f29c75b 100644 --- a/src/machine/board_circuitplay_express.go +++ b/src/machine/board_circuitplay_express.go @@ -102,7 +102,8 @@ var SPI0 = sercomSPIM3 // I2S pins const ( I2S_SCK_PIN = PA10 - I2S_SD_PIN = PA08 + I2S_SDO_PIN = PA08 + I2S_SDI_PIN = NoPin I2S_WS_PIN = NoPin // no WS, instead uses SCK to sync ) diff --git a/src/machine/board_feather-m0-express.go b/src/machine/board_feather-m0-express.go index a0f7c23055..226369ffcd 100644 --- a/src/machine/board_feather-m0-express.go +++ b/src/machine/board_feather-m0-express.go @@ -81,7 +81,8 @@ var SPI0 = sercomSPIM4 // I2S pins const ( I2S_SCK_PIN = PA10 - I2S_SD_PIN = PA07 + I2S_SDO_PIN = PA07 + I2S_SDI_PIN = NoPin I2S_WS_PIN = NoPin // TODO: figure out what this is on Feather M0 Express. ) diff --git a/src/machine/board_feather-m0.go b/src/machine/board_feather-m0.go index 5cd3393400..f38d8ec889 100644 --- a/src/machine/board_feather-m0.go +++ b/src/machine/board_feather-m0.go @@ -76,7 +76,8 @@ var SPI0 = sercomSPIM4 // I2S pins const ( I2S_SCK_PIN = PA10 - I2S_SD_PIN = PA08 + I2S_SDO_PIN = PA08 + I2S_SDI_PIN = NoPin I2S_WS_PIN = NoPin // TODO: figure out what this is on Feather M0. ) diff --git a/src/machine/board_gemma-m0.go b/src/machine/board_gemma-m0.go index 3702c74c3c..af1caaad6e 100644 --- a/src/machine/board_gemma-m0.go +++ b/src/machine/board_gemma-m0.go @@ -76,7 +76,8 @@ var ( // I2S (not connected, needed for atsamd21). const ( I2S_SCK_PIN = NoPin - I2S_SD_PIN = NoPin + I2S_SDO_PIN = NoPin + I2S_SDI_PIN = NoPin I2S_WS_PIN = NoPin ) diff --git a/src/machine/board_grandcentral-m4.go b/src/machine/board_grandcentral-m4.go index 46fb956978..61ef6a89b8 100644 --- a/src/machine/board_grandcentral-m4.go +++ b/src/machine/board_grandcentral-m4.go @@ -224,7 +224,8 @@ const ( I2S_SCK_PIN = I2S0_SCK_PIN // default pins I2S_WS_PIN = I2S0_FS_PIN // - I2S_SD_PIN = I2S0_SDO_PIN // + I2S_SDO_PIN = I2S0_SDO_PIN + I2S_SDI_PIN = NoPin ) // SD card pins diff --git a/src/machine/board_itsybitsy-m0.go b/src/machine/board_itsybitsy-m0.go index 67ebdee904..0cc6cad313 100644 --- a/src/machine/board_itsybitsy-m0.go +++ b/src/machine/board_itsybitsy-m0.go @@ -89,7 +89,8 @@ var SPI1 = sercomSPIM5 // I2S pins const ( I2S_SCK_PIN = PA10 - I2S_SD_PIN = PA08 + I2S_SDO_PIN = PA08 + I2S_SDI_PIN = NoPin I2S_WS_PIN = NoPin // TODO: figure out what this is on ItsyBitsy M0. ) diff --git a/src/machine/board_p1am-100.go b/src/machine/board_p1am-100.go index d6fbdcb36a..f2a7d13f95 100644 --- a/src/machine/board_p1am-100.go +++ b/src/machine/board_p1am-100.go @@ -123,7 +123,8 @@ var ( // I2S pins const ( I2S_SCK_PIN Pin = D2 - I2S_SD_PIN Pin = A6 + I2S_SDO_PIN Pin = A6 + I2S_SDI_PIN = NoPin I2S_WS_PIN = D3 ) diff --git a/src/machine/board_qtpy.go b/src/machine/board_qtpy.go index 49bb9c97b5..e8a93e38d9 100644 --- a/src/machine/board_qtpy.go +++ b/src/machine/board_qtpy.go @@ -81,7 +81,8 @@ var ( // I2S pins const ( I2S_SCK_PIN = PA10 - I2S_SD_PIN = PA08 + I2S_SDO_PIN = PA08 + I2S_SDI_PIN = NoPin // TODO: figure out what this is on QT Py M0. I2S_WS_PIN = NoPin // TODO: figure out what this is on QT Py M0. ) diff --git a/src/machine/board_trinket.go b/src/machine/board_trinket.go index 2ce419a4ac..089eadbf09 100644 --- a/src/machine/board_trinket.go +++ b/src/machine/board_trinket.go @@ -67,7 +67,8 @@ var ( // I2S pins const ( I2S_SCK_PIN = PA10 - I2S_SD_PIN = PA08 + I2S_SDO_PIN = PA08 + I2S_SDI_PIN = NoPin // TODO: figure out what this is on Trinket M0. I2S_WS_PIN = NoPin // TODO: figure out what this is on Trinket M0. ) diff --git a/src/machine/board_wioterminal.go b/src/machine/board_wioterminal.go index 99a04d6ecc..6997120b93 100644 --- a/src/machine/board_wioterminal.go +++ b/src/machine/board_wioterminal.go @@ -376,6 +376,14 @@ var ( I2C1 = sercomI2CM3 ) +// I2S pins +const ( + I2S_SCK_PIN = BCM18 + I2S_SDO_PIN = BCM21 + I2S_SDI_PIN = BCM20 + I2S_WS_PIN = BCM19 +) + // SPI pins const ( SPI0_SCK_PIN = SCK // SCK: SERCOM5/PAD[1] diff --git a/src/machine/board_xiao.go b/src/machine/board_xiao.go index f0ecf068e3..5bbb34d686 100644 --- a/src/machine/board_xiao.go +++ b/src/machine/board_xiao.go @@ -82,7 +82,8 @@ var SPI0 = sercomSPIM0 // I2S pins const ( I2S_SCK_PIN = PA10 - I2S_SD_PIN = PA08 + I2S_SDO_PIN = PA08 + I2S_SDI_PIN = NoPin // TODO: figure out what this is on Xiao I2S_WS_PIN = NoPin // TODO: figure out what this is on Xiao ) diff --git a/src/machine/i2s.go b/src/machine/i2s.go index 8f5e309532..13dc80f61a 100644 --- a/src/machine/i2s.go +++ b/src/machine/i2s.go @@ -1,4 +1,4 @@ -//go:build sam +//go:build sam && atsamd21 // This is the definition for I2S bus functions. // Actual implementations if available for any given hardware @@ -9,6 +9,22 @@ package machine +import "errors" + +// If you are getting a compile error on this line please check to see you've +// correctly implemented the methods on the I2S type. They must match +// the interface method signatures type to type perfectly. +// If not implementing the I2S type please remove your target from the build tags +// at the top of this file. +var _ interface { + SetSampleFrequency(freq uint32) error + ReadMono(b []uint16) (int, error) + ReadStereo(b []uint32) (int, error) + WriteMono(b []uint16) (int, error) + WriteStereo(b []uint32) (int, error) + Enable(enabled bool) +} = (*I2S)(nil) + type I2SMode uint8 type I2SStandard uint8 type I2SClockSource uint8 @@ -18,6 +34,7 @@ const ( I2SModeSource I2SMode = iota I2SModeReceiver I2SModePDM + I2SModeSourceReceiver ) const ( @@ -39,11 +56,20 @@ const ( I2SDataFormat32bit = 32 ) +var ( + ErrInvalidSampleFrequency = errors.New("i2s: invalid sample frequency") +) + // All fields are optional and may not be required or used on a particular platform. type I2SConfig struct { - SCK Pin - WS Pin - SD Pin + // clock + SCK Pin + // word select + WS Pin + // data out + SDO Pin + // data in + SDI Pin Mode I2SMode Standard I2SStandard ClockSource I2SClockSource diff --git a/src/machine/machine_atsamd21.go b/src/machine/machine_atsamd21.go index fe67f45a32..3d0abc0fa8 100644 --- a/src/machine/machine_atsamd21.go +++ b/src/machine/machine_atsamd21.go @@ -926,23 +926,26 @@ func (i2c *I2C) readByte() byte { // I2S type I2S struct { - Bus *sam.I2S_Type + Bus *sam.I2S_Type + Frequency uint32 + DataFormat I2SDataFormat } var I2S0 = I2S{Bus: sam.I2S} // Configure is used to configure the I2S interface. You must call this // before you can use the I2S bus. -func (i2s I2S) Configure(config I2SConfig) { +func (i2s *I2S) Configure(config I2SConfig) error { // handle defaults if config.SCK == 0 { config.SCK = I2S_SCK_PIN config.WS = I2S_WS_PIN - config.SD = I2S_SD_PIN + config.SDO = I2S_SDO_PIN + config.SDI = I2S_SDI_PIN } if config.AudioFrequency == 0 { - config.AudioFrequency = 48000 + config.AudioFrequency = 44100 } if config.DataFormat == I2SDataFormatDefault { @@ -952,39 +955,17 @@ func (i2s I2S) Configure(config I2SConfig) { config.DataFormat = I2SDataFormat32bit } } + i2s.DataFormat = config.DataFormat // Turn on clock for I2S sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_I2S_) - // setting clock rate for sample. - division_factor := CPUFrequency() / (config.AudioFrequency * uint32(config.DataFormat)) - - // Switch Generic Clock Generator 3 to DFLL48M. - sam.GCLK.GENDIV.Set((sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_GENDIV_ID_Pos) | - (division_factor << sam.GCLK_GENDIV_DIV_Pos)) - waitForSync() - - sam.GCLK.GENCTRL.Set((sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_GENCTRL_ID_Pos) | - (sam.GCLK_GENCTRL_SRC_DFLL48M << sam.GCLK_GENCTRL_SRC_Pos) | - sam.GCLK_GENCTRL_IDC | - sam.GCLK_GENCTRL_GENEN) - waitForSync() - - // Use Generic Clock Generator 3 as source for I2S. - sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_I2S_0 << sam.GCLK_CLKCTRL_ID_Pos) | - (sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_CLKCTRL_GEN_Pos) | - sam.GCLK_CLKCTRL_CLKEN) - waitForSync() - - // reset the device - i2s.Bus.CTRLA.SetBits(sam.I2S_CTRLA_SWRST) - for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_SWRST) { + if err := i2s.SetSampleFrequency(config.AudioFrequency); err != nil { + return err } // disable device before continuing - for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_ENABLE) { - } - i2s.Bus.CTRLA.ClearBits(sam.I2S_CTRLA_ENABLE) + i2s.Enable(false) // setup clock if config.ClockSource == I2SClockSourceInternal { @@ -1067,19 +1048,25 @@ func (i2s I2S) Configure(config I2SConfig) { } // set serializer mode. - if config.Mode == I2SModePDM { + switch config.Mode { + case I2SModePDM: i2s.Bus.SERCTRL1.SetBits(sam.I2S_SERCTRL_SERMODE_PDM2) - } else { + case I2SModeSource: + i2s.Bus.SERCTRL1.SetBits(sam.I2S_SERCTRL_SERMODE_TX) + case I2SModeReceiver: i2s.Bus.SERCTRL1.SetBits(sam.I2S_SERCTRL_SERMODE_RX) } - // configure data pin - config.SD.Configure(PinConfig{Mode: PinCom}) + // configure data pins + if config.SDO != NoPin { + config.SDO.Configure(PinConfig{Mode: PinCom}) + } + if config.SDI != NoPin { + config.SDI.Configure(PinConfig{Mode: PinCom}) + } // re-enable - i2s.Bus.CTRLA.SetBits(sam.I2S_CTRLA_ENABLE) - for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_ENABLE) { - } + i2s.Enable(true) // enable i2s clock i2s.Bus.CTRLA.SetBits(sam.I2S_CTRLA_CKEN0) @@ -1090,11 +1077,23 @@ func (i2s I2S) Configure(config I2SConfig) { i2s.Bus.CTRLA.SetBits(sam.I2S_CTRLA_SEREN1) for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_SEREN1) { } + + return nil } -// Read data from the I2S bus into the provided slice. +// Read mono data from the I2S bus into the provided slice. // The I2S bus must already have been configured correctly. -func (i2s I2S) Read(p []uint32) (n int, err error) { +func (i2s *I2S) ReadMono(p []uint16) (n int, err error) { + return i2sRead(i2s, p) +} + +// Read stereo data from the I2S bus into the provided slice. +// The I2S bus must already have been configured correctly. +func (i2s *I2S) ReadStereo(p []uint32) (n int, err error) { + return i2sRead(i2s, p) +} + +func i2sRead[T uint16 | uint32](i2s *I2S, p []T) (int, error) { i := 0 for i = 0; i < len(p); i++ { // Wait until ready @@ -1105,7 +1104,7 @@ func (i2s I2S) Read(p []uint32) (n int, err error) { } // read data - p[i] = i2s.Bus.DATA1.Get() + p[i] = T(i2s.Bus.DATA1.Get()) // indicate read complete i2s.Bus.INTFLAG.Set(sam.I2S_INTFLAG_RXRDY1) @@ -1114,9 +1113,19 @@ func (i2s I2S) Read(p []uint32) (n int, err error) { return i, nil } -// Write data to the I2S bus from the provided slice. +// Write mono data to the I2S bus from the provided slice. // The I2S bus must already have been configured correctly. -func (i2s I2S) Write(p []uint32) (n int, err error) { +func (i2s *I2S) WriteMono(p []uint16) (n int, err error) { + return i2sWrite(i2s, p) +} + +// Write stereo data to the I2S bus from the provided slice. +// The I2S bus must already have been configured correctly. +func (i2s *I2S) WriteStereo(p []uint32) (n int, err error) { + return i2sWrite(i2s, p) +} + +func i2sWrite[T uint16 | uint32](i2s *I2S, p []T) (int, error) { i := 0 for i = 0; i < len(p); i++ { // Wait until ready @@ -1127,7 +1136,7 @@ func (i2s I2S) Write(p []uint32) (n int, err error) { } // write data - i2s.Bus.DATA1.Set(p[i]) + i2s.Bus.DATA1.Set(uint32(p[i])) // indicate write complete i2s.Bus.INTFLAG.Set(sam.I2S_INTFLAG_TXRDY1) @@ -1136,18 +1145,64 @@ func (i2s I2S) Write(p []uint32) (n int, err error) { return i, nil } -// Close the I2S bus. -func (i2s I2S) Close() error { - // Sync wait - for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_ENABLE) { +// SetSampleFrequency is used to set the sample frequency for the I2S bus. +func (i2s *I2S) SetSampleFrequency(freq uint32) error { + if freq == 0 { + return ErrInvalidSampleFrequency } - // disable I2S - i2s.Bus.CTRLA.ClearBits(sam.I2S_CTRLA_ENABLE) + if i2s.Frequency == freq { + return nil + } + + i2s.Frequency = freq + + // setting clock rate for sample. + division_factor := CPUFrequency() / (i2s.Frequency * uint32(i2s.DataFormat)) + + // Switch Generic Clock Generator 3 to DFLL48M. + sam.GCLK.GENDIV.Set((sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_GENDIV_ID_Pos) | + (division_factor << sam.GCLK_GENDIV_DIV_Pos)) + waitForSync() + + sam.GCLK.GENCTRL.Set((sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_GENCTRL_ID_Pos) | + (sam.GCLK_GENCTRL_SRC_DFLL48M << sam.GCLK_GENCTRL_SRC_Pos) | + sam.GCLK_GENCTRL_IDC | + sam.GCLK_GENCTRL_GENEN) + waitForSync() + + // Use Generic Clock Generator 3 as source for I2S. + sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_I2S_0 << sam.GCLK_CLKCTRL_ID_Pos) | + (sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_CLKCTRL_GEN_Pos) | + sam.GCLK_CLKCTRL_CLKEN) + waitForSync() + + // reset the device + i2s.Bus.CTRLA.SetBits(sam.I2S_CTRLA_SWRST) + for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_SWRST) { + } return nil } +// Enabled is used to enable or disable the I2S bus. +func (i2s *I2S) Enable(enabled bool) { + if enabled { + i2s.Bus.CTRLA.SetBits(sam.I2S_CTRLA_ENABLE) + for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_ENABLE) { + } + + return + } + + // disable + for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_ENABLE) { + } + i2s.Bus.CTRLA.ClearBits(sam.I2S_CTRLA_ENABLE) + + return +} + func waitForSync() { for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) { } From 0d13e61d0cbe7aa82678a6dec7dda0115db82397 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Tue, 17 Dec 2024 17:31:21 +0100 Subject: [PATCH 325/444] fix: add build tags to ensure that tkey target has stubs for runtime/interrupt package Signed-off-by: deadprogram --- src/runtime/interrupt/interrupt_none.go | 2 +- src/runtime/interrupt/interrupt_tinygoriscv.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runtime/interrupt/interrupt_none.go b/src/runtime/interrupt/interrupt_none.go index 2f4aae6c8a..ea8bdb68c6 100644 --- a/src/runtime/interrupt/interrupt_none.go +++ b/src/runtime/interrupt/interrupt_none.go @@ -1,4 +1,4 @@ -//go:build !baremetal +//go:build !baremetal || tkey package interrupt diff --git a/src/runtime/interrupt/interrupt_tinygoriscv.go b/src/runtime/interrupt/interrupt_tinygoriscv.go index fd21afcda9..558e67150c 100644 --- a/src/runtime/interrupt/interrupt_tinygoriscv.go +++ b/src/runtime/interrupt/interrupt_tinygoriscv.go @@ -1,4 +1,4 @@ -//go:build tinygo.riscv +//go:build tinygo.riscv && !tkey package interrupt From 37f35f8c910b05e2040433448546f41b34535b32 Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Wed, 18 Dec 2024 15:36:30 -0300 Subject: [PATCH 326/444] Add RP2350 support (#4459) machine/rp2350: add support * add linker scripts for rp2350 * add bootloader * begin melding rp2040 and rp2350 APIs * add UART * add rp2350 boot patching * Fix RP2350 memory layout (#4626) * Remove rp2040-style second stage bootloader. * Add 'minimum viable' IMAGE_DEF embedded block * Create a pico2 specific target * Implement rp2350 init, clock, and uart support * Merge rp2 reset code back together * Separate chip-specific clock definitions * Clear pad isolation bit on rp2350 * Init UART in rp2350 runtime * Correct usb/serial initialization order * Implement jump-to-bootloader * test: add pico2 to smoketests --------- Signed-off-by: deadprogram Co-authored-by: Matthew Mets Co-authored-by: Matt Mets Co-authored-by: deadprogram --- .gitignore | 4 + GNUmakefile | 2 + builder/build.go | 32 +- compileopts/target.go | 1 + src/machine/board_pico2.go | 88 ++++ .../{machine_rp2040.go => machine_rp2.go} | 68 +++- src/machine/machine_rp2040_i2c.go | 21 - src/machine/machine_rp2040_rtc.go | 2 +- src/machine/machine_rp2350_rom.go | 153 +++++++ src/machine/machine_rp2350_usb.go | 380 ++++++++++++++++++ src/machine/machine_rp2_2040.go | 199 +++++++++ src/machine/machine_rp2_2350.go | 217 ++++++++++ ...chine_rp2040_adc.go => machine_rp2_adc.go} | 2 +- ...rp2040_clocks.go => machine_rp2_clocks.go} | 91 ++--- ...ine_rp2040_gpio.go => machine_rp2_gpio.go} | 135 +------ ...ine_rp2040_pins.go => machine_rp2_pins.go} | 2 +- ...chine_rp2040_pll.go => machine_rp2_pll.go} | 2 +- ...rp2040_resets.go => machine_rp2_resets.go} | 22 +- ...ine_rp2040_sync.go => machine_rp2_sync.go} | 35 +- ...e_rp2040_timer.go => machine_rp2_timer.go} | 8 +- ...ine_rp2040_uart.go => machine_rp2_uart.go} | 4 +- ...40_watchdog.go => machine_rp2_watchdog.go} | 10 +- ...ine_rp2040_xosc.go => machine_rp2_xosc.go} | 4 +- src/machine/uart.go | 2 +- src/machine/usb.go | 2 +- src/machine/watchdog.go | 2 +- src/runtime/runtime_rp2350.go | 88 ++++ targets/arm.ld | 1 + targets/pico2.json | 7 + targets/rp2350.json | 19 + targets/rp2350.ld | 23 ++ targets/rp2350_embedded_block.s | 10 + tools/gen-device-svd/gen-device-svd.go | 7 +- 33 files changed, 1349 insertions(+), 294 deletions(-) create mode 100644 src/machine/board_pico2.go rename src/machine/{machine_rp2040.go => machine_rp2.go} (69%) create mode 100644 src/machine/machine_rp2350_rom.go create mode 100644 src/machine/machine_rp2350_usb.go create mode 100644 src/machine/machine_rp2_2040.go create mode 100644 src/machine/machine_rp2_2350.go rename src/machine/{machine_rp2040_adc.go => machine_rp2_adc.go} (99%) rename src/machine/{machine_rp2040_clocks.go => machine_rp2_clocks.go} (68%) rename src/machine/{machine_rp2040_gpio.go => machine_rp2_gpio.go} (66%) rename src/machine/{machine_rp2040_pins.go => machine_rp2_pins.go} (85%) rename src/machine/{machine_rp2040_pll.go => machine_rp2_pll.go} (98%) rename src/machine/{machine_rp2040_resets.go => machine_rp2_resets.go} (51%) rename src/machine/{machine_rp2040_sync.go => machine_rp2_sync.go} (62%) rename src/machine/{machine_rp2040_timer.go => machine_rp2_timer.go} (95%) rename src/machine/{machine_rp2040_uart.go => machine_rp2_uart.go} (97%) rename src/machine/{machine_rp2040_watchdog.go => machine_rp2_watchdog.go} (83%) rename src/machine/{machine_rp2040_xosc.go => machine_rp2_xosc.go} (93%) create mode 100644 src/runtime/runtime_rp2350.go create mode 100644 targets/pico2.json create mode 100644 targets/rp2350.json create mode 100644 targets/rp2350.ld create mode 100644 targets/rp2350_embedded_block.s diff --git a/.gitignore b/.gitignore index a2bc8dc444..2761d1fcc7 100644 --- a/.gitignore +++ b/.gitignore @@ -37,5 +37,9 @@ test.exe test.gba test.hex test.nro +test.uf2 test.wasm wasm.wasm + +*.uf2 +*.elf \ No newline at end of file diff --git a/GNUmakefile b/GNUmakefile index 254f734940..273db5ed86 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -741,6 +741,8 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=thumby examples/echo @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=pico2 examples/blinky1 + @$(MD5SUM) test.hex # test pwm $(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm @$(MD5SUM) test.hex diff --git a/builder/build.go b/builder/build.go index 57b67ed455..d1d2c4be63 100644 --- a/builder/build.go +++ b/builder/build.go @@ -822,6 +822,12 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe return fmt.Errorf("could not modify stack sizes: %w", err) } } + + // Apply patches of bootloader in the order they appear. + if len(config.Target.BootPatches) > 0 { + err = applyPatches(result.Executable, config.Target.BootPatches) + } + if config.RP2040BootPatch() { // Patch the second stage bootloader CRC into the .boot2 section err = patchRP2040BootCRC(result.Executable) @@ -1434,6 +1440,23 @@ func printStacks(calculatedStacks []string, stackSizes map[string]functionStackS } } +func applyPatches(executable string, bootPatches []string) (err error) { + for _, patch := range bootPatches { + switch patch { + case "rp2040": + err = patchRP2040BootCRC(executable) + // case "rp2350": + // err = patchRP2350BootIMAGE_DEF(executable) + default: + err = errors.New("undefined boot patch name") + } + if err != nil { + return fmt.Errorf("apply boot patch %q: %w", patch, err) + } + } + return nil +} + // RP2040 second stage bootloader CRC32 calculation // // Spec: https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf @@ -1445,7 +1468,7 @@ func patchRP2040BootCRC(executable string) error { } if len(bytes) != 256 { - return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes") + return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes, got %d", len(bytes)) } // From the 'official' RP2040 checksum script: @@ -1484,3 +1507,10 @@ func lock(path string) func() { return func() { flock.Close() } } + +func b2u8(b bool) uint8 { + if b { + return 1 + } + return 0 +} diff --git a/compileopts/target.go b/compileopts/target.go index f60ee30972..fe864fe9db 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -46,6 +46,7 @@ type TargetSpec struct { LinkerScript string `json:"linkerscript,omitempty"` ExtraFiles []string `json:"extra-files,omitempty"` RP2040BootPatch *bool `json:"rp2040-boot-patch,omitempty"` // Patch RP2040 2nd stage bootloader checksum + BootPatches []string `json:"boot-patches,omitempty"` // Bootloader patches to be applied in the order they appear. Emulator string `json:"emulator,omitempty"` FlashCommand string `json:"flash-command,omitempty"` GDB []string `json:"gdb,omitempty"` diff --git a/src/machine/board_pico2.go b/src/machine/board_pico2.go new file mode 100644 index 0000000000..327c542fbc --- /dev/null +++ b/src/machine/board_pico2.go @@ -0,0 +1,88 @@ +//go:build pico2 + +package machine + +// GPIO pins +const ( + GP0 Pin = GPIO0 + GP1 Pin = GPIO1 + GP2 Pin = GPIO2 + GP3 Pin = GPIO3 + GP4 Pin = GPIO4 + GP5 Pin = GPIO5 + GP6 Pin = GPIO6 + GP7 Pin = GPIO7 + GP8 Pin = GPIO8 + GP9 Pin = GPIO9 + GP10 Pin = GPIO10 + GP11 Pin = GPIO11 + GP12 Pin = GPIO12 + GP13 Pin = GPIO13 + GP14 Pin = GPIO14 + GP15 Pin = GPIO15 + GP16 Pin = GPIO16 + GP17 Pin = GPIO17 + GP18 Pin = GPIO18 + GP19 Pin = GPIO19 + GP20 Pin = GPIO20 + GP21 Pin = GPIO21 + GP22 Pin = GPIO22 + GP26 Pin = GPIO26 + GP27 Pin = GPIO27 + GP28 Pin = GPIO28 + + // Onboard LED + LED Pin = GPIO25 + + // Onboard crystal oscillator frequency, in MHz. + xoscFreq = 12 // MHz +) + +// I2C Default pins on Raspberry Pico. +const ( + I2C0_SDA_PIN = GP4 + I2C0_SCL_PIN = GP5 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO18 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO19 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO16 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO10 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO11 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO12 // Rx +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO8 + UART1_RX_PIN = GPIO9 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Pico2" + usb_STRING_MANUFACTURER = "Raspberry Pi" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000A +) diff --git a/src/machine/machine_rp2040.go b/src/machine/machine_rp2.go similarity index 69% rename from src/machine/machine_rp2040.go rename to src/machine/machine_rp2.go index 45f9f510f5..6c09fedfea 100644 --- a/src/machine/machine_rp2040.go +++ b/src/machine/machine_rp2.go @@ -1,38 +1,55 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine import ( "device/rp" + "runtime/interrupt" "runtime/volatile" "unsafe" ) const deviceName = rp.Device +const ( + // Number of spin locks available + // Note: On RP2350, most spinlocks are unusable due to Errata 2 + _NUMSPINLOCKS = 32 + _PICO_SPINLOCK_ID_IRQ = 9 +) + +// UART on the RP2040 +var ( + UART0 = &_UART0 + _UART0 = UART{ + Buffer: NewRingBuffer(), + Bus: rp.UART0, + } + + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: rp.UART1, + } +) + +func init() { + UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) + UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) +} + //go:linkname machineInit runtime.machineInit func machineInit() { // Reset all peripherals to put system into a known state, // except for QSPI pads and the XIP IO bank, as this is fatal if running from flash // and the PLLs, as this is fatal if clock muxing has not been reset on this boot // and USB, syscfg, as this disturbs USB-to-SWD on core 1 - bits := ^uint32(rp.RESETS_RESET_IO_QSPI | - rp.RESETS_RESET_PADS_QSPI | - rp.RESETS_RESET_PLL_USB | - rp.RESETS_RESET_USBCTRL | - rp.RESETS_RESET_SYSCFG | - rp.RESETS_RESET_PLL_SYS) + bits := ^uint32(initDontReset) resetBlock(bits) // Remove reset from peripherals which are clocked only by clkSys and // clkRef. Other peripherals stay in reset until we've configured clocks. - bits = ^uint32(rp.RESETS_RESET_ADC | - rp.RESETS_RESET_RTC | - rp.RESETS_RESET_SPI0 | - rp.RESETS_RESET_SPI1 | - rp.RESETS_RESET_UART0 | - rp.RESETS_RESET_UART1 | - rp.RESETS_RESET_USBCTRL) + bits = ^uint32(initUnreset) unresetBlockWait(bits) clocks.init() @@ -94,4 +111,25 @@ const ( ) // DMA channels usable on the RP2040. -var dmaChannels = (*[12]dmaChannel)(unsafe.Pointer(rp.DMA)) +var dmaChannels = (*[12 + 4*rp2350ExtraReg]dmaChannel)(unsafe.Pointer(rp.DMA)) + +//go:inline +func boolToBit(a bool) uint32 { + if a { + return 1 + } + return 0 +} + +//go:inline +func u32max(a, b uint32) uint32 { + if a > b { + return a + } + return b +} + +//go:inline +func isReservedI2CAddr(addr uint8) bool { + return (addr&0x78) == 0 || (addr&0x78) == 0x78 +} diff --git a/src/machine/machine_rp2040_i2c.go b/src/machine/machine_rp2040_i2c.go index b7dc63d2b2..f34aa259f5 100644 --- a/src/machine/machine_rp2040_i2c.go +++ b/src/machine/machine_rp2040_i2c.go @@ -631,24 +631,3 @@ func (b i2cAbortError) Reasons() (reasons []string) { } return reasons } - -//go:inline -func boolToBit(a bool) uint32 { - if a { - return 1 - } - return 0 -} - -//go:inline -func u32max(a, b uint32) uint32 { - if a > b { - return a - } - return b -} - -//go:inline -func isReservedI2CAddr(addr uint8) bool { - return (addr&0x78) == 0 || (addr&0x78) == 0x78 -} diff --git a/src/machine/machine_rp2040_rtc.go b/src/machine/machine_rp2040_rtc.go index 192e187c0a..072432fc02 100644 --- a/src/machine/machine_rp2040_rtc.go +++ b/src/machine/machine_rp2040_rtc.go @@ -97,7 +97,7 @@ func toAlarmTime(delay uint32) rtcTime { func (rtc *rtcType) setDivider() { // Get clk_rtc freq and make sure it is running - rtcFreq := configuredFreq[clkRTC] + rtcFreq := configuredFreq[ClkRTC] if rtcFreq == 0 { panic("can not set RTC divider, clock is not running") } diff --git a/src/machine/machine_rp2350_rom.go b/src/machine/machine_rp2350_rom.go new file mode 100644 index 0000000000..4f3d4762a8 --- /dev/null +++ b/src/machine/machine_rp2350_rom.go @@ -0,0 +1,153 @@ +//go:build tinygo && rp2350 + +package machine + +import () + +/* +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned long uint32_t; +typedef unsigned long size_t; +typedef unsigned long uintptr_t; + +#define false 0 +#define true 1 +typedef int bool; + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/pico_platform_compiler/include/pico/platform/compiler.h + +#define pico_default_asm_volatile(...) __asm volatile (".syntax unified\n" __VA_ARGS__) + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2350/pico_platform/include/pico/platform.h + +static bool pico_processor_state_is_nonsecure(void) { +// // todo add a define to disable NS checking at all? +// // IDAU-Exempt addresses return S=1 when tested in the Secure state, +// // whereas executing a tt in the NonSecure state will always return S=0. +// uint32_t tt; +// pico_default_asm_volatile ( +// "movs %0, #0\n" +// "tt %0, %0\n" +// : "=r" (tt) : : "cc" +// ); +// return !(tt & (1u << 22)); + + return false; +} + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/pico_bootrom/include/pico/bootrom_constants.h + +// RP2040 & RP2350 +#define ROM_DATA_SOFTWARE_GIT_REVISION ROM_TABLE_CODE('G', 'R') +#define ROM_FUNC_FLASH_ENTER_CMD_XIP ROM_TABLE_CODE('C', 'X') +#define ROM_FUNC_FLASH_EXIT_XIP ROM_TABLE_CODE('E', 'X') +#define ROM_FUNC_FLASH_FLUSH_CACHE ROM_TABLE_CODE('F', 'C') +#define ROM_FUNC_CONNECT_INTERNAL_FLASH ROM_TABLE_CODE('I', 'F') +#define ROM_FUNC_FLASH_RANGE_ERASE ROM_TABLE_CODE('R', 'E') +#define ROM_FUNC_FLASH_RANGE_PROGRAM ROM_TABLE_CODE('R', 'P') + +// RP2350 only +#define ROM_FUNC_PICK_AB_PARTITION ROM_TABLE_CODE('A', 'B') +#define ROM_FUNC_CHAIN_IMAGE ROM_TABLE_CODE('C', 'I') +#define ROM_FUNC_EXPLICIT_BUY ROM_TABLE_CODE('E', 'B') +#define ROM_FUNC_FLASH_RUNTIME_TO_STORAGE_ADDR ROM_TABLE_CODE('F', 'A') +#define ROM_DATA_FLASH_DEVINFO16_PTR ROM_TABLE_CODE('F', 'D') +#define ROM_FUNC_FLASH_OP ROM_TABLE_CODE('F', 'O') +#define ROM_FUNC_GET_B_PARTITION ROM_TABLE_CODE('G', 'B') +#define ROM_FUNC_GET_PARTITION_TABLE_INFO ROM_TABLE_CODE('G', 'P') +#define ROM_FUNC_GET_SYS_INFO ROM_TABLE_CODE('G', 'S') +#define ROM_FUNC_GET_UF2_TARGET_PARTITION ROM_TABLE_CODE('G', 'U') +#define ROM_FUNC_LOAD_PARTITION_TABLE ROM_TABLE_CODE('L', 'P') +#define ROM_FUNC_OTP_ACCESS ROM_TABLE_CODE('O', 'A') +#define ROM_DATA_PARTITION_TABLE_PTR ROM_TABLE_CODE('P', 'T') +#define ROM_FUNC_FLASH_RESET_ADDRESS_TRANS ROM_TABLE_CODE('R', 'A') +#define ROM_FUNC_REBOOT ROM_TABLE_CODE('R', 'B') +#define ROM_FUNC_SET_ROM_CALLBACK ROM_TABLE_CODE('R', 'C') +#define ROM_FUNC_SECURE_CALL ROM_TABLE_CODE('S', 'C') +#define ROM_FUNC_SET_NS_API_PERMISSION ROM_TABLE_CODE('S', 'P') +#define ROM_FUNC_BOOTROM_STATE_RESET ROM_TABLE_CODE('S', 'R') +#define ROM_FUNC_SET_BOOTROM_STACK ROM_TABLE_CODE('S', 'S') +#define ROM_DATA_SAVED_XIP_SETUP_FUNC_PTR ROM_TABLE_CODE('X', 'F') +#define ROM_FUNC_FLASH_SELECT_XIP_READ_MODE ROM_TABLE_CODE('X', 'M') +#define ROM_FUNC_VALIDATE_NS_BUFFER ROM_TABLE_CODE('V', 'B') + +#define BOOTSEL_FLAG_GPIO_PIN_SPECIFIED 0x20 + +#define BOOTROM_FUNC_TABLE_OFFSET 0x14 + +// todo remove this (or #ifdef it for A1/A2) +#define BOOTROM_IS_A2() ((*(volatile uint8_t *)0x13) == 2) +#define BOOTROM_WELL_KNOWN_PTR_SIZE (BOOTROM_IS_A2() ? 2 : 4) + +#define BOOTROM_VTABLE_OFFSET 0x00 +#define BOOTROM_TABLE_LOOKUP_OFFSET (BOOTROM_FUNC_TABLE_OFFSET + BOOTROM_WELL_KNOWN_PTR_SIZE) + +// https://github.com/raspberrypi/pico-sdk +// src/common/boot_picoboot_headers/include/boot/picoboot_constants.h + +// values 0-7 are secure/non-secure +#define REBOOT2_FLAG_REBOOT_TYPE_NORMAL 0x0 // param0 = diagnostic partition +#define REBOOT2_FLAG_REBOOT_TYPE_BOOTSEL 0x2 // param0 = bootsel_flags, param1 = gpio_config +#define REBOOT2_FLAG_REBOOT_TYPE_RAM_IMAGE 0x3 // param0 = image_base, param1 = image_end +#define REBOOT2_FLAG_REBOOT_TYPE_FLASH_UPDATE 0x4 // param0 = update_base + +#define REBOOT2_FLAG_NO_RETURN_ON_SUCCESS 0x100 + +#define RT_FLAG_FUNC_ARM_SEC 0x0004 +#define RT_FLAG_FUNC_ARM_NONSEC 0x0010 + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/pico_bootrom/include/pico/bootrom.h + +#define ROM_TABLE_CODE(c1, c2) ((c1) | ((c2) << 8)) + +typedef void *(*rom_table_lookup_fn)(uint32_t code, uint32_t mask); + +__attribute__((always_inline)) +static void *rom_func_lookup_inline(uint32_t code) { + rom_table_lookup_fn rom_table_lookup = (rom_table_lookup_fn) (uintptr_t)*(uint16_t*)(BOOTROM_TABLE_LOOKUP_OFFSET); + if (pico_processor_state_is_nonsecure()) { + return rom_table_lookup(code, RT_FLAG_FUNC_ARM_NONSEC); + } else { + return rom_table_lookup(code, RT_FLAG_FUNC_ARM_SEC); + } +} + + +typedef int (*rom_reboot_fn)(uint32_t flags, uint32_t delay_ms, uint32_t p0, uint32_t p1); + +__attribute__((always_inline)) +int rom_reboot(uint32_t flags, uint32_t delay_ms, uint32_t p0, uint32_t p1) { + rom_reboot_fn func = (rom_reboot_fn) rom_func_lookup_inline(ROM_FUNC_REBOOT); + return func(flags, delay_ms, p0, p1); +} + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/pico_bootrom/bootrom.c + + +void reset_usb_boot(uint32_t usb_activity_gpio_pin_mask, uint32_t disable_interface_mask) { + uint32_t flags = disable_interface_mask; + if (usb_activity_gpio_pin_mask) { + flags |= BOOTSEL_FLAG_GPIO_PIN_SPECIFIED; + // the parameter is actually the gpio number, but we only care if BOOTSEL_FLAG_GPIO_PIN_SPECIFIED + usb_activity_gpio_pin_mask = (uint32_t)__builtin_ctz(usb_activity_gpio_pin_mask); + } + rom_reboot(REBOOT2_FLAG_REBOOT_TYPE_BOOTSEL | REBOOT2_FLAG_NO_RETURN_ON_SUCCESS, 10, flags, usb_activity_gpio_pin_mask); + __builtin_unreachable(); +} + + +*/ +import "C" + +func enterBootloader() { + C.reset_usb_boot(0, 0) +} diff --git a/src/machine/machine_rp2350_usb.go b/src/machine/machine_rp2350_usb.go new file mode 100644 index 0000000000..9106b50f94 --- /dev/null +++ b/src/machine/machine_rp2350_usb.go @@ -0,0 +1,380 @@ +//go:build rp2350 + +package machine + +import ( + "device/rp" + "machine/usb" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +var ( + sendOnEP0DATADONE struct { + offset int + data []byte + pid uint32 + } +) + +// Configure the USB peripheral. The config is here for compatibility with the UART interface. +func (dev *USBDevice) Configure(config UARTConfig) { + // Reset usb controller + resetBlock(rp.RESETS_RESET_USBCTRL) + unresetBlockWait(rp.RESETS_RESET_USBCTRL) + + // Clear any previous state in dpram just in case + usbDPSRAM.clear() + + // Enable USB interrupt at processor + rp.USB.INTE.Set(0) + intr := interrupt.New(rp.IRQ_USBCTRL_IRQ, handleUSBIRQ) + intr.SetPriority(0x00) + intr.Enable() + irqSet(rp.IRQ_USBCTRL_IRQ, true) + + // Mux the controller to the onboard usb phy + rp.USB.USB_MUXING.Set(rp.USB_USB_MUXING_TO_PHY | rp.USB_USB_MUXING_SOFTCON) + + // Force VBUS detect so the device thinks it is plugged into a host + rp.USB.USB_PWR.Set(rp.USB_USB_PWR_VBUS_DETECT | rp.USB_USB_PWR_VBUS_DETECT_OVERRIDE_EN) + + // Enable the USB controller in device mode. + rp.USB.MAIN_CTRL.Set(rp.USB_MAIN_CTRL_CONTROLLER_EN) + + // Enable an interrupt per EP0 transaction + rp.USB.SIE_CTRL.Set(rp.USB_SIE_CTRL_EP0_INT_1BUF) + + // Enable interrupts for when a buffer is done, when the bus is reset, + // and when a setup packet is received + rp.USB.INTE.Set(rp.USB_INTE_BUFF_STATUS | + rp.USB_INTE_BUS_RESET | + rp.USB_INTE_SETUP_REQ) + + // Present full speed device by enabling pull up on DP + rp.USB.SIE_CTRL.SetBits(rp.USB_SIE_CTRL_PULLUP_EN) + + // 12.7.2 Disable phy isolation + rp.USB.SetMAIN_CTRL_PHY_ISO(0x0) +} + +func handleUSBIRQ(intr interrupt.Interrupt) { + status := rp.USB.INTS.Get() + + // Setup packet received + if (status & rp.USB_INTS_SETUP_REQ) > 0 { + rp.USB.SIE_STATUS.Set(rp.USB_SIE_STATUS_SETUP_REC) + setup := usb.NewSetup(usbDPSRAM.setupBytes()) + + ok := false + if (setup.BmRequestType & usb.REQUEST_TYPE) == usb.REQUEST_STANDARD { + // Standard Requests + ok = handleStandardSetup(setup) + } else { + // Class Interface Requests + if setup.WIndex < uint16(len(usbSetupHandler)) && usbSetupHandler[setup.WIndex] != nil { + ok = usbSetupHandler[setup.WIndex](setup) + } + } + + if !ok { + // Stall endpoint? + sendStallViaEPIn(0) + } + + } + + // Buffer status, one or more buffers have completed + if (status & rp.USB_INTS_BUFF_STATUS) > 0 { + if sendOnEP0DATADONE.offset > 0 { + ep := uint32(0) + data := sendOnEP0DATADONE.data + count := len(data) - sendOnEP0DATADONE.offset + if ep == 0 && count > usb.EndpointPacketSize { + count = usb.EndpointPacketSize + } + + sendViaEPIn(ep, data[sendOnEP0DATADONE.offset:], count) + sendOnEP0DATADONE.offset += count + if sendOnEP0DATADONE.offset == len(data) { + sendOnEP0DATADONE.offset = 0 + } + } + + s2 := rp.USB.BUFF_STATUS.Get() + + // OUT (PC -> rp2040) + for i := 0; i < 16; i++ { + if s2&(1<<(i*2+1)) > 0 { + buf := handleEndpointRx(uint32(i)) + if usbRxHandler[i] != nil { + usbRxHandler[i](buf) + } + handleEndpointRxComplete(uint32(i)) + } + } + + // IN (rp2040 -> PC) + for i := 0; i < 16; i++ { + if s2&(1<<(i*2)) > 0 { + if usbTxHandler[i] != nil { + usbTxHandler[i]() + } + } + } + + rp.USB.BUFF_STATUS.Set(s2) + } + + // Bus is reset + if (status & rp.USB_INTS_BUS_RESET) > 0 { + rp.USB.SIE_STATUS.Set(rp.USB_SIE_STATUS_BUS_RESET) + //fixRP2040UsbDeviceEnumeration() + + rp.USB.ADDR_ENDP.Set(0) + initEndpoint(0, usb.ENDPOINT_TYPE_CONTROL) + } +} + +func initEndpoint(ep, config uint32) { + val := uint32(usbEpControlEnable) | uint32(usbEpControlInterruptPerBuff) + offset := ep*2*USBBufferLen + 0x100 + val |= offset + + switch config { + case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn: + val |= usbEpControlEndpointTypeInterrupt + usbDPSRAM.EPxControl[ep].In.Set(val) + + case usb.ENDPOINT_TYPE_BULK | usb.EndpointOut: + val |= usbEpControlEndpointTypeBulk + usbDPSRAM.EPxControl[ep].Out.Set(val) + usbDPSRAM.EPxBufferControl[ep].Out.Set(USBBufferLen & usbBuf0CtrlLenMask) + usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + + case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut: + val |= usbEpControlEndpointTypeInterrupt + usbDPSRAM.EPxControl[ep].Out.Set(val) + usbDPSRAM.EPxBufferControl[ep].Out.Set(USBBufferLen & usbBuf0CtrlLenMask) + usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + + case usb.ENDPOINT_TYPE_BULK | usb.EndpointIn: + val |= usbEpControlEndpointTypeBulk + usbDPSRAM.EPxControl[ep].In.Set(val) + + case usb.ENDPOINT_TYPE_CONTROL: + val |= usbEpControlEndpointTypeControl + usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBuf0CtrlData1Pid) + usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + + } +} + +func handleUSBSetAddress(setup usb.Setup) bool { + sendUSBPacket(0, []byte{}, 0) + + // last, set the device address to that requested by host + // wait for transfer to complete + timeout := 3000 + rp.USB.SIE_STATUS.Set(rp.USB_SIE_STATUS_ACK_REC) + for (rp.USB.SIE_STATUS.Get() & rp.USB_SIE_STATUS_ACK_REC) == 0 { + timeout-- + if timeout == 0 { + return true + } + } + + rp.USB.ADDR_ENDP.Set(uint32(setup.WValueL) & rp.USB_ADDR_ENDP_ADDRESS_Msk) + + return true +} + +// SendUSBInPacket sends a packet for USB (interrupt in / bulk in). +func SendUSBInPacket(ep uint32, data []byte) bool { + sendUSBPacket(ep, data, 0) + return true +} + +//go:noinline +func sendUSBPacket(ep uint32, data []byte, maxsize uint16) { + count := len(data) + if 0 < int(maxsize) && int(maxsize) < count { + count = int(maxsize) + } + + if ep == 0 { + if count > usb.EndpointPacketSize { + count = usb.EndpointPacketSize + + sendOnEP0DATADONE.offset = count + sendOnEP0DATADONE.data = data + } else { + sendOnEP0DATADONE.offset = 0 + } + epXdata0[ep] = true + } + + sendViaEPIn(ep, data, count) +} + +func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) { + var b [cdcLineInfoSize]byte + ep := 0 + + for !usbDPSRAM.EPxBufferControl[ep].Out.HasBits(usbBuf0CtrlFull) { + // TODO: timeout + } + + ctrl := usbDPSRAM.EPxBufferControl[ep].Out.Get() + usbDPSRAM.EPxBufferControl[ep].Out.Set(USBBufferLen & usbBuf0CtrlLenMask) + sz := ctrl & usbBuf0CtrlLenMask + + copy(b[:], usbDPSRAM.EPxBuffer[ep].Buffer0[:sz]) + + usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid) + usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + + return b, nil +} + +func handleEndpointRx(ep uint32) []byte { + ctrl := usbDPSRAM.EPxBufferControl[ep].Out.Get() + usbDPSRAM.EPxBufferControl[ep].Out.Set(USBBufferLen & usbBuf0CtrlLenMask) + sz := ctrl & usbBuf0CtrlLenMask + + return usbDPSRAM.EPxBuffer[ep].Buffer0[:sz] +} + +func handleEndpointRxComplete(ep uint32) { + epXdata0[ep] = !epXdata0[ep] + if epXdata0[ep] || ep == 0 { + usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid) + } + + usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) +} + +func SendZlp() { + sendUSBPacket(0, []byte{}, 0) +} + +func sendViaEPIn(ep uint32, data []byte, count int) { + // Prepare buffer control register value + val := uint32(count) | usbBuf0CtrlAvail + + // DATA0 or DATA1 + epXdata0[ep&0x7F] = !epXdata0[ep&0x7F] + if !epXdata0[ep&0x7F] { + val |= usbBuf0CtrlData1Pid + } + + // Mark as full + val |= usbBuf0CtrlFull + + copy(usbDPSRAM.EPxBuffer[ep&0x7F].Buffer0[:], data[:count]) + usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) +} + +func sendStallViaEPIn(ep uint32) { + // Prepare buffer control register value + if ep == 0 { + rp.USB.EP_STALL_ARM.Set(rp.USB_EP_STALL_ARM_EP0_IN) + } + val := uint32(usbBuf0CtrlFull) + usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) + val |= uint32(usbBuf0CtrlStall) + usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) +} + +type USBDPSRAM struct { + // Note that EPxControl[0] is not EP0Control but 8-byte setup data. + EPxControl [16]USBEndpointControlRegister + + EPxBufferControl [16]USBBufferControlRegister + + EPxBuffer [16]USBBuffer +} + +type USBEndpointControlRegister struct { + In volatile.Register32 + Out volatile.Register32 +} +type USBBufferControlRegister struct { + In volatile.Register32 + Out volatile.Register32 +} + +type USBBuffer struct { + Buffer0 [USBBufferLen]byte + Buffer1 [USBBufferLen]byte +} + +var ( + usbDPSRAM = (*USBDPSRAM)(unsafe.Pointer(uintptr(0x50100000))) + epXdata0 [16]bool + setupBytes [8]byte +) + +func (d *USBDPSRAM) setupBytes() []byte { + + data := d.EPxControl[usb.CONTROL_ENDPOINT].In.Get() + setupBytes[0] = byte(data) + setupBytes[1] = byte(data >> 8) + setupBytes[2] = byte(data >> 16) + setupBytes[3] = byte(data >> 24) + + data = d.EPxControl[usb.CONTROL_ENDPOINT].Out.Get() + setupBytes[4] = byte(data) + setupBytes[5] = byte(data >> 8) + setupBytes[6] = byte(data >> 16) + setupBytes[7] = byte(data >> 24) + + return setupBytes[:] +} + +func (d *USBDPSRAM) clear() { + for i := 0; i < len(d.EPxControl); i++ { + d.EPxControl[i].In.Set(0) + d.EPxControl[i].Out.Set(0) + d.EPxBufferControl[i].In.Set(0) + d.EPxBufferControl[i].Out.Set(0) + } +} + +const ( + // DPRAM : Endpoint control register + usbEpControlEnable = 0x80000000 + usbEpControlDoubleBuffered = 0x40000000 + usbEpControlInterruptPerBuff = 0x20000000 + usbEpControlInterruptPerDoubleBuff = 0x10000000 + usbEpControlEndpointType = 0x0c000000 + usbEpControlInterruptOnStall = 0x00020000 + usbEpControlInterruptOnNak = 0x00010000 + usbEpControlBufferAddress = 0x0000ffff + + usbEpControlEndpointTypeControl = 0x00000000 + usbEpControlEndpointTypeISO = 0x04000000 + usbEpControlEndpointTypeBulk = 0x08000000 + usbEpControlEndpointTypeInterrupt = 0x0c000000 + + // Endpoint buffer control bits + usbBuf1CtrlFull = 0x80000000 + usbBuf1CtrlLast = 0x40000000 + usbBuf1CtrlData0Pid = 0x20000000 + usbBuf1CtrlData1Pid = 0x00000000 + usbBuf1CtrlSel = 0x10000000 + usbBuf1CtrlStall = 0x08000000 + usbBuf1CtrlAvail = 0x04000000 + usbBuf1CtrlLenMask = 0x03FF0000 + usbBuf0CtrlFull = 0x00008000 + usbBuf0CtrlLast = 0x00004000 + usbBuf0CtrlData0Pid = 0x00000000 + usbBuf0CtrlData1Pid = 0x00002000 + usbBuf0CtrlSel = 0x00001000 + usbBuf0CtrlStall = 0x00000800 + usbBuf0CtrlAvail = 0x00000400 + usbBuf0CtrlLenMask = 0x000003FF + + USBBufferLen = 64 +) diff --git a/src/machine/machine_rp2_2040.go b/src/machine/machine_rp2_2040.go new file mode 100644 index 0000000000..c82b084831 --- /dev/null +++ b/src/machine/machine_rp2_2040.go @@ -0,0 +1,199 @@ +//go:build rp2040 + +package machine + +import ( + "device/rp" + "runtime/volatile" + "unsafe" +) + +const ( + _NUMBANK0_GPIOS = 30 + _NUMBANK0_IRQS = 4 + _NUMIRQ = 32 + rp2350ExtraReg = 0 + RESETS_RESET_Msk = 0x01ffffff + initUnreset = rp.RESETS_RESET_ADC | + rp.RESETS_RESET_RTC | + rp.RESETS_RESET_SPI0 | + rp.RESETS_RESET_SPI1 | + rp.RESETS_RESET_UART0 | + rp.RESETS_RESET_UART1 | + rp.RESETS_RESET_USBCTRL + initDontReset = rp.RESETS_RESET_IO_QSPI | + rp.RESETS_RESET_PADS_QSPI | + rp.RESETS_RESET_PLL_USB | + rp.RESETS_RESET_USBCTRL | + rp.RESETS_RESET_SYSCFG | + rp.RESETS_RESET_PLL_SYS + padEnableMask = rp.PADS_BANK0_GPIO0_IE_Msk | + rp.PADS_BANK0_GPIO0_OD_Msk +) + +const ( + PinOutput PinMode = iota + PinInput + PinInputPulldown + PinInputPullup + PinAnalog + PinUART + PinPWM + PinI2C + PinSPI + PinPIO0 + PinPIO1 +) + +const ( + ClkGPOUT0 clockIndex = iota // GPIO Muxing 0 + ClkGPOUT1 // GPIO Muxing 1 + ClkGPOUT2 // GPIO Muxing 2 + ClkGPOUT3 // GPIO Muxing 3 + ClkRef // Watchdog and timers reference clock + ClkSys // Processors, bus fabric, memory, memory mapped registers + ClkPeri // Peripheral clock for UART and SPI + ClkUSB // USB clock + ClkADC // ADC clock + ClkRTC // Real time clock + NumClocks +) + +func CalcClockDiv(srcFreq, freq uint32) uint32 { + // Div register is 24.8 int.frac divider so multiply by 2^8 (left shift by 8) + return uint32((uint64(srcFreq) << 8) / uint64(freq)) +} + +type clocksType struct { + clk [NumClocks]clockType + resus struct { + ctrl volatile.Register32 + status volatile.Register32 + } + fc0 fc + wakeEN0 volatile.Register32 + wakeEN1 volatile.Register32 + sleepEN0 volatile.Register32 + sleepEN1 volatile.Register32 + enabled0 volatile.Register32 + enabled1 volatile.Register32 + intR volatile.Register32 + intE volatile.Register32 + intF volatile.Register32 + intS volatile.Register32 +} + +// GPIO function selectors +const ( + fnJTAG pinFunc = 0 + fnSPI pinFunc = 1 // Connect one of the internal PL022 SPI peripherals to GPIO + fnUART pinFunc = 2 + fnI2C pinFunc = 3 + // Connect a PWM slice to GPIO. There are eight PWM slices, + // each with two outputchannels (A/B). The B pin can also be used as an input, + // for frequency and duty cyclemeasurement + fnPWM pinFunc = 4 + // Software control of GPIO, from the single-cycle IO (SIO) block. + // The SIO function (F5)must be selected for the processors to drive a GPIO, + // but the input is always connected,so software can check the state of GPIOs at any time. + fnSIO pinFunc = 5 + // Connect one of the programmable IO blocks (PIO) to GPIO. PIO can implement a widevariety of interfaces, + // and has its own internal pin mapping hardware, allowing flexibleplacement of digital interfaces on bank 0 GPIOs. + // The PIO function (F6, F7) must beselected for PIO to drive a GPIO, but the input is always connected, + // so the PIOs canalways see the state of all pins. + fnPIO0, fnPIO1 pinFunc = 6, 7 + // General purpose clock inputs/outputs. Can be routed to a number of internal clock domains onRP2040, + // e.g. Input: to provide a 1 Hz clock for the RTC, or can be connected to an internalfrequency counter. + // e.g. Output: optional integer divide + fnGPCK pinFunc = 8 + // USB power control signals to/from the internal USB controller + fnUSB pinFunc = 9 + fnNULL pinFunc = 0x1f + + fnXIP pinFunc = 0 +) + +// Configure configures the gpio pin as per mode. +func (p Pin) Configure(config PinConfig) { + if p == NoPin { + return + } + p.init() + mask := uint32(1) << p + switch config.Mode { + case PinOutput: + p.setFunc(fnSIO) + rp.SIO.GPIO_OE_SET.Set(mask) + case PinInput: + p.setFunc(fnSIO) + p.pulloff() + case PinInputPulldown: + p.setFunc(fnSIO) + p.pulldown() + case PinInputPullup: + p.setFunc(fnSIO) + p.pullup() + case PinAnalog: + p.setFunc(fnNULL) + p.pulloff() + case PinUART: + p.setFunc(fnUART) + case PinPWM: + p.setFunc(fnPWM) + case PinI2C: + // IO config according to 4.3.1.3 of rp2040 datasheet. + p.setFunc(fnI2C) + p.pullup() + p.setSchmitt(true) + p.setSlew(false) + case PinSPI: + p.setFunc(fnSPI) + case PinPIO0: + p.setFunc(fnPIO0) + case PinPIO1: + p.setFunc(fnPIO1) + } +} + +var ( + timer = (*timerType)(unsafe.Pointer(rp.TIMER)) +) + +// Enable or disable a specific interrupt on the executing core. +// num is the interrupt number which must be in [0,31]. +func irqSet(num uint32, enabled bool) { + if num >= _NUMIRQ { + return + } + irqSetMask(1<= _NUMIRQ { + return + } + + register_index := num / 32 + var mask uint32 = 1 << (num % 32) + + if enabled { + // Clear pending before enable + //(if IRQ is actually asserted, it will immediately re-pend) + if register_index == 0 { + rp.PPB.NVIC_ICPR0.Set(mask) + rp.PPB.NVIC_ISER0.Set(mask) + } else { + rp.PPB.NVIC_ICPR1.Set(mask) + rp.PPB.NVIC_ISER1.Set(mask) + } + } else { + if register_index == 0 { + rp.PPB.NVIC_ICER0.Set(mask) + } else { + rp.PPB.NVIC_ICER1.Set(mask) + } + } +} + +func (clks *clocksType) initRTC() {} // No RTC on RP2350. + +func (clks *clocksType) initTicks() { + rp.TICKS.SetTIMER0_CTRL_ENABLE(0) + rp.TICKS.SetTIMER0_CYCLES(12) + rp.TICKS.SetTIMER0_CTRL_ENABLE(1) +} + +func EnterBootloader() { + enterBootloader() +} + +// startTick starts the watchdog tick. +// On RP2040, the watchdog contained a tick generator used to generate a 1μs tick for the watchdog. This was also +// distributed to the system timer. On RP2350, the watchdog instead takes a tick input from the system-level ticks block. See Section 8.5. +func (wd *watchdogImpl) startTick(cycles uint32) { + rp.TICKS.WATCHDOG_CTRL.SetBits(1) +} diff --git a/src/machine/machine_rp2040_adc.go b/src/machine/machine_rp2_adc.go similarity index 99% rename from src/machine/machine_rp2040_adc.go rename to src/machine/machine_rp2_adc.go index 1f05684ebc..13504d719f 100644 --- a/src/machine/machine_rp2040_adc.go +++ b/src/machine/machine_rp2_adc.go @@ -1,4 +1,4 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine diff --git a/src/machine/machine_rp2040_clocks.go b/src/machine/machine_rp2_clocks.go similarity index 68% rename from src/machine/machine_rp2040_clocks.go rename to src/machine/machine_rp2_clocks.go index 57dfa68b0b..ad2c6517fa 100644 --- a/src/machine/machine_rp2040_clocks.go +++ b/src/machine/machine_rp2_clocks.go @@ -1,4 +1,4 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine @@ -22,20 +22,6 @@ func cpuPeriod() uint32 { // clockIndex identifies a hardware clock type clockIndex uint8 -const ( - clkGPOUT0 clockIndex = iota // GPIO Muxing 0 - clkGPOUT1 // GPIO Muxing 1 - clkGPOUT2 // GPIO Muxing 2 - clkGPOUT3 // GPIO Muxing 3 - clkRef // Watchdog and timers reference clock - clkSys // Processors, bus fabric, memory, memory mapped registers - clkPeri // Peripheral clock for UART and SPI - clkUSB // USB clock - clkADC // ADC clock - clkRTC // Real time clock - numClocks -) - type clockType struct { ctrl volatile.Register32 div volatile.Register32 @@ -53,28 +39,9 @@ type fc struct { result volatile.Register32 } -type clocksType struct { - clk [numClocks]clockType - resus struct { - ctrl volatile.Register32 - status volatile.Register32 - } - fc0 fc - wakeEN0 volatile.Register32 - wakeEN1 volatile.Register32 - sleepEN0 volatile.Register32 - sleepEN1 volatile.Register32 - enabled0 volatile.Register32 - enabled1 volatile.Register32 - intR volatile.Register32 - intE volatile.Register32 - intF volatile.Register32 - intS volatile.Register32 -} - var clocks = (*clocksType)(unsafe.Pointer(rp.CLOCKS)) -var configuredFreq [numClocks]uint32 +var configuredFreq [NumClocks]uint32 type clock struct { *clockType @@ -101,7 +68,7 @@ func (clks *clocksType) clock(cix clockIndex) clock { // // Not all clocks have both types of mux. func (clk *clock) hasGlitchlessMux() bool { - return clk.cix == clkSys || clk.cix == clkRef + return clk.cix == ClkSys || clk.cix == ClkRef } // configure configures the clock by selecting the main clock source src @@ -113,8 +80,7 @@ func (clk *clock) configure(src, auxsrc, srcFreq, freq uint32) { panic("clock frequency cannot be greater than source frequency") } - // Div register is 24.8 int.frac divider so multiply by 2^8 (left shift by 8) - div := uint32((uint64(srcFreq) << 8) / uint64(freq)) + div := CalcClockDiv(srcFreq, freq) // If increasing divisor, set divisor before source. Otherwise set source // before divisor. This avoids a momentary overspeed when e.g. switching @@ -133,16 +99,16 @@ func (clk *clock) configure(src, auxsrc, srcFreq, freq uint32) { } else // If no glitchless mux, cleanly stop the clock to avoid glitches // propagating when changing aux mux. Note it would be a really bad idea - // to do this on one of the glitchless clocks (clkSys, clkRef). + // to do this on one of the glitchless clocks (ClkSys, ClkRef). { - // Disable clock. On clkRef and clkSys this does nothing, + // Disable clock. On ClkRef and ClkSys this does nothing, // all other clocks have the ENABLE bit in the same position. clk.ctrl.ClearBits(rp.CLOCKS_CLK_GPOUT0_CTRL_ENABLE_Msk) if configuredFreq[clk.cix] > 0 { // Delay for 3 cycles of the target clock, for ENABLE propagation. // Note XOSC_COUNT is not helpful here because XOSC is not // necessarily running, nor is timer... so, 3 cycles per loop: - delayCyc := configuredFreq[clkSys]/configuredFreq[clk.cix] + 1 + delayCyc := configuredFreq[ClkSys]/configuredFreq[clk.cix] + 1 for delayCyc != 0 { // This could be done more efficiently but TinyGo inline // assembly is not yet capable enough to express that. In the @@ -164,7 +130,7 @@ func (clk *clock) configure(src, auxsrc, srcFreq, freq uint32) { } } - // Enable clock. On clkRef and clkSys this does nothing, + // Enable clock. On ClkRef and ClkSys this does nothing, // all other clocks have the ENABLE bit in the same position. clk.ctrl.SetBits(rp.CLOCKS_CLK_GPOUT0_CTRL_ENABLE) @@ -185,18 +151,18 @@ func (clks *clocksType) init() { Watchdog.startTick(xoscFreq) // Disable resus that may be enabled from previous software - clks.resus.ctrl.Set(0) + rp.CLOCKS.SetCLK_SYS_RESUS_CTRL_CLEAR(0) // Enable the xosc xosc.init() // Before we touch PLLs, switch sys and ref cleanly away from their aux sources. - clks.clk[clkSys].ctrl.ClearBits(rp.CLOCKS_CLK_SYS_CTRL_SRC_Msk) - for !clks.clk[clkSys].selected.HasBits(0x1) { + clks.clk[ClkSys].ctrl.ClearBits(rp.CLOCKS_CLK_SYS_CTRL_SRC_Msk) + for !clks.clk[ClkSys].selected.HasBits(0x1) { } - clks.clk[clkRef].ctrl.ClearBits(rp.CLOCKS_CLK_REF_CTRL_SRC_Msk) - for !clks.clk[clkRef].selected.HasBits(0x1) { + clks.clk[ClkRef].ctrl.ClearBits(rp.CLOCKS_CLK_REF_CTRL_SRC_Msk) + for !clks.clk[ClkRef].selected.HasBits(0x1) { } // Configure PLLs @@ -207,47 +173,44 @@ func (clks *clocksType) init() { pllUSB.init(1, 480*MHz, 5, 2) // Configure clocks - // clkRef = xosc (12MHz) / 1 = 12MHz - clkref := clks.clock(clkRef) + // ClkRef = xosc (12MHz) / 1 = 12MHz + clkref := clks.clock(ClkRef) clkref.configure(rp.CLOCKS_CLK_REF_CTRL_SRC_XOSC_CLKSRC, 0, // No aux mux 12*MHz, 12*MHz) - // clkSys = pllSys (125MHz) / 1 = 125MHz - clksys := clks.clock(clkSys) + // ClkSys = pllSys (125MHz) / 1 = 125MHz + clksys := clks.clock(ClkSys) clksys.configure(rp.CLOCKS_CLK_SYS_CTRL_SRC_CLKSRC_CLK_SYS_AUX, rp.CLOCKS_CLK_SYS_CTRL_AUXSRC_CLKSRC_PLL_SYS, 125*MHz, 125*MHz) - // clkUSB = pllUSB (48MHz) / 1 = 48MHz - clkusb := clks.clock(clkUSB) + // ClkUSB = pllUSB (48MHz) / 1 = 48MHz + clkusb := clks.clock(ClkUSB) clkusb.configure(0, // No GLMUX rp.CLOCKS_CLK_USB_CTRL_AUXSRC_CLKSRC_PLL_USB, 48*MHz, 48*MHz) - // clkADC = pllUSB (48MHZ) / 1 = 48MHz - clkadc := clks.clock(clkADC) + // ClkADC = pllUSB (48MHZ) / 1 = 48MHz + clkadc := clks.clock(ClkADC) clkadc.configure(0, // No GLMUX rp.CLOCKS_CLK_ADC_CTRL_AUXSRC_CLKSRC_PLL_USB, 48*MHz, 48*MHz) - // clkRTC = pllUSB (48MHz) / 1024 = 46875Hz - clkrtc := clks.clock(clkRTC) - clkrtc.configure(0, // No GLMUX - rp.CLOCKS_CLK_RTC_CTRL_AUXSRC_CLKSRC_PLL_USB, - 48*MHz, - 46875) + clks.initRTC() - // clkPeri = clkSys. Used as reference clock for Peripherals. + // ClkPeri = ClkSys. Used as reference clock for Peripherals. // No dividers so just select and enable. - // Normally choose clkSys or clkUSB. - clkperi := clks.clock(clkPeri) + // Normally choose ClkSys or ClkUSB. + clkperi := clks.clock(ClkPeri) clkperi.configure(0, rp.CLOCKS_CLK_PERI_CTRL_AUXSRC_CLK_SYS, 125*MHz, 125*MHz) + + clks.initTicks() } diff --git a/src/machine/machine_rp2040_gpio.go b/src/machine/machine_rp2_gpio.go similarity index 66% rename from src/machine/machine_rp2040_gpio.go rename to src/machine/machine_rp2_gpio.go index 89c5f40b16..d49d12f2ba 100644 --- a/src/machine/machine_rp2040_gpio.go +++ b/src/machine/machine_rp2_gpio.go @@ -1,4 +1,4 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine @@ -15,14 +15,26 @@ type ioType struct { } type irqCtrl struct { - intE [4]volatile.Register32 - intF [4]volatile.Register32 - intS [4]volatile.Register32 + intE [_NUMBANK0_IRQS]volatile.Register32 + intF [_NUMBANK0_IRQS]volatile.Register32 + intS [_NUMBANK0_IRQS]volatile.Register32 +} + +type irqSummary struct { + proc [2]struct { + secure [2]volatile.Register32 + nonsecure [2]volatile.Register32 + } + comaWake struct { + secure [2]volatile.Register32 + nonsecure [2]volatile.Register32 + } } type ioBank0Type struct { - io [30]ioType - intR [4]volatile.Register32 + io [_NUMBANK0_GPIOS]ioType + irqsum [rp2350ExtraReg]irqSummary + intR [_NUMBANK0_IRQS]volatile.Register32 proc0IRQctrl irqCtrl proc1IRQctrl irqCtrl dormantWakeIRQctrl irqCtrl @@ -32,7 +44,7 @@ var ioBank0 = (*ioBank0Type)(unsafe.Pointer(rp.IO_BANK0)) type padsBank0Type struct { voltageSelect volatile.Register32 - io [30]volatile.Register32 + io [_NUMBANK0_GPIOS]volatile.Register32 } var padsBank0 = (*padsBank0Type)(unsafe.Pointer(rp.PADS_BANK0)) @@ -45,50 +57,6 @@ var padsBank0 = (*padsBank0Type)(unsafe.Pointer(rp.PADS_BANK0)) // the peripheral sees the logical OR of these GPIO inputs. type pinFunc uint8 -// GPIO function selectors -const ( - fnJTAG pinFunc = 0 - fnSPI pinFunc = 1 // Connect one of the internal PL022 SPI peripherals to GPIO - fnUART pinFunc = 2 - fnI2C pinFunc = 3 - // Connect a PWM slice to GPIO. There are eight PWM slices, - // each with two outputchannels (A/B). The B pin can also be used as an input, - // for frequency and duty cyclemeasurement - fnPWM pinFunc = 4 - // Software control of GPIO, from the single-cycle IO (SIO) block. - // The SIO function (F5)must be selected for the processors to drive a GPIO, - // but the input is always connected,so software can check the state of GPIOs at any time. - fnSIO pinFunc = 5 - // Connect one of the programmable IO blocks (PIO) to GPIO. PIO can implement a widevariety of interfaces, - // and has its own internal pin mapping hardware, allowing flexibleplacement of digital interfaces on bank 0 GPIOs. - // The PIO function (F6, F7) must beselected for PIO to drive a GPIO, but the input is always connected, - // so the PIOs canalways see the state of all pins. - fnPIO0, fnPIO1 pinFunc = 6, 7 - // General purpose clock inputs/outputs. Can be routed to a number of internal clock domains onRP2040, - // e.g. Input: to provide a 1 Hz clock for the RTC, or can be connected to an internalfrequency counter. - // e.g. Output: optional integer divide - fnGPCK pinFunc = 8 - // USB power control signals to/from the internal USB controller - fnUSB pinFunc = 9 - fnNULL pinFunc = 0x1f - - fnXIP pinFunc = 0 -) - -const ( - PinOutput PinMode = iota - PinInput - PinInputPulldown - PinInputPullup - PinAnalog - PinUART - PinPWM - PinI2C - PinSPI - PinPIO0 - PinPIO1 -) - func (p Pin) PortMaskSet() (*uint32, uint32) { return (*uint32)(unsafe.Pointer(&rp.SIO.GPIO_OUT_SET)), 1 << p } @@ -157,8 +125,7 @@ func (p Pin) setSchmitt(trigger bool) { // setFunc will set pin function to fn. func (p Pin) setFunc(fn pinFunc) { // Set input enable, Clear output disable - p.padCtrl().ReplaceBits(rp.PADS_BANK0_GPIO0_IE, - rp.PADS_BANK0_GPIO0_IE_Msk|rp.PADS_BANK0_GPIO0_OD_Msk, 0) + p.padCtrl().ReplaceBits(rp.PADS_BANK0_GPIO0_IE, padEnableMask, 0) // Zero all fields apart from fsel; we want this IO to do what the peripheral tells it. // This doesn't affect e.g. pullup/pulldown, as these are in pad controls. @@ -172,48 +139,6 @@ func (p Pin) init() { p.clr() } -// Configure configures the gpio pin as per mode. -func (p Pin) Configure(config PinConfig) { - if p == NoPin { - return - } - p.init() - mask := uint32(1) << p - switch config.Mode { - case PinOutput: - p.setFunc(fnSIO) - rp.SIO.GPIO_OE_SET.Set(mask) - case PinInput: - p.setFunc(fnSIO) - p.pulloff() - case PinInputPulldown: - p.setFunc(fnSIO) - p.pulldown() - case PinInputPullup: - p.setFunc(fnSIO) - p.pullup() - case PinAnalog: - p.setFunc(fnNULL) - p.pulloff() - case PinUART: - p.setFunc(fnUART) - case PinPWM: - p.setFunc(fnPWM) - case PinI2C: - // IO config according to 4.3.1.3 of rp2040 datasheet. - p.setFunc(fnI2C) - p.pullup() - p.setSchmitt(true) - p.setSlew(false) - case PinSPI: - p.setFunc(fnSPI) - case PinPIO0: - p.setFunc(fnPIO0) - case PinPIO1: - p.setFunc(fnPIO1) - } -} - // Set drives the pin high if value is true else drives it low. func (p Pin) Set(value bool) { if p == NoPin { @@ -331,23 +256,3 @@ func (p Pin) ioIntBit(change PinChange) uint32 { func getIntChange(p Pin, status uint32) PinChange { return PinChange(status>>(4*(p%8))) & 0xf } - -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } - - UART1 = &_UART1 - _UART1 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART1, - } -) - -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) - UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) -} diff --git a/src/machine/machine_rp2040_pins.go b/src/machine/machine_rp2_pins.go similarity index 85% rename from src/machine/machine_rp2040_pins.go rename to src/machine/machine_rp2_pins.go index 9abbdb002e..93f2d50a01 100644 --- a/src/machine/machine_rp2040_pins.go +++ b/src/machine/machine_rp2_pins.go @@ -1,4 +1,4 @@ -//go:build rp2040 || ae_rp2040 || badger2040 || challenger_rp2040 || feather_rp2040 || gopher_badge || kb2040 || macropad_rp2040 || nano_rp2040 || pico || qtpy_rp2040 || thingplus_rp2040 || thumby || tufty2040 || waveshare_rp2040_zero || xiao_rp2040 +//go:build rp2040 || rp2350 || ae_rp2040 || badger2040 || challenger_rp2040 || feather_rp2040 || gopher_badge || kb2040 || macropad_rp2040 || nano_rp2040 || pico || qtpy_rp2040 || thingplus_rp2040 || thumby || tufty2040 || waveshare_rp2040_zero || xiao_rp2040 package machine diff --git a/src/machine/machine_rp2040_pll.go b/src/machine/machine_rp2_pll.go similarity index 98% rename from src/machine/machine_rp2040_pll.go rename to src/machine/machine_rp2_pll.go index d611f2924d..f409768ab3 100644 --- a/src/machine/machine_rp2040_pll.go +++ b/src/machine/machine_rp2_pll.go @@ -1,4 +1,4 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine diff --git a/src/machine/machine_rp2040_resets.go b/src/machine/machine_rp2_resets.go similarity index 51% rename from src/machine/machine_rp2040_resets.go rename to src/machine/machine_rp2_resets.go index 6f15e99528..245436c47c 100644 --- a/src/machine/machine_rp2040_resets.go +++ b/src/machine/machine_rp2_resets.go @@ -1,36 +1,24 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine import ( "device/rp" - "runtime/volatile" "unsafe" ) -// RESETS_RESET_Msk is bitmask to reset all peripherals -// -// TODO: This field is not available in the device file. -const RESETS_RESET_Msk = 0x01ffffff - -type resetsType struct { - reset volatile.Register32 - wdSel volatile.Register32 - resetDone volatile.Register32 -} - -var resets = (*resetsType)(unsafe.Pointer(rp.RESETS)) +var resets = (*rp.RESETS_Type)(unsafe.Pointer(rp.RESETS)) // resetBlock resets hardware blocks specified // by the bit pattern in bits. func resetBlock(bits uint32) { - resets.reset.SetBits(bits) + resets.RESET.SetBits(bits) } // unresetBlock brings hardware blocks specified by the // bit pattern in bits out of reset. func unresetBlock(bits uint32) { - resets.reset.ClearBits(bits) + resets.RESET.ClearBits(bits) } // unresetBlockWait brings specified hardware blocks @@ -38,6 +26,6 @@ func unresetBlock(bits uint32) { // out of reset and wait for completion. func unresetBlockWait(bits uint32) { unresetBlock(bits) - for !resets.resetDone.HasBits(bits) { + for !resets.RESET_DONE.HasBits(bits) { } } diff --git a/src/machine/machine_rp2040_sync.go b/src/machine/machine_rp2_sync.go similarity index 62% rename from src/machine/machine_rp2040_sync.go rename to src/machine/machine_rp2_sync.go index 4c9b443237..5019552afd 100644 --- a/src/machine/machine_rp2040_sync.go +++ b/src/machine/machine_rp2_sync.go @@ -1,24 +1,11 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine -import ( - "device/rp" -) - // machine_rp2040_sync.go contains interrupt and // lock primitives similar to those found in Pico SDK's // irq.c -const ( - // Number of spin locks available - _NUMSPINLOCKS = 32 - // Number of interrupt handlers available - _NUMIRQ = 32 - _PICO_SPINLOCK_ID_IRQ = 9 - _NUMBANK0_GPIOS = 30 -) - // Clears interrupt flag on a pin func (p Pin) acknowledgeInterrupt(change PinChange) { ioBank0.intR[p>>3].Set(p.ioIntBit(change)) @@ -50,23 +37,3 @@ func (p Pin) ctrlSetInterrupt(change PinChange, enabled bool, base *irqCtrl) { enReg.ClearBits(p.ioIntBit(change)) } } - -// Enable or disable a specific interrupt on the executing core. -// num is the interrupt number which must be in [0,31]. -func irqSet(num uint32, enabled bool) { - if num >= _NUMIRQ { - return - } - irqSetMask(1< Date: Tue, 17 Dec 2024 20:38:31 +0100 Subject: [PATCH 327/444] feature: make i2c implementation shared for rp2040/rp2350 Signed-off-by: deadprogram --- src/machine/i2c.go | 2 +- src/machine/{machine_rp2040_i2c.go => machine_rp2_i2c.go} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/machine/{machine_rp2040_i2c.go => machine_rp2_i2c.go} (99%) diff --git a/src/machine/i2c.go b/src/machine/i2c.go index 24fbf6897f..3b1b4dd4da 100644 --- a/src/machine/i2c.go +++ b/src/machine/i2c.go @@ -1,4 +1,4 @@ -//go:build !baremetal || atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || mimxrt1062 || (esp32c3 && !m5stamp_c3) || esp32 +//go:build !baremetal || atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || rp2350 || mimxrt1062 || (esp32c3 && !m5stamp_c3) || esp32 package machine diff --git a/src/machine/machine_rp2040_i2c.go b/src/machine/machine_rp2_i2c.go similarity index 99% rename from src/machine/machine_rp2040_i2c.go rename to src/machine/machine_rp2_i2c.go index f34aa259f5..2552eb94e6 100644 --- a/src/machine/machine_rp2040_i2c.go +++ b/src/machine/machine_rp2_i2c.go @@ -1,4 +1,4 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine @@ -8,7 +8,7 @@ import ( "internal/itoa" ) -// I2C on the RP2040. +// I2C on the RP2040/RP2350 var ( I2C0 = &_I2C0 _I2C0 = I2C{ From f64e70e6590ffbe281ca096ee61212d6b586e986 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 18 Dec 2024 11:21:51 +0100 Subject: [PATCH 328/444] feature: make SPI implementation shared for rp2040/rp2350 Signed-off-by: deadprogram --- .../{machine_rp2040_spi.go => machine_rp2_spi.go} | 9 ++++----- src/machine/spi.go | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) rename src/machine/{machine_rp2040_spi.go => machine_rp2_spi.go} (97%) diff --git a/src/machine/machine_rp2040_spi.go b/src/machine/machine_rp2_spi.go similarity index 97% rename from src/machine/machine_rp2040_spi.go rename to src/machine/machine_rp2_spi.go index dbb063cb30..faab9839af 100644 --- a/src/machine/machine_rp2040_spi.go +++ b/src/machine/machine_rp2_spi.go @@ -1,4 +1,4 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine @@ -207,7 +207,7 @@ func (spi SPI) initSPI(config SPIConfig) (err error) { } err = spi.SetBaudRate(config.Frequency) // Set SPI Format (CPHA and CPOL) and frame format (default is Motorola) - spi.setFormat(config.Mode, rp.XIP_SSI_CTRLR0_SPI_FRF_STD) + spi.setFormat(config.Mode) // Always enable DREQ signals -- harmless if DMA is not listening spi.Bus.SSPDMACR.SetBits(rp.SPI0_SSPDMACR_TXDMAE | rp.SPI0_SSPDMACR_RXDMAE) @@ -217,14 +217,13 @@ func (spi SPI) initSPI(config SPIConfig) (err error) { } //go:inline -func (spi SPI) setFormat(mode uint8, frameFormat uint32) { +func (spi SPI) setFormat(mode uint8) { cpha := uint32(mode) & 1 cpol := uint32(mode>>1) & 1 spi.Bus.SSPCR0.ReplaceBits( (cpha< Date: Wed, 18 Dec 2024 20:06:49 +0100 Subject: [PATCH 329/444] targets: add support for Pimoroni Tiny2350 board Signed-off-by: deadprogram --- GNUmakefile | 2 + src/machine/board_tiny2350.go | 82 +++++++++++++++++++++++++++++++++++ targets/tiny2350.json | 7 +++ 3 files changed, 91 insertions(+) create mode 100644 src/machine/board_tiny2350.go create mode 100644 targets/tiny2350.json diff --git a/GNUmakefile b/GNUmakefile index 273db5ed86..efc70b0ae8 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -743,6 +743,8 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pico2 examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=tiny2350 examples/blinky1 + @$(MD5SUM) test.hex # test pwm $(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm @$(MD5SUM) test.hex diff --git a/src/machine/board_tiny2350.go b/src/machine/board_tiny2350.go new file mode 100644 index 0000000000..f04fa061b6 --- /dev/null +++ b/src/machine/board_tiny2350.go @@ -0,0 +1,82 @@ +//go:build tiny2350 + +package machine + +// GPIO pins +const ( + GP0 Pin = GPIO0 + GP1 Pin = GPIO1 + GP2 Pin = GPIO2 + GP3 Pin = GPIO3 + GP4 Pin = GPIO4 + GP5 Pin = GPIO5 + GP6 Pin = GPIO6 + GP7 Pin = GPIO7 + GP12 Pin = GPIO12 + GP13 Pin = GPIO13 + GP18 Pin = GPIO18 + GP19 Pin = GPIO19 + GP20 Pin = GPIO20 + GP26 Pin = GPIO26 + GP27 Pin = GPIO27 + GP28 Pin = GPIO28 + GP29 Pin = GPIO29 + + // Onboard LED + LED_RED Pin = GPIO18 + LED_GREEN Pin = GPIO19 + LED_BLUE Pin = GPIO20 + LED = LED_RED + + // Onboard crystal oscillator frequency, in MHz. + xoscFreq = 12 // MHz +) + +// I2C Default pins on Tiny2350. +const ( + I2C0_SDA_PIN = GP12 + I2C0_SCL_PIN = GP13 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO6 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO7 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO4 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO26 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO27 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO28 // Rx +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO4 + UART1_RX_PIN = GPIO5 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Tiny2350" + usb_STRING_MANUFACTURER = "Pimoroni" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000F +) diff --git a/targets/tiny2350.json b/targets/tiny2350.json new file mode 100644 index 0000000000..185be33202 --- /dev/null +++ b/targets/tiny2350.json @@ -0,0 +1,7 @@ +{ + "inherits": [ + "rp2350" + ], + "build-tags": ["tiny2350"], + "serial-port": ["2e8a:000f"] +} From a98e35ebab07acc0be95bea1bbc94d71f089010e Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 19 Dec 2024 10:26:18 +0100 Subject: [PATCH 330/444] reflect: fix incorrect comment on elemType PR that introduced this: https://github.com/tinygo-org/tinygo/pull/4543 --- src/reflect/type.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reflect/type.go b/src/reflect/type.go index 05915350af..c81d6ba554 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -424,8 +424,8 @@ type rawType struct { meta uint8 // metadata byte, contains kind and flags (see constants above) } -// All types that have an element type: named, chan, slice, array, map, interface -// (but not pointer because it doesn't have ptrTo). +// All types that have an element type: named, chan, slice, array, map (but not +// pointer because it doesn't have ptrTo). type elemType struct { rawType numMethod uint16 From 650776588351aac22c038605b959ae252a7faf6f Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 18 Dec 2024 12:15:29 +0100 Subject: [PATCH 331/444] feature: make RNG implementation shared for rp2040/rp2350 Signed-off-by: deadprogram --- src/machine/{machine_rp2040_rng.go => machine_rp2_rng.go} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/machine/{machine_rp2040_rng.go => machine_rp2_rng.go} (97%) diff --git a/src/machine/machine_rp2040_rng.go b/src/machine/machine_rp2_rng.go similarity index 97% rename from src/machine/machine_rp2040_rng.go rename to src/machine/machine_rp2_rng.go index 1706785d0f..e619f05002 100644 --- a/src/machine/machine_rp2040_rng.go +++ b/src/machine/machine_rp2_rng.go @@ -1,4 +1,4 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 // Implementation based on code located here: // https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/pico_lwip/random.c From eeba90fd5b1236ade833602bee6706a72e9b5f68 Mon Sep 17 00:00:00 2001 From: Thomas Legris Date: Wed, 4 Dec 2024 12:58:19 +0900 Subject: [PATCH 332/444] properly handle unix read on directory --- src/os/file_anyos.go | 7 ++++++- src/os/file_anyos_test.go | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/os/file_anyos.go b/src/os/file_anyos.go index 593512cb5a..33445e79f9 100644 --- a/src/os/file_anyos.go +++ b/src/os/file_anyos.go @@ -1,6 +1,6 @@ //go:build !baremetal && !js && !wasm_unknown && !nintendoswitch -// Portions copyright 2009 The Go Authors. All rights reserved. +// Portions copyright 2009-2024 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -97,6 +97,11 @@ type unixFileHandle uintptr // read and any error encountered. At end of file, Read returns 0, io.EOF. func (f unixFileHandle) Read(b []byte) (n int, err error) { n, err = syscall.Read(syscallFd(f), b) + // In case of EISDIR, n == -1. + // This breaks the assumption that n always represent the number of read bytes. + if err == syscall.EISDIR { + n = 0 + } err = handleSyscallError(err) if n == 0 && len(b) > 0 && err == nil { err = io.EOF diff --git a/src/os/file_anyos_test.go b/src/os/file_anyos_test.go index c7d6e50ac7..97f8ea13e5 100644 --- a/src/os/file_anyos_test.go +++ b/src/os/file_anyos_test.go @@ -185,3 +185,22 @@ func TestClose(t *testing.T) { } } } + +func TestReadOnDir(t *testing.T) { + name := TempDir() + "/_os_test_TestReadOnDir" + defer Remove(name) + f, err := OpenFile(name, O_RDWR|O_CREATE, 0644) + if err != nil { + t.Errorf("OpenFile %s: %s", name, err) + return + } + var buf [32]byte + n, err := f.Read(buf[:]) + if err == nil { + t.Errorf("Error expected") + return + } + if n != 0 { + t.Errorf("Wrong read bytes: %s", err) + } +} From b18213805ac22ec2f1c46fbe7a6a06a2bcd0bf0f Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 16 Dec 2024 12:08:10 +0100 Subject: [PATCH 333/444] builder: write HTML size report This is not a big change over the existing size report with -size=full, but it is a bit more readable. More information will be added in subsequent commits. --- builder/build.go | 14 ++++++-- builder/size-report.go | 56 +++++++++++++++++++++++++++++ builder/size-report.html | 72 +++++++++++++++++++++++++++++++++++++ builder/sizes.go | 52 +++++++++++++++------------ compileopts/options.go | 2 +- compileopts/options_test.go | 2 +- main.go | 2 +- 7 files changed, 171 insertions(+), 29 deletions(-) create mode 100644 builder/size-report.go create mode 100644 builder/size-report.html diff --git a/builder/build.go b/builder/build.go index d1d2c4be63..87c342a323 100644 --- a/builder/build.go +++ b/builder/build.go @@ -931,15 +931,16 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } // Print code size if requested. - if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" { + if config.Options.PrintSizes != "" { sizes, err := loadProgramSize(result.Executable, result.PackagePathMap) if err != nil { return err } - if config.Options.PrintSizes == "short" { + switch config.Options.PrintSizes { + case "short": fmt.Printf(" code data bss | flash ram\n") fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code+sizes.ROData, sizes.Data, sizes.BSS, sizes.Flash(), sizes.RAM()) - } else { + case "full": if !config.Debug() { fmt.Println("warning: data incomplete, remove the -no-debug flag for more detail") } @@ -951,6 +952,13 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } fmt.Printf("------------------------------- | --------------- | -------\n") fmt.Printf("%7d %7d %7d %7d | %7d %7d | total\n", sizes.Code, sizes.ROData, sizes.Data, sizes.BSS, sizes.Code+sizes.ROData+sizes.Data, sizes.Data+sizes.BSS) + case "html": + const filename = "size-report.html" + err := writeSizeReport(sizes, filename, pkgName) + if err != nil { + return err + } + fmt.Println("Wrote size report to", filename) } } diff --git a/builder/size-report.go b/builder/size-report.go new file mode 100644 index 0000000000..d826f30274 --- /dev/null +++ b/builder/size-report.go @@ -0,0 +1,56 @@ +package builder + +import ( + _ "embed" + "fmt" + "html/template" + "os" +) + +//go:embed size-report.html +var sizeReportBase string + +func writeSizeReport(sizes *programSize, filename, pkgName string) error { + tmpl, err := template.New("report").Parse(sizeReportBase) + if err != nil { + return err + } + + f, err := os.Create(filename) + if err != nil { + return fmt.Errorf("could not open report file: %w", err) + } + defer f.Close() + + // Prepare data for the report. + type sizeLine struct { + Name string + Size *packageSize + } + programData := []sizeLine{} + for _, name := range sizes.sortedPackageNames() { + pkgSize := sizes.Packages[name] + programData = append(programData, sizeLine{ + Name: name, + Size: pkgSize, + }) + } + sizeTotal := map[string]uint64{ + "code": sizes.Code, + "rodata": sizes.ROData, + "data": sizes.Data, + "bss": sizes.BSS, + "flash": sizes.Flash(), + } + + // Write the report. + err = tmpl.Execute(f, map[string]any{ + "pkgName": pkgName, + "sizes": programData, + "sizeTotal": sizeTotal, + }) + if err != nil { + return fmt.Errorf("could not create report file: %w", err) + } + return nil +} diff --git a/builder/size-report.html b/builder/size-report.html new file mode 100644 index 0000000000..d9c4822b97 --- /dev/null +++ b/builder/size-report.html @@ -0,0 +1,72 @@ + + + + Codestin Search App + + + + + + +
+

Size Report for {{.pkgName}}

+ +

How much space is used by Go packages, C libraries, and other bits to set up the program environment.

+ +
    +
  • Code is the actual program code (machine code instructions).
  • +
  • Read-only data are read-only global variables. On most microcontrollers, these are stored in flash and do not take up any RAM.
  • +
  • Data are writable global variables with a non-zero initializer. On microcontrollers, they are copied from flash to RAM on reset.
  • +
  • BSS are writable global variables that are zero initialized. They do not take up any space in the binary, but do take up RAM. On microcontrollers, this area is zeroed on reset.
  • +
+ +

The binary size consists of code, read-only data, and data. On microcontrollers, this is exactly the size of the firmware image. On other systems, there is some extra overhead: binary metadata (headers of the ELF/MachO/COFF file), debug information, exception tables, symbol names, etc. Using -no-debug strips most of those.

+ +

Program breakdown

+
+ + + + + + + + + + + + + {{range .sizes}} + + + + + + + + + {{end}} + + + + + + + + + + + +
PackageCodeRead-only dataDataBSSBinary size
{{.Name}}{{.Size.Code}}{{.Size.ROData}}{{.Size.Data}}{{.Size.BSS}} + {{.Size.Flash}} +
Total{{.sizeTotal.code}}{{.sizeTotal.rodata}}{{.sizeTotal.data}}{{.sizeTotal.bss}}{{.sizeTotal.flash}}
+
+
+ + diff --git a/builder/sizes.go b/builder/sizes.go index 3f6cc4518c..7e6eefb3c5 100644 --- a/builder/sizes.go +++ b/builder/sizes.go @@ -25,7 +25,7 @@ const sizesDebug = false // programSize contains size statistics per package of a compiled program. type programSize struct { - Packages map[string]packageSize + Packages map[string]*packageSize Code uint64 ROData uint64 Data uint64 @@ -56,10 +56,11 @@ func (ps *programSize) RAM() uint64 { // packageSize contains the size of a package, calculated from the linked object // file. type packageSize struct { - Code uint64 - ROData uint64 - Data uint64 - BSS uint64 + Program *programSize + Code uint64 + ROData uint64 + Data uint64 + BSS uint64 } // Flash usage in regular microcontrollers. @@ -72,6 +73,12 @@ func (ps *packageSize) RAM() uint64 { return ps.Data + ps.BSS } +// Flash usage in regular microcontrollers, as a percentage of the total flash +// usage of the program. +func (ps *packageSize) FlashPercent() float64 { + return float64(ps.Flash()) / float64(ps.Program.Flash()) * 100 +} + // A mapping of a single chunk of code or data to a file path. type addressLine struct { Address uint64 @@ -785,49 +792,48 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz // Now finally determine the binary/RAM size usage per package by going // through each allocated section. - sizes := make(map[string]packageSize) + sizes := make(map[string]*packageSize) + program := &programSize{ + Packages: sizes, + } + getSize := func(path string) *packageSize { + if field, ok := sizes[path]; ok { + return field + } + field := &packageSize{Program: program} + sizes[path] = field + return field + } for _, section := range sections { switch section.Type { case memoryCode: readSection(section, addresses, func(path string, size uint64, isVariable bool) { - field := sizes[path] + field := getSize(path) if isVariable { field.ROData += size } else { field.Code += size } - sizes[path] = field }, packagePathMap) case memoryROData: readSection(section, addresses, func(path string, size uint64, isVariable bool) { - field := sizes[path] - field.ROData += size - sizes[path] = field + getSize(path).ROData += size }, packagePathMap) case memoryData: readSection(section, addresses, func(path string, size uint64, isVariable bool) { - field := sizes[path] - field.Data += size - sizes[path] = field + getSize(path).Data += size }, packagePathMap) case memoryBSS: readSection(section, addresses, func(path string, size uint64, isVariable bool) { - field := sizes[path] - field.BSS += size - sizes[path] = field + getSize(path).BSS += size }, packagePathMap) case memoryStack: // We store the C stack as a pseudo-package. - sizes["C stack"] = packageSize{ - BSS: section.Size, - } + getSize("C stack").BSS += section.Size } } // ...and summarize the results. - program := &programSize{ - Packages: sizes, - } for _, pkg := range sizes { program.Code += pkg.Code program.ROData += pkg.ROData diff --git a/compileopts/options.go b/compileopts/options.go index bc462b29bd..30e0e4dbed 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -12,7 +12,7 @@ var ( validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise"} validSchedulerOptions = []string{"none", "tasks", "asyncify"} validSerialOptions = []string{"none", "uart", "usb", "rtt"} - validPrintSizeOptions = []string{"none", "short", "full"} + validPrintSizeOptions = []string{"none", "short", "full", "html"} validPanicStrategyOptions = []string{"print", "trap"} validOptOptions = []string{"none", "0", "1", "2", "s", "z"} ) diff --git a/compileopts/options_test.go b/compileopts/options_test.go index 23ffec465f..ee63c4c46d 100644 --- a/compileopts/options_test.go +++ b/compileopts/options_test.go @@ -11,7 +11,7 @@ func TestVerifyOptions(t *testing.T) { expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, conservative, custom, precise`) expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, asyncify`) - expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full`) + expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full, html`) expectedPanicStrategyError := errors.New(`invalid panic option 'incorrect': valid values are print, trap`) testCases := []struct { diff --git a/main.go b/main.go index 8ae5ce316a..9f0cc631a9 100644 --- a/main.go +++ b/main.go @@ -1509,7 +1509,7 @@ func main() { stackSize = uint64(size) return err }) - printSize := flag.String("size", "", "print sizes (none, short, full)") + printSize := flag.String("size", "", "print sizes (none, short, full, html)") printStacks := flag.Bool("print-stacks", false, "print stack sizes of goroutines") printAllocsString := flag.String("print-allocs", "", "regular expression of functions for which heap allocations should be printed") printCommands := flag.Bool("x", false, "Print commands") From 9d2f52805b3e4d7e98c84ff3eb4f8b4fb2849369 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 17 Dec 2024 11:30:22 +0100 Subject: [PATCH 334/444] builder: show files in size report table Show which files cause a binary size increase. This makes it easier to see where the size is going: for example, this makes it easy to see how much the GC contributes to code size compared to other runtime parts. --- builder/size-report.html | 41 ++++++++++++++- builder/sizes.go | 109 ++++++++++++++++++++++++++------------- 2 files changed, 112 insertions(+), 38 deletions(-) diff --git a/builder/size-report.html b/builder/size-report.html index d9c4822b97..2afb5c43d1 100644 --- a/builder/size-report.html +++ b/builder/size-report.html @@ -11,6 +11,12 @@ border-left: calc(var(--bs-border-width) * 2) solid currentcolor; } +/* Hover on only the rows that are clickable. */ +.row-package:hover > * { + --bs-table-color-state: var(--bs-table-hover-color); + --bs-table-bg-state: var(--bs-table-hover-bg); +} + @@ -29,6 +35,9 @@

Size Report for {{.pkgName}}

The binary size consists of code, read-only data, and data. On microcontrollers, this is exactly the size of the firmware image. On other systems, there is some extra overhead: binary metadata (headers of the ELF/MachO/COFF file), debug information, exception tables, symbol names, etc. Using -no-debug strips most of those.

Program breakdown

+ +

You can click on the rows below to see which files contribute to the binary size.

+
@@ -42,8 +51,8 @@

Program breakdown

- {{range .sizes}} - + {{range $i, $pkg := .sizes}} + @@ -53,6 +62,24 @@

Program breakdown

{{.Size.Flash}} + {{range $filename, $sizes := .Size.Sub}} + + + + + + + + + {{end}} {{end}} @@ -68,5 +95,15 @@

Program breakdown

{{.Name}} {{.Size.Code}} {{.Size.ROData}}
+ {{if eq $filename ""}} + (unknown file) + {{else}} + {{$filename}} + {{end}} + {{$sizes.Code}}{{$sizes.ROData}}{{$sizes.Data}}{{$sizes.BSS}} + {{$sizes.Flash}} +
+ diff --git a/builder/sizes.go b/builder/sizes.go index 7e6eefb3c5..485a652d97 100644 --- a/builder/sizes.go +++ b/builder/sizes.go @@ -53,6 +53,20 @@ func (ps *programSize) RAM() uint64 { return ps.Data + ps.BSS } +// Return the package size information for a given package path, creating it if +// it doesn't exist yet. +func (ps *programSize) getPackage(path string) *packageSize { + if field, ok := ps.Packages[path]; ok { + return field + } + field := &packageSize{ + Program: ps, + Sub: map[string]*packageSize{}, + } + ps.Packages[path] = field + return field +} + // packageSize contains the size of a package, calculated from the linked object // file. type packageSize struct { @@ -61,6 +75,7 @@ type packageSize struct { ROData uint64 Data uint64 BSS uint64 + Sub map[string]*packageSize } // Flash usage in regular microcontrollers. @@ -79,6 +94,25 @@ func (ps *packageSize) FlashPercent() float64 { return float64(ps.Flash()) / float64(ps.Program.Flash()) * 100 } +// Add a single size data point to this package. +// This must only be called while calculating package size, not afterwards. +func (ps *packageSize) addSize(getField func(*packageSize, bool) *uint64, filename string, size uint64, isVariable bool) { + if size == 0 { + return + } + + // Add size for the package. + *getField(ps, isVariable) += size + + // Add size for file inside package. + sub, ok := ps.Sub[filename] + if !ok { + sub = &packageSize{Program: ps.Program} + ps.Sub[filename] = sub + } + *getField(sub, isVariable) += size +} + // A mapping of a single chunk of code or data to a file path. type addressLine struct { Address uint64 @@ -796,40 +830,32 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz program := &programSize{ Packages: sizes, } - getSize := func(path string) *packageSize { - if field, ok := sizes[path]; ok { - return field - } - field := &packageSize{Program: program} - sizes[path] = field - return field - } for _, section := range sections { switch section.Type { case memoryCode: - readSection(section, addresses, func(path string, size uint64, isVariable bool) { - field := getSize(path) + readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 { if isVariable { - field.ROData += size - } else { - field.Code += size + return &ps.ROData } + return &ps.Code }, packagePathMap) case memoryROData: - readSection(section, addresses, func(path string, size uint64, isVariable bool) { - getSize(path).ROData += size + readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 { + return &ps.ROData }, packagePathMap) case memoryData: - readSection(section, addresses, func(path string, size uint64, isVariable bool) { - getSize(path).Data += size + readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 { + return &ps.Data }, packagePathMap) case memoryBSS: - readSection(section, addresses, func(path string, size uint64, isVariable bool) { - getSize(path).BSS += size + readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 { + return &ps.BSS }, packagePathMap) case memoryStack: // We store the C stack as a pseudo-package. - getSize("C stack").BSS += section.Size + program.getPackage("C stack").addSize(func(ps *packageSize, isVariable bool) *uint64 { + return &ps.BSS + }, "", section.Size, false) } } @@ -844,8 +870,8 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz } // readSection determines for each byte in this section to which package it -// belongs. It reports this usage through the addSize callback. -func readSection(section memorySection, addresses []addressLine, addSize func(string, uint64, bool), packagePathMap map[string]string) { +// belongs. +func readSection(section memorySection, addresses []addressLine, program *programSize, getField func(*packageSize, bool) *uint64, packagePathMap map[string]string) { // The addr variable tracks at which address we are while going through this // section. We start at the beginning. addr := section.Address @@ -867,9 +893,9 @@ func readSection(section memorySection, addresses []addressLine, addSize func(st addrAligned := (addr + line.Align - 1) &^ (line.Align - 1) if line.Align > 1 && addrAligned >= line.Address { // It is, assume that's what causes the gap. - addSize("(padding)", line.Address-addr, true) + program.getPackage("(padding)").addSize(getField, "", line.Address-addr, true) } else { - addSize("(unknown)", line.Address-addr, false) + program.getPackage("(unknown)").addSize(getField, "", line.Address-addr, false) if sizesDebug { fmt.Printf("%08x..%08x %5d: unknown (gap), alignment=%d\n", addr, line.Address, line.Address-addr, line.Align) } @@ -891,7 +917,8 @@ func readSection(section memorySection, addresses []addressLine, addSize func(st length = line.Length - (addr - line.Address) } // Finally, mark this chunk of memory as used by the given package. - addSize(findPackagePath(line.File, packagePathMap), length, line.IsVariable) + packagePath, filename := findPackagePath(line.File, packagePathMap) + program.getPackage(packagePath).addSize(getField, filename, length, line.IsVariable) addr = line.Address + line.Length } if addr < sectionEnd { @@ -900,9 +927,9 @@ func readSection(section memorySection, addresses []addressLine, addSize func(st if section.Align > 1 && addrAligned >= sectionEnd { // The gap is caused by the section alignment. // For example, if a .rodata section ends with a non-aligned string. - addSize("(padding)", sectionEnd-addr, true) + program.getPackage("(padding)").addSize(getField, "", sectionEnd-addr, true) } else { - addSize("(unknown)", sectionEnd-addr, false) + program.getPackage("(unknown)").addSize(getField, "", sectionEnd-addr, false) if sizesDebug { fmt.Printf("%08x..%08x %5d: unknown (end), alignment=%d\n", addr, sectionEnd, sectionEnd-addr, section.Align) } @@ -912,17 +939,25 @@ func readSection(section memorySection, addresses []addressLine, addSize func(st // findPackagePath returns the Go package (or a pseudo package) for the given // path. It uses some heuristics, for example for some C libraries. -func findPackagePath(path string, packagePathMap map[string]string) string { +func findPackagePath(path string, packagePathMap map[string]string) (packagePath, filename string) { // Check whether this path is part of one of the compiled packages. packagePath, ok := packagePathMap[filepath.Dir(path)] - if !ok { + if ok { + // Directory is known as a Go package. + // Add the file itself as well. + filename = filepath.Base(path) + } else { if strings.HasPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")) { // Emit C libraries (in the lib subdirectory of TinyGo) as a single - // package, with a "C" prefix. For example: "C compiler-rt" for the - // compiler runtime library from LLVM. - packagePath = "C " + strings.Split(strings.TrimPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")), string(os.PathSeparator))[1] - } else if strings.HasPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "llvm-project")) { + // package, with a "C" prefix. For example: "C picolibc" for the + // baremetal libc. + libPath := strings.TrimPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")+string(os.PathSeparator)) + parts := strings.SplitN(libPath, string(os.PathSeparator), 2) + packagePath = "C " + parts[0] + filename = parts[1] + } else if prefix := filepath.Join(goenv.Get("TINYGOROOT"), "llvm-project", "compiler-rt"); strings.HasPrefix(path, prefix) { packagePath = "C compiler-rt" + filename = strings.TrimPrefix(path, prefix+string(os.PathSeparator)) } else if packageSymbolRegexp.MatchString(path) { // Parse symbol names like main$alloc or runtime$string. packagePath = path[:strings.LastIndex(path, "$")] @@ -945,9 +980,11 @@ func findPackagePath(path string, packagePathMap map[string]string) string { // fixed in the compiler. packagePath = "-" } else { - // This is some other path. Not sure what it is, so just emit its directory. - packagePath = filepath.Dir(path) // fallback + // This is some other path. Not sure what it is, so just emit its + // directory as a fallback. + packagePath = filepath.Dir(path) + filename = filepath.Base(path) } } - return packagePath + return } From 52983794d702af7a00833ae12b0d2e7175e46017 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 19 Dec 2024 11:01:25 +0100 Subject: [PATCH 335/444] all: version 0.35.0 --- CHANGELOG.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++++ goenv/version.go | 2 +- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41c5d05932..2f47bcea91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,71 @@ +0.35.0 +--- +* **general** + - update cmsis-svd library + - use default UART settings in the echo example + - `goenv`: also show git hash with custom build of TinyGo + - `goenv`: support parsing development versions of Go + - `main`: parse extldflags early so we can report the error message +* **compiler** + - `builder`: whitelist temporary directory env var for Clang invocation to fix Windows bug + - `builder`: fix cache paths in `-size=full` output + - `builder`: work around incorrectly escaped DWARF paths on Windows (Clang bug) + - `builder`: fix wasi-libc path names on Windows with `-size=full` + - `builder`: write HTML size report + - `cgo`: support C identifiers only referred to from within macros + - `cgo`: support function-like macros + - `cgo`: support errno value as second return parameter + - `cgo`: add support for `#cgo noescape` lines + - `compiler`: fix bug in interrupt lowering + - `compiler`: allow panic directly in `defer` + - `compiler`: fix wasmimport -> wasmexport in error message + - `compiler`: support `//go:noescape` pragma + - `compiler`: report error instead of crashing when instantiating a generic function without body + - `interp`: align created globals +* **standard library** + - `machine`: modify i2s interface/implementation to better match specification + - `os`: implement `StartProcess` + - `reflect`: add `Value.Clear` + - `reflect`: add interface support to `NumMethods` + - `reflect`: fix `AssignableTo` for named + non-named types + - `reflect`: implement `CanConvert` + - `reflect`: handle more cases in `Convert` + - `reflect`: fix Copy of non-pointer array with size > 64bits + - `runtime`: don't call sleepTicks with a negative duration + - `runtime`: optimize GC scanning (findHead) + - `runtime`: move constants into shared package + - `runtime`: add `runtime.fcntl` function for internal/syscall/unix + - `runtime`: heapptr only needs to be initialized once + - `runtime`: refactor scheduler (this fixes a few bugs with `-scheduler=none`) + - `runtime`: rewrite channel implementation to be smaller and more flexible + - `runtime`: use `SA_RESTART` when registering a signal for os/signal + - `runtime`: implement race-free signals using futexes + - `runtime`: run deferred functions in `Goexit` + - `runtime`: remove `Cond` which seems to be unused + - `runtime`: properly handle unix read on directory + - `runtime/trace`: stub all public methods + - `sync`: don't use volatile in `Mutex` + - `sync`: implement `WaitGroup` using a (pseudo)futex + - `sync`: make `Cond` parallelism-safe + - `syscall`: use wasi-libc tables for wasm/js target +* **targets** + - `mips`: fix a bug when scanning the stack + - `nintendoswitch`: get this target to compile again + - `rp2350`: add support for the new RP2350 + - `rp2040/rp2350` : make I2C implementation shared for rp2040/rp2350 + - `rp2040/rp2350` : make SPI implementation shared for rp2040/rp2350 + - `rp2040/rp2350` : make RNG implementation shared for rp2040/rp2350 + - `wasm`: revise and simplify wasmtime argument handling + - `wasm`: support `//go:wasmexport` functions after a call to `time.Sleep` + - `wasm`: correctly return from run() in wasm_exec.js + - `wasm`: call process.exit() when go.run() returns + - `windows`: don't return, exit via exit(0) instead to flush stdout buffer +* **boards** + - add support for the Tillitis TKey + - add support for the Raspberry Pi Pico2 (based on the RP2040) + - add support for Pimoroni Tiny2350 + + 0.34.0 --- * **general** diff --git a/goenv/version.go b/goenv/version.go index 4815d434a3..c02e38a7cc 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -10,7 +10,7 @@ import ( // Version of TinyGo. // Update this value before release of new version of software. -const version = "0.35.0-dev" +const version = "0.35.0" // Return TinyGo version, either in the form 0.30.0 or as a development version // (like 0.30.0-dev-abcd012). From b898916d521fefef68ab3d907a2b08f44ad4dc7d Mon Sep 17 00:00:00 2001 From: soypat Date: Sat, 21 Dec 2024 12:16:37 -0300 Subject: [PATCH 336/444] rp2350 cleanup: unexport internal USB and clock package variable, consts and types --- src/machine/machine_rp2040_rtc.go | 2 +- src/machine/machine_rp2040_usb.go | 82 +++++++++++++++---------------- src/machine/machine_rp2350_usb.go | 82 +++++++++++++++---------------- src/machine/machine_rp2_2040.go | 32 ++++++------ src/machine/machine_rp2_2350.go | 24 ++++----- src/machine/machine_rp2_clocks.go | 54 ++++++++++---------- 6 files changed, 138 insertions(+), 138 deletions(-) diff --git a/src/machine/machine_rp2040_rtc.go b/src/machine/machine_rp2040_rtc.go index 072432fc02..192e187c0a 100644 --- a/src/machine/machine_rp2040_rtc.go +++ b/src/machine/machine_rp2040_rtc.go @@ -97,7 +97,7 @@ func toAlarmTime(delay uint32) rtcTime { func (rtc *rtcType) setDivider() { // Get clk_rtc freq and make sure it is running - rtcFreq := configuredFreq[ClkRTC] + rtcFreq := configuredFreq[clkRTC] if rtcFreq == 0 { panic("can not set RTC divider, clock is not running") } diff --git a/src/machine/machine_rp2040_usb.go b/src/machine/machine_rp2040_usb.go index 6e6fd49623..ac7df98204 100644 --- a/src/machine/machine_rp2040_usb.go +++ b/src/machine/machine_rp2040_usb.go @@ -25,7 +25,7 @@ func (dev *USBDevice) Configure(config UARTConfig) { unresetBlockWait(rp.RESETS_RESET_USBCTRL) // Clear any previous state in dpram just in case - usbDPSRAM.clear() + _usbDPSRAM.clear() // Enable USB interrupt at processor rp.USBCTRL_REGS.INTE.Set(0) @@ -62,7 +62,7 @@ func handleUSBIRQ(intr interrupt.Interrupt) { // Setup packet received if (status & rp.USBCTRL_REGS_INTS_SETUP_REQ) > 0 { rp.USBCTRL_REGS.SIE_STATUS.Set(rp.USBCTRL_REGS_SIE_STATUS_SETUP_REC) - setup := usb.NewSetup(usbDPSRAM.setupBytes()) + setup := usb.NewSetup(_usbDPSRAM.setupBytes()) ok := false if (setup.BmRequestType & usb.REQUEST_TYPE) == usb.REQUEST_STANDARD { @@ -136,34 +136,34 @@ func handleUSBIRQ(intr interrupt.Interrupt) { func initEndpoint(ep, config uint32) { val := uint32(usbEpControlEnable) | uint32(usbEpControlInterruptPerBuff) - offset := ep*2*USBBufferLen + 0x100 + offset := ep*2*usbBufferLen + 0x100 val |= offset switch config { case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn: val |= usbEpControlEndpointTypeInterrupt - usbDPSRAM.EPxControl[ep].In.Set(val) + _usbDPSRAM.EPxControl[ep].In.Set(val) case usb.ENDPOINT_TYPE_BULK | usb.EndpointOut: val |= usbEpControlEndpointTypeBulk - usbDPSRAM.EPxControl[ep].Out.Set(val) - usbDPSRAM.EPxBufferControl[ep].Out.Set(USBBufferLen & usbBuf0CtrlLenMask) - usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + _usbDPSRAM.EPxControl[ep].Out.Set(val) + _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut: val |= usbEpControlEndpointTypeInterrupt - usbDPSRAM.EPxControl[ep].Out.Set(val) - usbDPSRAM.EPxBufferControl[ep].Out.Set(USBBufferLen & usbBuf0CtrlLenMask) - usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + _usbDPSRAM.EPxControl[ep].Out.Set(val) + _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) case usb.ENDPOINT_TYPE_BULK | usb.EndpointIn: val |= usbEpControlEndpointTypeBulk - usbDPSRAM.EPxControl[ep].In.Set(val) + _usbDPSRAM.EPxControl[ep].In.Set(val) case usb.ENDPOINT_TYPE_CONTROL: val |= usbEpControlEndpointTypeControl - usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBuf0CtrlData1Pid) - usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBuf0CtrlData1Pid) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) } } @@ -219,37 +219,37 @@ func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) { var b [cdcLineInfoSize]byte ep := 0 - for !usbDPSRAM.EPxBufferControl[ep].Out.HasBits(usbBuf0CtrlFull) { + for !_usbDPSRAM.EPxBufferControl[ep].Out.HasBits(usbBuf0CtrlFull) { // TODO: timeout } - ctrl := usbDPSRAM.EPxBufferControl[ep].Out.Get() - usbDPSRAM.EPxBufferControl[ep].Out.Set(USBBufferLen & usbBuf0CtrlLenMask) + ctrl := _usbDPSRAM.EPxBufferControl[ep].Out.Get() + _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask) sz := ctrl & usbBuf0CtrlLenMask - copy(b[:], usbDPSRAM.EPxBuffer[ep].Buffer0[:sz]) + copy(b[:], _usbDPSRAM.EPxBuffer[ep].Buffer0[:sz]) - usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid) - usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) return b, nil } func handleEndpointRx(ep uint32) []byte { - ctrl := usbDPSRAM.EPxBufferControl[ep].Out.Get() - usbDPSRAM.EPxBufferControl[ep].Out.Set(USBBufferLen & usbBuf0CtrlLenMask) + ctrl := _usbDPSRAM.EPxBufferControl[ep].Out.Get() + _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask) sz := ctrl & usbBuf0CtrlLenMask - return usbDPSRAM.EPxBuffer[ep].Buffer0[:sz] + return _usbDPSRAM.EPxBuffer[ep].Buffer0[:sz] } func handleEndpointRxComplete(ep uint32) { epXdata0[ep] = !epXdata0[ep] if epXdata0[ep] || ep == 0 { - usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid) } - usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) } func SendZlp() { @@ -269,8 +269,8 @@ func sendViaEPIn(ep uint32, data []byte, count int) { // Mark as full val |= usbBuf0CtrlFull - copy(usbDPSRAM.EPxBuffer[ep&0x7F].Buffer0[:], data[:count]) - usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) + copy(_usbDPSRAM.EPxBuffer[ep&0x7F].Buffer0[:], data[:count]) + _usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) } func sendStallViaEPIn(ep uint32) { @@ -279,41 +279,41 @@ func sendStallViaEPIn(ep uint32) { rp.USBCTRL_REGS.EP_STALL_ARM.Set(rp.USBCTRL_REGS_EP_STALL_ARM_EP0_IN) } val := uint32(usbBuf0CtrlFull) - usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) + _usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) val |= uint32(usbBuf0CtrlStall) - usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) + _usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) } -type USBDPSRAM struct { +type usbDPSRAM struct { // Note that EPxControl[0] is not EP0Control but 8-byte setup data. - EPxControl [16]USBEndpointControlRegister + EPxControl [16]usbEndpointControlRegister - EPxBufferControl [16]USBBufferControlRegister + EPxBufferControl [16]usbBufferControlRegister - EPxBuffer [16]USBBuffer + EPxBuffer [16]usbBuffer } -type USBEndpointControlRegister struct { +type usbEndpointControlRegister struct { In volatile.Register32 Out volatile.Register32 } -type USBBufferControlRegister struct { +type usbBufferControlRegister struct { In volatile.Register32 Out volatile.Register32 } -type USBBuffer struct { - Buffer0 [USBBufferLen]byte - Buffer1 [USBBufferLen]byte +type usbBuffer struct { + Buffer0 [usbBufferLen]byte + Buffer1 [usbBufferLen]byte } var ( - usbDPSRAM = (*USBDPSRAM)(unsafe.Pointer(uintptr(0x50100000))) + _usbDPSRAM = (*usbDPSRAM)(unsafe.Pointer(uintptr(0x50100000))) epXdata0 [16]bool setupBytes [8]byte ) -func (d *USBDPSRAM) setupBytes() []byte { +func (d *usbDPSRAM) setupBytes() []byte { data := d.EPxControl[usb.CONTROL_ENDPOINT].In.Get() setupBytes[0] = byte(data) @@ -330,7 +330,7 @@ func (d *USBDPSRAM) setupBytes() []byte { return setupBytes[:] } -func (d *USBDPSRAM) clear() { +func (d *usbDPSRAM) clear() { for i := 0; i < len(d.EPxControl); i++ { d.EPxControl[i].In.Set(0) d.EPxControl[i].Out.Set(0) @@ -373,5 +373,5 @@ const ( usbBuf0CtrlAvail = 0x00000400 usbBuf0CtrlLenMask = 0x000003FF - USBBufferLen = 64 + usbBufferLen = 64 ) diff --git a/src/machine/machine_rp2350_usb.go b/src/machine/machine_rp2350_usb.go index 9106b50f94..48fbbcbd05 100644 --- a/src/machine/machine_rp2350_usb.go +++ b/src/machine/machine_rp2350_usb.go @@ -25,7 +25,7 @@ func (dev *USBDevice) Configure(config UARTConfig) { unresetBlockWait(rp.RESETS_RESET_USBCTRL) // Clear any previous state in dpram just in case - usbDPSRAM.clear() + _usbDPSRAM.clear() // Enable USB interrupt at processor rp.USB.INTE.Set(0) @@ -65,7 +65,7 @@ func handleUSBIRQ(intr interrupt.Interrupt) { // Setup packet received if (status & rp.USB_INTS_SETUP_REQ) > 0 { rp.USB.SIE_STATUS.Set(rp.USB_SIE_STATUS_SETUP_REC) - setup := usb.NewSetup(usbDPSRAM.setupBytes()) + setup := usb.NewSetup(_usbDPSRAM.setupBytes()) ok := false if (setup.BmRequestType & usb.REQUEST_TYPE) == usb.REQUEST_STANDARD { @@ -139,34 +139,34 @@ func handleUSBIRQ(intr interrupt.Interrupt) { func initEndpoint(ep, config uint32) { val := uint32(usbEpControlEnable) | uint32(usbEpControlInterruptPerBuff) - offset := ep*2*USBBufferLen + 0x100 + offset := ep*2*usbBufferLen + 0x100 val |= offset switch config { case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn: val |= usbEpControlEndpointTypeInterrupt - usbDPSRAM.EPxControl[ep].In.Set(val) + _usbDPSRAM.EPxControl[ep].In.Set(val) case usb.ENDPOINT_TYPE_BULK | usb.EndpointOut: val |= usbEpControlEndpointTypeBulk - usbDPSRAM.EPxControl[ep].Out.Set(val) - usbDPSRAM.EPxBufferControl[ep].Out.Set(USBBufferLen & usbBuf0CtrlLenMask) - usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + _usbDPSRAM.EPxControl[ep].Out.Set(val) + _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut: val |= usbEpControlEndpointTypeInterrupt - usbDPSRAM.EPxControl[ep].Out.Set(val) - usbDPSRAM.EPxBufferControl[ep].Out.Set(USBBufferLen & usbBuf0CtrlLenMask) - usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + _usbDPSRAM.EPxControl[ep].Out.Set(val) + _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) case usb.ENDPOINT_TYPE_BULK | usb.EndpointIn: val |= usbEpControlEndpointTypeBulk - usbDPSRAM.EPxControl[ep].In.Set(val) + _usbDPSRAM.EPxControl[ep].In.Set(val) case usb.ENDPOINT_TYPE_CONTROL: val |= usbEpControlEndpointTypeControl - usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBuf0CtrlData1Pid) - usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBuf0CtrlData1Pid) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) } } @@ -222,37 +222,37 @@ func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) { var b [cdcLineInfoSize]byte ep := 0 - for !usbDPSRAM.EPxBufferControl[ep].Out.HasBits(usbBuf0CtrlFull) { + for !_usbDPSRAM.EPxBufferControl[ep].Out.HasBits(usbBuf0CtrlFull) { // TODO: timeout } - ctrl := usbDPSRAM.EPxBufferControl[ep].Out.Get() - usbDPSRAM.EPxBufferControl[ep].Out.Set(USBBufferLen & usbBuf0CtrlLenMask) + ctrl := _usbDPSRAM.EPxBufferControl[ep].Out.Get() + _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask) sz := ctrl & usbBuf0CtrlLenMask - copy(b[:], usbDPSRAM.EPxBuffer[ep].Buffer0[:sz]) + copy(b[:], _usbDPSRAM.EPxBuffer[ep].Buffer0[:sz]) - usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid) - usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) return b, nil } func handleEndpointRx(ep uint32) []byte { - ctrl := usbDPSRAM.EPxBufferControl[ep].Out.Get() - usbDPSRAM.EPxBufferControl[ep].Out.Set(USBBufferLen & usbBuf0CtrlLenMask) + ctrl := _usbDPSRAM.EPxBufferControl[ep].Out.Get() + _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask) sz := ctrl & usbBuf0CtrlLenMask - return usbDPSRAM.EPxBuffer[ep].Buffer0[:sz] + return _usbDPSRAM.EPxBuffer[ep].Buffer0[:sz] } func handleEndpointRxComplete(ep uint32) { epXdata0[ep] = !epXdata0[ep] if epXdata0[ep] || ep == 0 { - usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid) } - usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) } func SendZlp() { @@ -272,8 +272,8 @@ func sendViaEPIn(ep uint32, data []byte, count int) { // Mark as full val |= usbBuf0CtrlFull - copy(usbDPSRAM.EPxBuffer[ep&0x7F].Buffer0[:], data[:count]) - usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) + copy(_usbDPSRAM.EPxBuffer[ep&0x7F].Buffer0[:], data[:count]) + _usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) } func sendStallViaEPIn(ep uint32) { @@ -282,41 +282,41 @@ func sendStallViaEPIn(ep uint32) { rp.USB.EP_STALL_ARM.Set(rp.USB_EP_STALL_ARM_EP0_IN) } val := uint32(usbBuf0CtrlFull) - usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) + _usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) val |= uint32(usbBuf0CtrlStall) - usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) + _usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) } -type USBDPSRAM struct { +type usbDPSRAM struct { // Note that EPxControl[0] is not EP0Control but 8-byte setup data. - EPxControl [16]USBEndpointControlRegister + EPxControl [16]usbEndpointControlRegister - EPxBufferControl [16]USBBufferControlRegister + EPxBufferControl [16]usbBufferControlRegister - EPxBuffer [16]USBBuffer + EPxBuffer [16]usbBuffer } -type USBEndpointControlRegister struct { +type usbEndpointControlRegister struct { In volatile.Register32 Out volatile.Register32 } -type USBBufferControlRegister struct { +type usbBufferControlRegister struct { In volatile.Register32 Out volatile.Register32 } -type USBBuffer struct { - Buffer0 [USBBufferLen]byte - Buffer1 [USBBufferLen]byte +type usbBuffer struct { + Buffer0 [usbBufferLen]byte + Buffer1 [usbBufferLen]byte } var ( - usbDPSRAM = (*USBDPSRAM)(unsafe.Pointer(uintptr(0x50100000))) + _usbDPSRAM = (*usbDPSRAM)(unsafe.Pointer(uintptr(0x50100000))) epXdata0 [16]bool setupBytes [8]byte ) -func (d *USBDPSRAM) setupBytes() []byte { +func (d *usbDPSRAM) setupBytes() []byte { data := d.EPxControl[usb.CONTROL_ENDPOINT].In.Get() setupBytes[0] = byte(data) @@ -333,7 +333,7 @@ func (d *USBDPSRAM) setupBytes() []byte { return setupBytes[:] } -func (d *USBDPSRAM) clear() { +func (d *usbDPSRAM) clear() { for i := 0; i < len(d.EPxControl); i++ { d.EPxControl[i].In.Set(0) d.EPxControl[i].Out.Set(0) @@ -376,5 +376,5 @@ const ( usbBuf0CtrlAvail = 0x00000400 usbBuf0CtrlLenMask = 0x000003FF - USBBufferLen = 64 + usbBufferLen = 64 ) diff --git a/src/machine/machine_rp2_2040.go b/src/machine/machine_rp2_2040.go index c82b084831..484d8e923a 100644 --- a/src/machine/machine_rp2_2040.go +++ b/src/machine/machine_rp2_2040.go @@ -46,26 +46,26 @@ const ( ) const ( - ClkGPOUT0 clockIndex = iota // GPIO Muxing 0 - ClkGPOUT1 // GPIO Muxing 1 - ClkGPOUT2 // GPIO Muxing 2 - ClkGPOUT3 // GPIO Muxing 3 - ClkRef // Watchdog and timers reference clock - ClkSys // Processors, bus fabric, memory, memory mapped registers - ClkPeri // Peripheral clock for UART and SPI - ClkUSB // USB clock - ClkADC // ADC clock - ClkRTC // Real time clock - NumClocks + clkGPOUT0 clockIndex = iota // GPIO Muxing 0 + clkGPOUT1 // GPIO Muxing 1 + clkGPOUT2 // GPIO Muxing 2 + clkGPOUT3 // GPIO Muxing 3 + clkRef // Watchdog and timers reference clock + clkSys // Processors, bus fabric, memory, memory mapped registers + clkPeri // Peripheral clock for UART and SPI + clkUSB // USB clock + clkADC // ADC clock + clkRTC // Real time clock + numClocks ) -func CalcClockDiv(srcFreq, freq uint32) uint32 { +func calcClockDiv(srcFreq, freq uint32) uint32 { // Div register is 24.8 int.frac divider so multiply by 2^8 (left shift by 8) return uint32((uint64(srcFreq) << 8) / uint64(freq)) } type clocksType struct { - clk [NumClocks]clockType + clk [numClocks]clockType resus struct { ctrl volatile.Register32 status volatile.Register32 @@ -180,9 +180,9 @@ func irqSetMask(mask uint32, enabled bool) { } func (clks *clocksType) initRTC() { - // ClkRTC = pllUSB (48MHz) / 1024 = 46875Hz - clkrtc := clks.clock(ClkRTC) - clkrtc.configure(0, // No GLMUX + // clkRTC = pllUSB (48MHz) / 1024 = 46875Hz + crtc := clks.clock(clkRTC) + crtc.configure(0, // No GLMUX rp.CLOCKS_CLK_RTC_CTRL_AUXSRC_CLKSRC_PLL_USB, 48*MHz, 46875) diff --git a/src/machine/machine_rp2_2350.go b/src/machine/machine_rp2_2350.go index 54fc62b47b..4e12bebe35 100644 --- a/src/machine/machine_rp2_2350.go +++ b/src/machine/machine_rp2_2350.go @@ -49,26 +49,26 @@ const ( ) const ( - ClkGPOUT0 clockIndex = iota // GPIO Muxing 0 - ClkGPOUT1 // GPIO Muxing 1 - ClkGPOUT2 // GPIO Muxing 2 - ClkGPOUT3 // GPIO Muxing 3 - ClkRef // Watchdog and timers reference clock - ClkSys // Processors, bus fabric, memory, memory mapped registers - ClkPeri // Peripheral clock for UART and SPI + clkGPOUT0 clockIndex = iota // GPIO Muxing 0 + clkGPOUT1 // GPIO Muxing 1 + clkGPOUT2 // GPIO Muxing 2 + clkGPOUT3 // GPIO Muxing 3 + clkRef // Watchdog and timers reference clock + clkSys // Processors, bus fabric, memory, memory mapped registers + clkPeri // Peripheral clock for UART and SPI ClkHSTX // High speed interface - ClkUSB // USB clock - ClkADC // ADC clock - NumClocks + clkUSB // USB clock + clkADC // ADC clock + numClocks ) -func CalcClockDiv(srcFreq, freq uint32) uint32 { +func calcClockDiv(srcFreq, freq uint32) uint32 { // Div register is 4.16 int.frac divider so multiply by 2^16 (left shift by 16) return uint32((uint64(srcFreq) << 16) / uint64(freq)) } type clocksType struct { - clk [NumClocks]clockType + clk [numClocks]clockType dftclk_xosc_ctrl volatile.Register32 dftclk_rosc_ctrl volatile.Register32 dftclk_lposc_ctrl volatile.Register32 diff --git a/src/machine/machine_rp2_clocks.go b/src/machine/machine_rp2_clocks.go index ad2c6517fa..cc152a7f82 100644 --- a/src/machine/machine_rp2_clocks.go +++ b/src/machine/machine_rp2_clocks.go @@ -41,7 +41,7 @@ type fc struct { var clocks = (*clocksType)(unsafe.Pointer(rp.CLOCKS)) -var configuredFreq [NumClocks]uint32 +var configuredFreq [numClocks]uint32 type clock struct { *clockType @@ -68,7 +68,7 @@ func (clks *clocksType) clock(cix clockIndex) clock { // // Not all clocks have both types of mux. func (clk *clock) hasGlitchlessMux() bool { - return clk.cix == ClkSys || clk.cix == ClkRef + return clk.cix == clkSys || clk.cix == clkRef } // configure configures the clock by selecting the main clock source src @@ -80,7 +80,7 @@ func (clk *clock) configure(src, auxsrc, srcFreq, freq uint32) { panic("clock frequency cannot be greater than source frequency") } - div := CalcClockDiv(srcFreq, freq) + div := calcClockDiv(srcFreq, freq) // If increasing divisor, set divisor before source. Otherwise set source // before divisor. This avoids a momentary overspeed when e.g. switching @@ -99,16 +99,16 @@ func (clk *clock) configure(src, auxsrc, srcFreq, freq uint32) { } else // If no glitchless mux, cleanly stop the clock to avoid glitches // propagating when changing aux mux. Note it would be a really bad idea - // to do this on one of the glitchless clocks (ClkSys, ClkRef). + // to do this on one of the glitchless clocks (clkSys, clkRef). { - // Disable clock. On ClkRef and ClkSys this does nothing, + // Disable clock. On clkRef and ClkSys this does nothing, // all other clocks have the ENABLE bit in the same position. clk.ctrl.ClearBits(rp.CLOCKS_CLK_GPOUT0_CTRL_ENABLE_Msk) if configuredFreq[clk.cix] > 0 { // Delay for 3 cycles of the target clock, for ENABLE propagation. // Note XOSC_COUNT is not helpful here because XOSC is not // necessarily running, nor is timer... so, 3 cycles per loop: - delayCyc := configuredFreq[ClkSys]/configuredFreq[clk.cix] + 1 + delayCyc := configuredFreq[clkSys]/configuredFreq[clk.cix] + 1 for delayCyc != 0 { // This could be done more efficiently but TinyGo inline // assembly is not yet capable enough to express that. In the @@ -130,7 +130,7 @@ func (clk *clock) configure(src, auxsrc, srcFreq, freq uint32) { } } - // Enable clock. On ClkRef and ClkSys this does nothing, + // Enable clock. On clkRef and clkSys this does nothing, // all other clocks have the ENABLE bit in the same position. clk.ctrl.SetBits(rp.CLOCKS_CLK_GPOUT0_CTRL_ENABLE) @@ -157,12 +157,12 @@ func (clks *clocksType) init() { xosc.init() // Before we touch PLLs, switch sys and ref cleanly away from their aux sources. - clks.clk[ClkSys].ctrl.ClearBits(rp.CLOCKS_CLK_SYS_CTRL_SRC_Msk) - for !clks.clk[ClkSys].selected.HasBits(0x1) { + clks.clk[clkSys].ctrl.ClearBits(rp.CLOCKS_CLK_SYS_CTRL_SRC_Msk) + for !clks.clk[clkSys].selected.HasBits(0x1) { } - clks.clk[ClkRef].ctrl.ClearBits(rp.CLOCKS_CLK_REF_CTRL_SRC_Msk) - for !clks.clk[ClkRef].selected.HasBits(0x1) { + clks.clk[clkRef].ctrl.ClearBits(rp.CLOCKS_CLK_REF_CTRL_SRC_Msk) + for !clks.clk[clkRef].selected.HasBits(0x1) { } // Configure PLLs @@ -173,41 +173,41 @@ func (clks *clocksType) init() { pllUSB.init(1, 480*MHz, 5, 2) // Configure clocks - // ClkRef = xosc (12MHz) / 1 = 12MHz - clkref := clks.clock(ClkRef) - clkref.configure(rp.CLOCKS_CLK_REF_CTRL_SRC_XOSC_CLKSRC, + // clkRef = xosc (12MHz) / 1 = 12MHz + cref := clks.clock(clkRef) + cref.configure(rp.CLOCKS_CLK_REF_CTRL_SRC_XOSC_CLKSRC, 0, // No aux mux 12*MHz, 12*MHz) - // ClkSys = pllSys (125MHz) / 1 = 125MHz - clksys := clks.clock(ClkSys) - clksys.configure(rp.CLOCKS_CLK_SYS_CTRL_SRC_CLKSRC_CLK_SYS_AUX, + // clkSys = pllSys (125MHz) / 1 = 125MHz + csys := clks.clock(clkSys) + csys.configure(rp.CLOCKS_CLK_SYS_CTRL_SRC_CLKSRC_CLK_SYS_AUX, rp.CLOCKS_CLK_SYS_CTRL_AUXSRC_CLKSRC_PLL_SYS, 125*MHz, 125*MHz) - // ClkUSB = pllUSB (48MHz) / 1 = 48MHz - clkusb := clks.clock(ClkUSB) - clkusb.configure(0, // No GLMUX + // clkUSB = pllUSB (48MHz) / 1 = 48MHz + cusb := clks.clock(clkUSB) + cusb.configure(0, // No GLMUX rp.CLOCKS_CLK_USB_CTRL_AUXSRC_CLKSRC_PLL_USB, 48*MHz, 48*MHz) - // ClkADC = pllUSB (48MHZ) / 1 = 48MHz - clkadc := clks.clock(ClkADC) - clkadc.configure(0, // No GLMUX + // clkADC = pllUSB (48MHZ) / 1 = 48MHz + cadc := clks.clock(clkADC) + cadc.configure(0, // No GLMUX rp.CLOCKS_CLK_ADC_CTRL_AUXSRC_CLKSRC_PLL_USB, 48*MHz, 48*MHz) clks.initRTC() - // ClkPeri = ClkSys. Used as reference clock for Peripherals. + // clkPeri = clkSys. Used as reference clock for Peripherals. // No dividers so just select and enable. - // Normally choose ClkSys or ClkUSB. - clkperi := clks.clock(ClkPeri) - clkperi.configure(0, + // Normally choose clkSys or clkUSB. + cperi := clks.clock(clkPeri) + cperi.configure(0, rp.CLOCKS_CLK_PERI_CTRL_AUXSRC_CLK_SYS, 125*MHz, 125*MHz) From 0426a5f90247b29a8960702e680b23b66f3af17d Mon Sep 17 00:00:00 2001 From: sago35 Date: Sun, 22 Dec 2024 14:38:12 +0900 Subject: [PATCH 337/444] goenv: update to new v0.36.0 development version --- goenv/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/goenv/version.go b/goenv/version.go index c02e38a7cc..5592866a9c 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -10,7 +10,7 @@ import ( // Version of TinyGo. // Update this value before release of new version of software. -const version = "0.35.0" +const version = "0.36.0-dev" // Return TinyGo version, either in the form 0.30.0 or as a development version // (like 0.30.0-dev-abcd012). From 3efc6340ad976ed80d1373076d8553aae88ca861 Mon Sep 17 00:00:00 2001 From: sago35 Date: Sat, 4 Jan 2025 14:29:40 +0900 Subject: [PATCH 338/444] main: update to use `Get-CimInstance` as `wmic` is being deprecated --- main.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 9f0cc631a9..dee0165582 100644 --- a/main.go +++ b/main.go @@ -1062,9 +1062,8 @@ func findFATMounts(options *compileopts.Options) ([]mountPoint, error) { return points, nil case "windows": // Obtain a list of all currently mounted volumes. - cmd := executeCommand(options, "wmic", - "PATH", "Win32_LogicalDisk", - "get", "DeviceID,VolumeName,FileSystem,DriveType") + cmd := executeCommand(options, "powershell", "-c", + "Get-CimInstance -ClassName Win32_LogicalDisk | Select-Object DeviceID, DriveType, FileSystem, VolumeName") var out bytes.Buffer cmd.Stdout = &out err := cmd.Run() From ff15b474fd9ea9722253ffca6661346852a0a8c0 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 31 Dec 2024 00:52:49 -0500 Subject: [PATCH 339/444] Remove unnecessary executable permissions These files are plain text and have no shebang. --- src/internal/wasi/cli/v0.2.0/command/command.wit | 0 src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go | 0 src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go | 0 src/internal/wasi/cli/v0.2.0/run/run.wasm.go | 0 src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go | 0 src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go | 0 src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go | 0 src/internal/wasi/cli/v0.2.0/terminal-input/terminalinput.wasm.go | 0 .../wasi/cli/v0.2.0/terminal-output/terminaloutput.wasm.go | 0 .../wasi/cli/v0.2.0/terminal-stderr/terminalstderr.wasm.go | 0 src/internal/wasi/cli/v0.2.0/terminal-stdin/terminalstdin.wasm.go | 0 .../wasi/cli/v0.2.0/terminal-stdout/terminalstdout.wasm.go | 0 .../wasi/clocks/v0.2.0/monotonic-clock/monotonicclock.wasm.go | 0 src/internal/wasi/clocks/v0.2.0/wall-clock/wallclock.wasm.go | 0 src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go | 0 src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go | 0 src/internal/wasi/io/v0.2.0/error/ioerror.wasm.go | 0 src/internal/wasi/io/v0.2.0/poll/poll.wasm.go | 0 src/internal/wasi/io/v0.2.0/streams/streams.wasm.go | 0 .../wasi/random/v0.2.0/insecure-seed/insecureseed.wasm.go | 0 src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go | 0 src/internal/wasi/random/v0.2.0/random/random.wasm.go | 0 .../wasi/sockets/v0.2.0/instance-network/instancenetwork.wasm.go | 0 .../wasi/sockets/v0.2.0/ip-name-lookup/ipnamelookup.wasm.go | 0 src/internal/wasi/sockets/v0.2.0/network/network.wasm.go | 0 .../wasi/sockets/v0.2.0/tcp-create-socket/tcpcreatesocket.wasm.go | 0 src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go | 0 .../wasi/sockets/v0.2.0/udp-create-socket/udpcreatesocket.wasm.go | 0 src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go | 0 29 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/command/command.wit mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/run/run.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-input/terminalinput.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-output/terminaloutput.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-stderr/terminalstderr.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-stdin/terminalstdin.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-stdout/terminalstdout.wasm.go mode change 100755 => 100644 src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonicclock.wasm.go mode change 100755 => 100644 src/internal/wasi/clocks/v0.2.0/wall-clock/wallclock.wasm.go mode change 100755 => 100644 src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go mode change 100755 => 100644 src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go mode change 100755 => 100644 src/internal/wasi/io/v0.2.0/error/ioerror.wasm.go mode change 100755 => 100644 src/internal/wasi/io/v0.2.0/poll/poll.wasm.go mode change 100755 => 100644 src/internal/wasi/io/v0.2.0/streams/streams.wasm.go mode change 100755 => 100644 src/internal/wasi/random/v0.2.0/insecure-seed/insecureseed.wasm.go mode change 100755 => 100644 src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go mode change 100755 => 100644 src/internal/wasi/random/v0.2.0/random/random.wasm.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/instance-network/instancenetwork.wasm.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ipnamelookup.wasm.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/network/network.wasm.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcpcreatesocket.wasm.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/udp-create-socket/udpcreatesocket.wasm.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go diff --git a/src/internal/wasi/cli/v0.2.0/command/command.wit b/src/internal/wasi/cli/v0.2.0/command/command.wit old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go b/src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go b/src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/run/run.wasm.go b/src/internal/wasi/cli/v0.2.0/run/run.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-input/terminalinput.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-input/terminalinput.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-output/terminaloutput.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-output/terminaloutput.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminalstderr.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminalstderr.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminalstdin.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminalstdin.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminalstdout.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminalstdout.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonicclock.wasm.go b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonicclock.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/clocks/v0.2.0/wall-clock/wallclock.wasm.go b/src/internal/wasi/clocks/v0.2.0/wall-clock/wallclock.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go b/src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/io/v0.2.0/error/ioerror.wasm.go b/src/internal/wasi/io/v0.2.0/error/ioerror.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/io/v0.2.0/poll/poll.wasm.go b/src/internal/wasi/io/v0.2.0/poll/poll.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/io/v0.2.0/streams/streams.wasm.go b/src/internal/wasi/io/v0.2.0/streams/streams.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/random/v0.2.0/insecure-seed/insecureseed.wasm.go b/src/internal/wasi/random/v0.2.0/insecure-seed/insecureseed.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go b/src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/random/v0.2.0/random/random.wasm.go b/src/internal/wasi/random/v0.2.0/random/random.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/instance-network/instancenetwork.wasm.go b/src/internal/wasi/sockets/v0.2.0/instance-network/instancenetwork.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ipnamelookup.wasm.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ipnamelookup.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/network/network.wasm.go b/src/internal/wasi/sockets/v0.2.0/network/network.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcpcreatesocket.wasm.go b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcpcreatesocket.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udpcreatesocket.wasm.go b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udpcreatesocket.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go b/src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go old mode 100755 new mode 100644 From 16b0f4cc9af91e4996e1e4c10901a7af8fdf2ccc Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sun, 5 Jan 2025 02:20:33 -0500 Subject: [PATCH 340/444] builder: Fix parsing of external ld.lld error messages If `ld.lld` is a version-specific binary (e.g., `ld.lld-18`), then its error messages include the version. The parsing previously incorrectly assumed it would be unversioned. --- builder/tools.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/builder/tools.go b/builder/tools.go index 66329a099b..9d15e4ccaa 100644 --- a/builder/tools.go +++ b/builder/tools.go @@ -114,8 +114,8 @@ func parseLLDErrors(text string) error { // Check for undefined symbols. // This can happen in some cases like with CGo and //go:linkname tricker. - if matches := regexp.MustCompile(`^ld.lld: error: undefined symbol: (.*)\n`).FindStringSubmatch(message); matches != nil { - symbolName := matches[1] + if matches := regexp.MustCompile(`^ld.lld(-[0-9]+)?: error: undefined symbol: (.*)\n`).FindStringSubmatch(message); matches != nil { + symbolName := matches[2] for _, line := range strings.Split(message, "\n") { matches := regexp.MustCompile(`referenced by .* \(((.*):([0-9]+))\)`).FindStringSubmatch(line) if matches != nil { @@ -134,9 +134,9 @@ func parseLLDErrors(text string) error { } // Check for flash/RAM overflow. - if matches := regexp.MustCompile(`^ld.lld: error: section '(.*?)' will not fit in region '(.*?)': overflowed by ([0-9]+) bytes$`).FindStringSubmatch(message); matches != nil { - region := matches[2] - n, err := strconv.ParseUint(matches[3], 10, 64) + if matches := regexp.MustCompile(`^ld.lld(-[0-9]+)?: error: section '(.*?)' will not fit in region '(.*?)': overflowed by ([0-9]+) bytes$`).FindStringSubmatch(message); matches != nil { + region := matches[3] + n, err := strconv.ParseUint(matches[4], 10, 64) if err != nil { // Should not happen at all (unless it overflows an uint64 for some reason). continue From 217f677bbe8ee334d5694ec22f622add62383544 Mon Sep 17 00:00:00 2001 From: Scott Feldman Date: Sat, 4 Jan 2025 23:22:56 -0800 Subject: [PATCH 341/444] crypto/tls: add Dialer.DialContext() to fix websocket client The latest golang.org/x/net websocket package v0.33.0 needs Dialer.DailContext in crypto/tls. This PR adds it. Apps using golang.org/x/net are encouraged to use v0.33.0 to address: CVE-2024-45338: Non-linear parsing of case-insensitive content in golang.org/x/net/html CVE-2023-45288: net/http, x/net/http2: close connections when receiving too many headers Tested with examples/net/websocket/dial. --- src/crypto/tls/tls.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/crypto/tls/tls.go b/src/crypto/tls/tls.go index 1520c30fca..6fdedc39fc 100644 --- a/src/crypto/tls/tls.go +++ b/src/crypto/tls/tls.go @@ -101,7 +101,13 @@ type Dialer struct { // // The returned Conn, if any, will always be of type *Conn. func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { - return nil, errors.New("tls:DialContext not implemented") + switch network { + case "tcp", "tcp4": + default: + return nil, fmt.Errorf("Network %s not supported", network) + } + + return net.DialTLS(addr) } // LoadX509KeyPair reads and parses a public/private key pair from a pair From 194438cdd687d8952af3ec9f3fe56a84808f429a Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sun, 12 Jan 2025 13:56:46 +0100 Subject: [PATCH 342/444] fix: correctly handle calls for GetRNG() when being made from nrf devices with SoftDevice enabled. Signed-off-by: deadprogram --- src/machine/machine_nrf.go | 4 +-- src/machine/machine_nrf_bare.go | 9 +++++ src/machine/machine_nrf_sd.go | 59 +++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 src/machine/machine_nrf_bare.go create mode 100644 src/machine/machine_nrf_sd.go diff --git a/src/machine/machine_nrf.go b/src/machine/machine_nrf.go index 9457007115..4da6645352 100644 --- a/src/machine/machine_nrf.go +++ b/src/machine/machine_nrf.go @@ -318,9 +318,9 @@ func (i2c *I2C) signalStop() error { var rngStarted = false -// GetRNG returns 32 bits of non-deterministic random data based on internal thermal noise. +// getRNG returns 32 bits of non-deterministic random data based on internal thermal noise. // According to Nordic's documentation, the random output is suitable for cryptographic purposes. -func GetRNG() (ret uint32, err error) { +func getRNG() (ret uint32, err error) { // There's no apparent way to check the status of the RNG peripheral's task, so simply start it // to avoid deadlocking while waiting for output. if !rngStarted { diff --git a/src/machine/machine_nrf_bare.go b/src/machine/machine_nrf_bare.go new file mode 100644 index 0000000000..b94886ed91 --- /dev/null +++ b/src/machine/machine_nrf_bare.go @@ -0,0 +1,9 @@ +//go:build nrf && !softdevice + +package machine + +// GetRNG returns 32 bits of non-deterministic random data based on internal thermal noise. +// According to Nordic's documentation, the random output is suitable for cryptographic purposes. +func GetRNG() (ret uint32, err error) { + return getRNG() +} diff --git a/src/machine/machine_nrf_sd.go b/src/machine/machine_nrf_sd.go new file mode 100644 index 0000000000..b816e62ee0 --- /dev/null +++ b/src/machine/machine_nrf_sd.go @@ -0,0 +1,59 @@ +//go:build nrf && softdevice + +package machine + +import ( + "device/arm" + "device/nrf" + + "errors" +) + +// avoid a heap allocation in GetRNG. +var ( + softdeviceEnabled uint8 + bytesAvailable uint8 + buf [4]uint8 + + errNoSoftDeviceSupport = errors.New("rng: softdevice not supported on this device") + errNotEnoughRandomData = errors.New("rng: not enough random data available") +) + +// GetRNG returns 32 bits of non-deterministic random data based on internal thermal noise. +// According to Nordic's documentation, the random output is suitable for cryptographic purposes. +func GetRNG() (ret uint32, err error) { + // First check whether the SoftDevice is enabled. + // sd_rand_application_bytes_available_get cannot be called when the SoftDevice is not enabled. + arm.SVCall1(0x12, &softdeviceEnabled) // sd_softdevice_is_enabled + + if softdeviceEnabled == 0 { + return getRNG() + } + + // call into the SoftDevice to get random data bytes available + switch nrf.Device { + case "nrf51": + // sd_rand_application_bytes_available_get: SOC_SVC_BASE_NOT_AVAILABLE + 4 + arm.SVCall1(0x2B+4, &bytesAvailable) + case "nrf52", "nrf52840", "nrf52833": + // sd_rand_application_bytes_available_get: SOC_SVC_BASE_NOT_AVAILABLE + 4 + arm.SVCall1(0x2C+4, &bytesAvailable) + default: + return 0, errNoSoftDeviceSupport + } + + if bytesAvailable < 4 { + return 0, errNotEnoughRandomData + } + + switch nrf.Device { + case "nrf51": + // sd_rand_application_vector_get: SOC_SVC_BASE_NOT_AVAILABLE + 5 + arm.SVCall2(0x2B+5, &buf, 4) + case "nrf52", "nrf52840", "nrf52833": + // sd_rand_application_vector_get: SOC_SVC_BASE_NOT_AVAILABLE + 5 + arm.SVCall2(0x2C+5, &buf, 4) + } + + return uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24, nil +} From 29d2719badfa4f8edbbe2bdaae3637d0734039de Mon Sep 17 00:00:00 2001 From: Yurii Soldak Date: Mon, 13 Jan 2025 17:21:23 +0100 Subject: [PATCH 343/444] example: naive debouncing for pininterrupt example (#2233) --- builder/sizes_test.go | 2 +- src/examples/pininterrupt/pininterrupt.go | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/builder/sizes_test.go b/builder/sizes_test.go index a1be28f274..801a1184e9 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -44,7 +44,7 @@ func TestBinarySize(t *testing.T) { // microcontrollers {"hifive1b", "examples/echo", 4600, 280, 0, 2268}, {"microbit", "examples/serial", 2908, 388, 8, 2272}, - {"wioterminal", "examples/pininterrupt", 6140, 1484, 116, 6824}, + {"wioterminal", "examples/pininterrupt", 7286, 1486, 116, 6912}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the diff --git a/src/examples/pininterrupt/pininterrupt.go b/src/examples/pininterrupt/pininterrupt.go index e13ba5eb3a..df3a0a9056 100644 --- a/src/examples/pininterrupt/pininterrupt.go +++ b/src/examples/pininterrupt/pininterrupt.go @@ -3,7 +3,7 @@ package main // This example demonstrates how to use pin change interrupts. // // This is only an example and should not be copied directly in any serious -// circuit, because it lacks an important feature: debouncing. +// circuit, because it only naively implements an important feature: debouncing. // See: https://en.wikipedia.org/wiki/Switch#Contact_bounce import ( @@ -15,6 +15,8 @@ const ( led = machine.LED ) +var lastPress time.Time + func main() { // Configure the LED, defaulting to on (usually setting the pin to low will @@ -29,6 +31,13 @@ func main() { // Set an interrupt on this pin. err := button.SetInterrupt(buttonPinChange, func(machine.Pin) { + + // Ignore events that are too close to the last registered press (debouncing) + if time.Since(lastPress) < 100*time.Millisecond { + return + } + lastPress = time.Now() + led.Set(!led.Get()) }) if err != nil { From b15adf23f8d76b1a40e276fcbf55adcb85f09780 Mon Sep 17 00:00:00 2001 From: Volodymyr Pobochii Date: Tue, 14 Jan 2025 02:53:40 +0200 Subject: [PATCH 344/444] machine: add support for waveshare-rp2040-tiny (#4683) --- GNUmakefile | 2 + src/machine/board_waveshare_rp2040_tiny.go | 121 +++++++++++++++++++++ targets/waveshare-rp2040-tiny.json | 13 +++ 3 files changed, 136 insertions(+) create mode 100644 src/machine/board_waveshare_rp2040_tiny.go create mode 100644 targets/waveshare-rp2040-tiny.json diff --git a/GNUmakefile b/GNUmakefile index efc70b0ae8..099aab0ced 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -745,6 +745,8 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=tiny2350 examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=waveshare-rp2040-tiny examples/echo + @$(MD5SUM) test.hex # test pwm $(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm @$(MD5SUM) test.hex diff --git a/src/machine/board_waveshare_rp2040_tiny.go b/src/machine/board_waveshare_rp2040_tiny.go new file mode 100644 index 0000000000..a3ef354041 --- /dev/null +++ b/src/machine/board_waveshare_rp2040_tiny.go @@ -0,0 +1,121 @@ +//go:build waveshare_rp2040_tiny + +// This file contains the pin mappings for the Waveshare RP2040-Tiny boards. +// +// Waveshare RP2040-Tiny is a microcontroller using the Raspberry Pi RP2040 chip. +// +// - https://www.waveshare.com/wiki/RP2040-Tiny +package machine + +// Digital Pins +const ( + GP0 Pin = GPIO0 + GP1 Pin = GPIO1 + GP2 Pin = GPIO2 + GP3 Pin = GPIO3 + GP4 Pin = GPIO4 + GP5 Pin = GPIO5 + GP6 Pin = GPIO6 + GP7 Pin = GPIO7 + GP8 Pin = GPIO8 + GP9 Pin = GPIO9 + GP10 Pin = GPIO10 + GP11 Pin = GPIO11 + GP12 Pin = GPIO12 + GP13 Pin = GPIO13 + GP14 Pin = GPIO14 + GP15 Pin = GPIO15 + GP16 Pin = GPIO16 + GP17 Pin = NoPin + GP18 Pin = NoPin + GP19 Pin = NoPin + GP20 Pin = NoPin + GP21 Pin = NoPin + GP22 Pin = NoPin + GP23 Pin = NoPin + GP24 Pin = GPIO24 + GP25 Pin = GPIO25 + GP26 Pin = GPIO26 + GP27 Pin = GPIO27 + GP28 Pin = GPIO28 + GP29 Pin = GPIO29 +) + +// Analog pins +const ( + A0 Pin = GP26 + A1 Pin = GP27 + A2 Pin = GP28 + A3 Pin = GP29 +) + +// Onboard LEDs +const ( + LED = GP16 + WS2812 = GP16 +) + +// I2C pins +const ( + I2C0_SDA_PIN Pin = GP0 + I2C0_SCL_PIN Pin = GP1 + I2C1_SDA_PIN Pin = GP2 + I2C1_SCL_PIN Pin = GP3 + + // default I2C0 + I2C_SDA_PIN Pin = I2C0_SDA_PIN + I2C_SCL_PIN Pin = I2C0_SCL_PIN +) + +// SPI pins +const ( + SPI0_RX_PIN Pin = GP0 + SPI0_CSN_PIN Pin = GP1 + SPI0_SCK_PIN Pin = GP2 + SPI0_TX_PIN Pin = GP3 + SPI0_SDO_PIN Pin = SPI0_TX_PIN + SPI0_SDI_PIN Pin = SPI0_RX_PIN + + SPI1_RX_PIN Pin = GP8 + SPI1_CSN_PIN Pin = GP9 + SPI1_SCK_PIN Pin = GP10 + SPI1_TX_PIN Pin = GP11 + SPI1_SDO_PIN Pin = SPI1_TX_PIN + SPI1_SDI_PIN Pin = SPI1_RX_PIN + + // default SPI0 + SPI_RX_PIN Pin = SPI0_RX_PIN + SPI_CSN_PIN Pin = SPI0_CSN_PIN + SPI_SCK_PIN Pin = SPI0_SCK_PIN + SPI_TX_PIN Pin = SPI0_TX_PIN + SPI_SDO_PIN Pin = SPI0_TX_PIN + SPI_SDI_PIN Pin = SPI0_RX_PIN +) + +// Onboard crystal oscillator frequency, in MHz. +const ( + xoscFreq = 12 // MHz +) + +// UART pins +const ( + UART0_TX_PIN = GP0 + UART0_RX_PIN = GP1 + UART1_TX_PIN = GP8 + UART1_RX_PIN = GP9 + + // default UART0 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "RP2040-Tiny" + usb_STRING_MANUFACTURER = "Waveshare" +) + +var ( + usb_VID uint16 = 0x2e8a + usb_PID uint16 = 0x0003 +) diff --git a/targets/waveshare-rp2040-tiny.json b/targets/waveshare-rp2040-tiny.json new file mode 100644 index 0000000000..74b651b51b --- /dev/null +++ b/targets/waveshare-rp2040-tiny.json @@ -0,0 +1,13 @@ +{ + "inherits": [ + "rp2040" + ], + "serial-port": ["2e8a:0003"], + "build-tags": ["waveshare_rp2040_tiny"], + "ldflags": [ + "--defsym=__flash_size=1020K" + ], + "extra-files": [ + "targets/pico-boot-stage2.S" + ] +} From 5a1b8850244c5595aa15b66331c313eeaadf02e2 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 13 Jan 2025 16:55:02 +0100 Subject: [PATCH 345/444] sync: move Mutex to internal/task The mutex implementation needs a different implementation once support for threading lands. This implementation just moves code to the internal/task package to centralize these algorithms. --- src/internal/task/mutex-cooperative.go | 43 ++++++++++++++++++++++++ src/sync/cond.go | 3 ++ src/sync/mutex.go | 46 +------------------------- 3 files changed, 47 insertions(+), 45 deletions(-) create mode 100644 src/internal/task/mutex-cooperative.go diff --git a/src/internal/task/mutex-cooperative.go b/src/internal/task/mutex-cooperative.go new file mode 100644 index 0000000000..e40966bed4 --- /dev/null +++ b/src/internal/task/mutex-cooperative.go @@ -0,0 +1,43 @@ +package task + +type Mutex struct { + locked bool + blocked Stack +} + +func (m *Mutex) Lock() { + if m.locked { + // Push self onto stack of blocked tasks, and wait to be resumed. + m.blocked.Push(Current()) + Pause() + return + } + + m.locked = true +} + +func (m *Mutex) Unlock() { + if !m.locked { + panic("sync: unlock of unlocked Mutex") + } + + // Wake up a blocked task, if applicable. + if t := m.blocked.Pop(); t != nil { + scheduleTask(t) + } else { + m.locked = false + } +} + +// TryLock tries to lock m and reports whether it succeeded. +// +// Note that while correct uses of TryLock do exist, they are rare, +// and use of TryLock is often a sign of a deeper problem +// in a particular use of mutexes. +func (m *Mutex) TryLock() bool { + if m.locked { + return false + } + m.Lock() + return true +} diff --git a/src/sync/cond.go b/src/sync/cond.go index fb5f224927..139d8e0229 100644 --- a/src/sync/cond.go +++ b/src/sync/cond.go @@ -89,3 +89,6 @@ func (c *Cond) Wait() { // signal. task.Pause() } + +//go:linkname scheduleTask runtime.scheduleTask +func scheduleTask(*task.Task) diff --git a/src/sync/mutex.go b/src/sync/mutex.go index b62b9fafdb..08c674d7ea 100644 --- a/src/sync/mutex.go +++ b/src/sync/mutex.go @@ -2,53 +2,9 @@ package sync import ( "internal/task" - _ "unsafe" ) -type Mutex struct { - locked bool - blocked task.Stack -} - -//go:linkname scheduleTask runtime.scheduleTask -func scheduleTask(*task.Task) - -func (m *Mutex) Lock() { - if m.locked { - // Push self onto stack of blocked tasks, and wait to be resumed. - m.blocked.Push(task.Current()) - task.Pause() - return - } - - m.locked = true -} - -func (m *Mutex) Unlock() { - if !m.locked { - panic("sync: unlock of unlocked Mutex") - } - - // Wake up a blocked task, if applicable. - if t := m.blocked.Pop(); t != nil { - scheduleTask(t) - } else { - m.locked = false - } -} - -// TryLock tries to lock m and reports whether it succeeded. -// -// Note that while correct uses of TryLock do exist, they are rare, -// and use of TryLock is often a sign of a deeper problem -// in a particular use of mutexes. -func (m *Mutex) TryLock() bool { - if m.locked { - return false - } - m.Lock() - return true -} +type Mutex = task.Mutex type RWMutex struct { // waitingWriters are all of the tasks waiting for write locks. From fd89e9b83a85c0a1bfc3f6a6abebe33b7aa899e8 Mon Sep 17 00:00:00 2001 From: Roman Grudzinski Date: Thu, 16 Jan 2025 12:06:22 +0300 Subject: [PATCH 346/444] Fix stm32f103 ADC (#4702) fix: Fix stm32f103 ADC --- src/machine/machine_stm32_adc_f1.go | 17 +---------------- src/machine/machine_stm32f103.go | 13 +++++++++---- src/runtime/runtime_stm32f103.go | 9 +++++---- 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/src/machine/machine_stm32_adc_f1.go b/src/machine/machine_stm32_adc_f1.go index f852f402b3..dedfea8694 100644 --- a/src/machine/machine_stm32_adc_f1.go +++ b/src/machine/machine_stm32_adc_f1.go @@ -23,15 +23,6 @@ func InitADC() { // Enable ADC clock enableAltFuncClock(unsafe.Pointer(stm32.ADC1)) - // set scan mode - stm32.ADC1.CR1.SetBits(stm32.ADC_CR1_SCAN) - - // clear CONT, ALIGN, EXTRIG and EXTSEL bits from CR2 - stm32.ADC1.CR2.ClearBits(stm32.ADC_CR2_CONT | stm32.ADC_CR2_ALIGN | stm32.ADC_CR2_EXTTRIG_Msk | stm32.ADC_CR2_EXTSEL_Msk) - - stm32.ADC1.SQR1.ClearBits(stm32.ADC_SQR1_L_Msk) - stm32.ADC1.SQR1.SetBits(2 << stm32.ADC_SQR1_L_Pos) // 2 means 3 conversions - // enable stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_ADON) @@ -61,7 +52,7 @@ func (a ADC) Get() uint16 { stm32.ADC1.SQR3.SetBits(ch) // start conversion - stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_SWSTART) + stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_ADON) // wait for conversion to complete for !stm32.ADC1.SR.HasBits(stm32.ADC_SR_EOC) { @@ -70,12 +61,6 @@ func (a ADC) Get() uint16 { // read result as 16 bit value result := uint16(stm32.ADC1.DR.Get()) << 4 - // clear flag - stm32.ADC1.SR.ClearBits(stm32.ADC_SR_EOC) - - // clear rank - stm32.ADC1.SQR3.ClearBits(ch) - return result } diff --git a/src/machine/machine_stm32f103.go b/src/machine/machine_stm32f103.go index 545c431110..b8d494ad7b 100644 --- a/src/machine/machine_stm32f103.go +++ b/src/machine/machine_stm32f103.go @@ -221,14 +221,19 @@ func (p Pin) enableClock() { // Enable peripheral clock. Expand to include all the desired peripherals func enableAltFuncClock(bus unsafe.Pointer) { - if bus == unsafe.Pointer(stm32.USART1) { + switch bus { + case unsafe.Pointer(stm32.USART1): stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN) - } else if bus == unsafe.Pointer(stm32.USART2) { + case unsafe.Pointer(stm32.USART2): stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART2EN) - } else if bus == unsafe.Pointer(stm32.I2C1) { + case unsafe.Pointer(stm32.I2C1): stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C1EN) - } else if bus == unsafe.Pointer(stm32.SPI1) { + case unsafe.Pointer(stm32.SPI1): stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SPI1EN) + case unsafe.Pointer(stm32.ADC1): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_ADC1EN) + default: + panic("machine: unknown peripheral") } } diff --git a/src/runtime/runtime_stm32f103.go b/src/runtime/runtime_stm32f103.go index ac98de674c..702d773897 100644 --- a/src/runtime/runtime_stm32f103.go +++ b/src/runtime/runtime_stm32f103.go @@ -33,10 +33,11 @@ func buffered() int { // initCLK sets clock to 72MHz using HSE 8MHz crystal w/ PLL X 9 (8MHz x 9 = 72MHz). func initCLK() { - stm32.FLASH.ACR.SetBits(stm32.FLASH_ACR_LATENCY_WS2) // Two wait states, per datasheet - stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PPRE1_Div2 << stm32.RCC_CFGR_PPRE1_Pos) // prescale PCLK1 = HCLK/2 - stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PPRE2_Div1 << stm32.RCC_CFGR_PPRE2_Pos) // prescale PCLK2 = HCLK/1 - stm32.RCC.CR.SetBits(stm32.RCC_CR_HSEON) // enable HSE clock + stm32.FLASH.ACR.SetBits(stm32.FLASH_ACR_LATENCY_WS2) // Two wait states, per datasheet + stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PPRE1_Div2 << stm32.RCC_CFGR_PPRE1_Pos) // prescale PCLK1 = HCLK/2 + stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PPRE2_Div1 << stm32.RCC_CFGR_PPRE2_Pos) // prescale PCLK2 = HCLK/1 + stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_ADCPRE_Div6 << stm32.RCC_CFGR_ADCPRE_Pos) // prescale ADCCLK = PCLK2/6 + stm32.RCC.CR.SetBits(stm32.RCC_CR_HSEON) // enable HSE clock // wait for the HSEREADY flag for !stm32.RCC.CR.HasBits(stm32.RCC_CR_HSERDY) { From 127557d27ea6e40e7cc6231c6d6bc7f9b8a07392 Mon Sep 17 00:00:00 2001 From: Roman Grudzinski Date: Thu, 16 Jan 2025 13:25:33 +0300 Subject: [PATCH 347/444] Fix ADC channel selecting and ADC value reading --- src/machine/machine_stm32_adc_f1.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/machine/machine_stm32_adc_f1.go b/src/machine/machine_stm32_adc_f1.go index dedfea8694..7076bdd8f6 100644 --- a/src/machine/machine_stm32_adc_f1.go +++ b/src/machine/machine_stm32_adc_f1.go @@ -24,7 +24,7 @@ func InitADC() { enableAltFuncClock(unsafe.Pointer(stm32.ADC1)) // enable - stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_ADON) + stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_ADON | stm32.ADC_CR2_ALIGN) return } @@ -49,7 +49,7 @@ func (a ADC) Configure(ADCConfig) { func (a ADC) Get() uint16 { // set rank ch := uint32(a.getChannel()) - stm32.ADC1.SQR3.SetBits(ch) + stm32.ADC1.SetSQR3_SQ1(ch) // start conversion stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_ADON) @@ -59,9 +59,7 @@ func (a ADC) Get() uint16 { } // read result as 16 bit value - result := uint16(stm32.ADC1.DR.Get()) << 4 - - return result + return uint16(stm32.ADC1.DR.Get()) } func (a ADC) getChannel() uint8 { From 9e9768b51df06b645d30639be2743219402c6fc6 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 25 Aug 2024 16:45:10 +0200 Subject: [PATCH 348/444] all: add support for LLVM 19 --- .circleci/config.yml | 12 +++--- .github/workflows/build-macos.yml | 10 ++--- .github/workflows/linux.yml | 12 +++--- .github/workflows/llvm.yml | 4 +- .github/workflows/nix.yml | 2 +- .github/workflows/sizediff-install-pkgs.sh | 10 ++--- .github/workflows/sizediff.yml | 2 +- .github/workflows/windows.yml | 4 +- GNUmakefile | 6 +-- builder/cc1as.cpp | 28 ++++++------- builder/cc1as.h | 26 +++++++++++- builder/sizes_test.go | 4 +- cgo/libclang_config_llvm18.go | 2 +- cgo/libclang_config_llvm19.go | 15 +++++++ compileopts/target.go | 10 +++-- compiler/defer.go | 2 +- compiler/testdata/basic.ll | 6 +-- compiler/testdata/channel.ll | 12 +++--- compiler/testdata/defer-cortex-m-qemu.ll | 21 +++++----- compiler/testdata/float.ll | 6 +-- compiler/testdata/func.ll | 6 +-- compiler/testdata/gc.ll | 20 ++++----- compiler/testdata/go1.20.ll | 8 ++-- compiler/testdata/go1.21.ll | 6 +-- .../testdata/goroutine-cortex-m-qemu-tasks.ll | 42 +++++++++---------- compiler/testdata/goroutine-wasm-asyncify.ll | 42 +++++++++---------- compiler/testdata/interface.ll | 14 +++---- compiler/testdata/pointer.ll | 6 +-- compiler/testdata/pragma.ll | 20 ++++----- compiler/testdata/slice.ll | 14 +++---- compiler/testdata/string.ll | 6 +-- compiler/testdata/zeromap.ll | 12 +++--- go.mod | 4 +- go.sum | 8 ++-- interp/memory.go | 2 + interp/testdata/consteval.ll | 15 ------- interp/testdata/consteval.out.ll | 3 +- targets/cortex-m3.json | 2 +- targets/cortex-m33.json | 2 +- targets/cortex-m4.json | 2 +- targets/cortex-m7.json | 2 +- targets/esp32.ld | 2 +- targets/esp32c3.json | 2 +- targets/fe310.json | 2 +- targets/k210.json | 2 +- targets/nintendoswitch.json | 2 +- targets/riscv-qemu.json | 2 +- targets/tkey.json | 2 +- targets/wasip1.json | 4 +- targets/wasip2.json | 4 +- targets/wasm-unknown.json | 4 +- targets/wasm.json | 4 +- 52 files changed, 247 insertions(+), 213 deletions(-) create mode 100644 cgo/libclang_config_llvm19.go diff --git a/.circleci/config.yml b/.circleci/config.yml index ce3417ffb3..e2069e3485 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,12 +10,12 @@ commands: steps: - restore_cache: keys: - - llvm-source-18-v1 + - llvm-source-19-v1 - run: name: "Fetch LLVM source" command: make llvm-source - save_cache: - key: llvm-source-18-v1 + key: llvm-source-19-v1 paths: - llvm-project/clang/lib/Headers - llvm-project/clang/include @@ -109,12 +109,12 @@ jobs: # "make lint" fails before go 1.21 because internal/tools/go.mod specifies packages that require go 1.21 fmt-check: false resource_class: large - test-llvm18-go123: + test-llvm19-go123: docker: - image: golang:1.23-bullseye steps: - test-linux: - llvm: "18" + llvm: "19" resource_class: large workflows: @@ -123,5 +123,5 @@ workflows: # This tests our lowest supported versions of Go and LLVM, to make sure at # least the smoke tests still pass. - test-llvm15-go119 - # This tests LLVM 18 support when linking against system libraries. - - test-llvm18-go123 + # This tests LLVM 19 support when linking against system libraries. + - test-llvm19-go123 diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 4f26e1fb8f..07b0d307d0 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -45,7 +45,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-18-${{ matrix.os }}-v2 + key: llvm-source-19-${{ matrix.os }}-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -70,7 +70,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-18-${{ matrix.os }}-v3 + key: llvm-build-19-${{ matrix.os }}-v1 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -126,7 +126,7 @@ jobs: runs-on: macos-latest strategy: matrix: - version: [16, 17, 18] + version: [16, 17, 18, 19] steps: - name: Set up Homebrew uses: Homebrew/actions/setup-homebrew@master @@ -150,8 +150,8 @@ jobs: - name: Check binary run: tinygo version - name: Build TinyGo (default LLVM) - if: matrix.version == 18 + if: matrix.version == 19 run: go install - name: Check binary - if: matrix.version == 18 + if: matrix.version == 19 run: tinygo version diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 8d5976777a..178467c9eb 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -48,7 +48,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-18-linux-alpine-v1 + key: llvm-source-19-linux-alpine-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -73,7 +73,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-18-linux-alpine-v2 + key: llvm-build-19-linux-alpine-v1 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -205,7 +205,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-18-linux-asserts-v1 + key: llvm-source-19-linux-asserts-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -230,7 +230,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-18-linux-asserts-v2 + key: llvm-build-19-linux-asserts-v1 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -321,7 +321,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-18-linux-v1 + key: llvm-source-19-linux-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -346,7 +346,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-18-linux-${{ matrix.goarch }}-v2 + key: llvm-build-19-linux-${{ matrix.goarch }}-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index f97646f7b9..d1ba745b2e 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -35,8 +35,8 @@ jobs: uses: docker/metadata-action@v5 with: images: | - tinygo/llvm-18 - ghcr.io/${{ github.repository_owner }}/llvm-18 + tinygo/llvm-19 + ghcr.io/${{ github.repository_owner }}/llvm-19 tags: | type=sha,format=long type=raw,value=latest diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 7ad4911d6c..2f24df54a8 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -29,7 +29,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-18-linux-nix-v1 + key: llvm-source-19-linux-nix-v1 path: | llvm-project/compiler-rt - name: Download LLVM source diff --git a/.github/workflows/sizediff-install-pkgs.sh b/.github/workflows/sizediff-install-pkgs.sh index e77600d184..e81c994ea8 100755 --- a/.github/workflows/sizediff-install-pkgs.sh +++ b/.github/workflows/sizediff-install-pkgs.sh @@ -2,11 +2,11 @@ # still works after checking out the dev branch (that is, when going from LLVM # 16 to LLVM 17 for example, both Clang 16 and Clang 17 are installed). -echo 'deb https://apt.llvm.org/noble/ llvm-toolchain-noble-18 main' | sudo tee /etc/apt/sources.list.d/llvm.list +echo 'deb https://apt.llvm.org/noble/ llvm-toolchain-noble-19 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 --no-install-recommends -y \ - llvm-18-dev \ - clang-18 \ - libclang-18-dev \ - lld-18 + llvm-19-dev \ + clang-19 \ + libclang-19-dev \ + lld-19 diff --git a/.github/workflows/sizediff.yml b/.github/workflows/sizediff.yml index b9c40b1ea0..9c2b49e283 100644 --- a/.github/workflows/sizediff.yml +++ b/.github/workflows/sizediff.yml @@ -30,7 +30,7 @@ jobs: uses: actions/cache@v4 id: cache-llvm-source with: - key: llvm-source-18-sizediff-v1 + key: llvm-source-19-sizediff-v1 path: | llvm-project/compiler-rt - name: Download LLVM source diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index e81c2b4d46..d547579434 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -47,7 +47,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-18-windows-v1 + key: llvm-source-19-windows-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -72,7 +72,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-18-windows-v2 + key: llvm-build-19-windows-v1 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' diff --git a/GNUmakefile b/GNUmakefile index 099aab0ced..57d382d391 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -10,7 +10,7 @@ LLD_SRC ?= $(LLVM_PROJECTDIR)/lld # Try to autodetect LLVM build tools. # Versions are listed here in descending priority order. -LLVM_VERSIONS = 18 17 16 15 +LLVM_VERSIONS = 19 18 17 16 15 errifempty = $(if $(1),$(1),$(error $(2))) detect = $(shell which $(call errifempty,$(firstword $(foreach p,$(2),$(shell command -v $(p) 2> /dev/null && echo $(p)))),failed to locate $(1) at any of: $(2))) toolSearchPathsVersion = $(1)-$(2) @@ -147,7 +147,7 @@ endif MD5SUM ?= md5sum # Libraries that should be linked in for the statically linked Clang. -CLANG_LIB_NAMES = clangAnalysis clangAPINotes clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangExtractAPI clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangSupport clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions +CLANG_LIB_NAMES = clangAnalysis clangAPINotes clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangExtractAPI clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangInstallAPI clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangSupport clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++ # Libraries that should be linked in for the statically linked LLD. @@ -238,7 +238,7 @@ gen-device-renesas: build/gen-device-svd GO111MODULE=off $(GO) fmt ./src/device/renesas $(LLVM_PROJECTDIR)/llvm: - git clone -b tinygo_xtensa_release_18.1.2 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR) + git clone -b xtensa_release_19.1.2 --depth=1 https://github.com/espressif/llvm-project $(LLVM_PROJECTDIR) llvm-source: $(LLVM_PROJECTDIR)/llvm ## Get LLVM sources # Configure LLVM. diff --git a/builder/cc1as.cpp b/builder/cc1as.cpp index e489866ec7..2cbb7b9f68 100644 --- a/builder/cc1as.cpp +++ b/builder/cc1as.cpp @@ -145,6 +145,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts, } Opts.RelaxELFRelocations = !Args.hasArg(OPT_mrelax_relocations_no); + Opts.SSE2AVX = Args.hasArg(OPT_msse2avx); if (auto *DwarfFormatArg = Args.getLastArg(OPT_gdwarf64, OPT_gdwarf32)) Opts.Dwarf64 = DwarfFormatArg->getOption().matches(OPT_gdwarf64); Opts.DwarfVersion = getLastArgIntValue(Args, OPT_dwarf_version_EQ, 2, Diags); @@ -234,6 +235,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts, Opts.EmitCompactUnwindNonCanonical = Args.hasArg(OPT_femit_compact_unwind_non_canonical); + Opts.Crel = Args.hasArg(OPT_crel); Opts.AsSecureLogFile = Args.getLastArgValue(OPT_as_secure_log_file); @@ -287,8 +289,14 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts, assert(MRI && "Unable to create target register info!"); MCTargetOptions MCOptions; + MCOptions.MCRelaxAll = Opts.RelaxAll; MCOptions.EmitDwarfUnwind = Opts.EmitDwarfUnwind; MCOptions.EmitCompactUnwindNonCanonical = Opts.EmitCompactUnwindNonCanonical; + MCOptions.MCSaveTempLabels = Opts.SaveTemporaryLabels; + MCOptions.Crel = Opts.Crel; + MCOptions.X86RelaxRelocations = Opts.RelaxELFRelocations; + MCOptions.X86Sse2Avx = Opts.SSE2AVX; + MCOptions.CompressDebugSections = Opts.CompressDebugSections; MCOptions.AsSecureLogFile = Opts.AsSecureLogFile; std::unique_ptr MAI( @@ -297,9 +305,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts, // Ensure MCAsmInfo initialization occurs before any use, otherwise sections // may be created with a combination of default and explicit settings. - MAI->setCompressDebugSections(Opts.CompressDebugSections); - MAI->setRelaxELFRelocations(Opts.RelaxELFRelocations); bool IsBinary = Opts.OutputType == AssemblerInvocation::FT_Obj; if (Opts.OutputPath.empty()) @@ -343,8 +349,6 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts, MOFI->setDarwinTargetVariantSDKVersion(Opts.DarwinTargetVariantSDKVersion); Ctx.setObjectFileInfo(MOFI.get()); - if (Opts.SaveTemporaryLabels) - Ctx.setAllowTemporaryLabels(false); if (Opts.GenDwarfForAssembly) Ctx.setGenDwarfForAssembly(true); if (!Opts.DwarfDebugFlags.empty()) @@ -381,6 +385,9 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts, MCOptions.MCNoWarn = Opts.NoWarn; MCOptions.MCFatalWarnings = Opts.FatalWarnings; MCOptions.MCNoTypeCheck = Opts.NoTypeCheck; + MCOptions.ShowMCInst = Opts.ShowInst; + MCOptions.AsmVerbose = true; + MCOptions.MCUseDwarfDirectory = MCTargetOptions::EnableDwarfDirectory; MCOptions.ABIName = Opts.TargetABI; // FIXME: There is a bit of code duplication with addPassesToEmitFile. @@ -395,10 +402,8 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts, TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions)); auto FOut = std::make_unique(*Out); - Str.reset(TheTarget->createAsmStreamer( - Ctx, std::move(FOut), /*asmverbose*/ true, - /*useDwarfDirectory*/ true, IP, std::move(CE), std::move(MAB), - Opts.ShowInst)); + Str.reset(TheTarget->createAsmStreamer(Ctx, std::move(FOut), IP, + std::move(CE), std::move(MAB))); } else if (Opts.OutputType == AssemblerInvocation::FT_Null) { Str.reset(createNullStreamer(Ctx)); } else { @@ -421,9 +426,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts, Triple T(Opts.Triple); Str.reset(TheTarget->createMCObjectStreamer( - T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI, - Opts.RelaxAll, Opts.IncrementalLinkerCompatible, - /*DWARFMustBeAtTheEnd*/ true)); + T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI)); Str.get()->initSections(Opts.NoExecStack, *STI); } @@ -436,9 +439,6 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts, Str.get()->emitZeros(1); } - // Assembly to object compilation should leverage assembly info. - Str->setUseAssemblerInfoForParsing(true); - bool Failed = false; std::unique_ptr Parser( diff --git a/builder/cc1as.h b/builder/cc1as.h index 67b97f6cf9..cc973fd88a 100644 --- a/builder/cc1as.h +++ b/builder/cc1as.h @@ -38,10 +38,17 @@ struct AssemblerInvocation { /// @{ std::vector IncludePaths; + LLVM_PREFERRED_TYPE(bool) unsigned NoInitialTextSection : 1; + LLVM_PREFERRED_TYPE(bool) unsigned SaveTemporaryLabels : 1; + LLVM_PREFERRED_TYPE(bool) unsigned GenDwarfForAssembly : 1; + LLVM_PREFERRED_TYPE(bool) unsigned RelaxELFRelocations : 1; + LLVM_PREFERRED_TYPE(bool) + unsigned SSE2AVX : 1; + LLVM_PREFERRED_TYPE(bool) unsigned Dwarf64 : 1; unsigned DwarfVersion; std::string DwarfDebugFlags; @@ -66,7 +73,9 @@ struct AssemblerInvocation { FT_Obj ///< Object file output. }; FileType OutputType; + LLVM_PREFERRED_TYPE(bool) unsigned ShowHelp : 1; + LLVM_PREFERRED_TYPE(bool) unsigned ShowVersion : 1; /// @} @@ -74,28 +83,41 @@ struct AssemblerInvocation { /// @{ unsigned OutputAsmVariant; + LLVM_PREFERRED_TYPE(bool) unsigned ShowEncoding : 1; + LLVM_PREFERRED_TYPE(bool) unsigned ShowInst : 1; /// @} /// @name Assembler Options /// @{ + LLVM_PREFERRED_TYPE(bool) unsigned RelaxAll : 1; + LLVM_PREFERRED_TYPE(bool) unsigned NoExecStack : 1; + LLVM_PREFERRED_TYPE(bool) unsigned FatalWarnings : 1; + LLVM_PREFERRED_TYPE(bool) unsigned NoWarn : 1; + LLVM_PREFERRED_TYPE(bool) unsigned NoTypeCheck : 1; + LLVM_PREFERRED_TYPE(bool) unsigned IncrementalLinkerCompatible : 1; + LLVM_PREFERRED_TYPE(bool) unsigned EmbedBitcode : 1; /// Whether to emit DWARF unwind info. EmitDwarfUnwindType EmitDwarfUnwind; // Whether to emit compact-unwind for non-canonical entries. - // Note: maybe overridden by other constraints. + // Note: maybe overriden by other constraints. + LLVM_PREFERRED_TYPE(bool) unsigned EmitCompactUnwindNonCanonical : 1; + LLVM_PREFERRED_TYPE(bool) + unsigned Crel : 1; + /// The name of the relocation model to use. std::string RelocationModel; @@ -126,6 +148,7 @@ struct AssemblerInvocation { ShowInst = 0; ShowEncoding = 0; RelaxAll = 0; + SSE2AVX = 0; NoExecStack = 0; FatalWarnings = 0; NoWarn = 0; @@ -136,6 +159,7 @@ struct AssemblerInvocation { EmbedBitcode = 0; EmitDwarfUnwind = EmitDwarfUnwindType::Default; EmitCompactUnwindNonCanonical = false; + Crel = false; } static bool CreateFromArgs(AssemblerInvocation &Res, diff --git a/builder/sizes_test.go b/builder/sizes_test.go index 801a1184e9..8c87e22917 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -42,9 +42,9 @@ func TestBinarySize(t *testing.T) { // This is a small number of very diverse targets that we want to test. tests := []sizeTest{ // microcontrollers - {"hifive1b", "examples/echo", 4600, 280, 0, 2268}, + {"hifive1b", "examples/echo", 4560, 280, 0, 2268}, {"microbit", "examples/serial", 2908, 388, 8, 2272}, - {"wioterminal", "examples/pininterrupt", 7286, 1486, 116, 6912}, + {"wioterminal", "examples/pininterrupt", 7293, 1487, 116, 6912}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the diff --git a/cgo/libclang_config_llvm18.go b/cgo/libclang_config_llvm18.go index 3b769c622d..da181291c4 100644 --- a/cgo/libclang_config_llvm18.go +++ b/cgo/libclang_config_llvm18.go @@ -1,4 +1,4 @@ -//go:build !byollvm && !llvm15 && !llvm16 && !llvm17 +//go:build !byollvm && llvm18 package cgo diff --git a/cgo/libclang_config_llvm19.go b/cgo/libclang_config_llvm19.go new file mode 100644 index 0000000000..867d23f24b --- /dev/null +++ b/cgo/libclang_config_llvm19.go @@ -0,0 +1,15 @@ +//go:build !byollvm && !llvm15 && !llvm16 && !llvm17 && !llvm18 + +package cgo + +/* +#cgo linux CFLAGS: -I/usr/include/llvm-19 -I/usr/include/llvm-c-19 -I/usr/lib/llvm-19/include +#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@19/include +#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@19/include +#cgo freebsd CFLAGS: -I/usr/local/llvm19/include +#cgo linux LDFLAGS: -L/usr/lib/llvm-19/lib -lclang +#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@19/lib -lclang +#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@19/lib -lclang +#cgo freebsd LDFLAGS: -L/usr/local/llvm19/lib -lclang +*/ +import "C" diff --git a/compileopts/target.go b/compileopts/target.go index fe864fe9db..7893e58290 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -328,14 +328,14 @@ func defaultTarget(options *Options) (*TargetSpec, error) { spec.CPU = "generic" llvmarch = "aarch64" if options.GOOS == "darwin" { - spec.Features = "+fp-armv8,+neon" + spec.Features = "+ete,+fp-armv8,+neon,+trbe,+v8a" // Looks like Apple prefers to call this architecture ARM64 // instead of AArch64. llvmarch = "arm64" } else if options.GOOS == "windows" { - spec.Features = "+fp-armv8,+neon,-fmv" + spec.Features = "+ete,+fp-armv8,+neon,+trbe,+v8a,-fmv" } else { // linux - spec.Features = "+fp-armv8,+neon,-fmv,-outline-atomics" + spec.Features = "+ete,+fp-armv8,+neon,+trbe,+v8a,-fmv,-outline-atomics" } case "mips", "mipsle": spec.CPU = "mips32" @@ -358,11 +358,13 @@ func defaultTarget(options *Options) (*TargetSpec, error) { case "wasm": llvmarch = "wasm32" spec.CPU = "generic" - spec.Features = "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" + spec.Features = "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" spec.BuildTags = append(spec.BuildTags, "tinygo.wasm") spec.CFlags = append(spec.CFlags, "-mbulk-memory", "-mnontrapping-fptoint", + "-mno-multivalue", + "-mno-reference-types", "-msign-ext", ) default: diff --git a/compiler/defer.go b/compiler/defer.go index 677df8f2a1..2ca76a8325 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -161,7 +161,7 @@ str x2, [x1, #8] mov x0, #0 1: ` - constraints = "={x0},{x1},~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7},~{x8},~{x9},~{x10},~{x11},~{x12},~{x13},~{x14},~{x15},~{x16},~{x17},~{x19},~{x20},~{x21},~{x22},~{x23},~{x24},~{x25},~{x26},~{x27},~{x28},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{q16},~{q17},~{q18},~{q19},~{q20},~{q21},~{q22},~{q23},~{q24},~{q25},~{q26},~{q27},~{q28},~{q29},~{q30},~{nzcv},~{ffr},~{vg},~{memory}" + constraints = "={x0},{x1},~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7},~{x8},~{x9},~{x10},~{x11},~{x12},~{x13},~{x14},~{x15},~{x16},~{x17},~{x19},~{x20},~{x21},~{x22},~{x23},~{x24},~{x25},~{x26},~{x27},~{x28},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{q16},~{q17},~{q18},~{q19},~{q20},~{q21},~{q22},~{q23},~{q24},~{q25},~{q26},~{q27},~{q28},~{q29},~{q30},~{nzcv},~{ffr},~{memory}" if b.GOOS != "darwin" && b.GOOS != "windows" { // These registers cause the following warning when compiling for // MacOS and Windows: diff --git a/compiler/testdata/basic.ll b/compiler/testdata/basic.ll index e81d5f2a6a..2c2797504b 100644 --- a/compiler/testdata/basic.ll +++ b/compiler/testdata/basic.ll @@ -206,7 +206,7 @@ entry: ret void } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind } diff --git a/compiler/testdata/channel.ll b/compiler/testdata/channel.ll index 68982d051c..b99c428665 100644 --- a/compiler/testdata/channel.ll +++ b/compiler/testdata/channel.ll @@ -82,11 +82,11 @@ entry: store i32 1, ptr %select.send.value, align 4 call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %select.states.alloca) store ptr %ch1, ptr %select.states.alloca, align 4 - %select.states.alloca.repack1 = getelementptr inbounds %runtime.chanSelectState, ptr %select.states.alloca, i32 0, i32 1 + %select.states.alloca.repack1 = getelementptr inbounds i8, ptr %select.states.alloca, i32 4 store ptr %select.send.value, ptr %select.states.alloca.repack1, align 4 - %0 = getelementptr inbounds [2 x %runtime.chanSelectState], ptr %select.states.alloca, i32 0, i32 1 + %0 = getelementptr inbounds i8, ptr %select.states.alloca, i32 8 store ptr %ch2, ptr %0, align 4 - %.repack3 = getelementptr inbounds [2 x %runtime.chanSelectState], ptr %select.states.alloca, i32 0, i32 1, i32 1 + %.repack3 = getelementptr inbounds i8, ptr %select.states.alloca, i32 12 store ptr null, ptr %.repack3, align 4 %select.result = call { i32, i1 } @runtime.chanSelect(ptr undef, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr null, i32 0, i32 0, ptr undef) #4 call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %select.states.alloca) @@ -107,8 +107,8 @@ select.body: ; preds = %select.next declare { i32, i1 } @runtime.chanSelect(ptr, ptr, i32, i32, ptr, i32, i32, ptr) #1 -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } attributes #4 = { nounwind } diff --git a/compiler/testdata/defer-cortex-m-qemu.ll b/compiler/testdata/defer-cortex-m-qemu.ll index f1fbad7f81..7f5c3d6800 100644 --- a/compiler/testdata/defer-cortex-m-qemu.ll +++ b/compiler/testdata/defer-cortex-m-qemu.ll @@ -5,7 +5,6 @@ target triple = "thumbv7m-unknown-unknown-eabi" %runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i8, %runtime._interface } %runtime._interface = type { ptr, ptr } -%runtime._defer = type { i32, ptr } ; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 @@ -28,7 +27,7 @@ entry: %0 = call ptr @llvm.stacksave.p0() call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4 store i32 0, ptr %defer.alloca, align 4 - %defer.alloca.repack15 = getelementptr inbounds { i32, ptr }, ptr %defer.alloca, i32 0, i32 1 + %defer.alloca.repack15 = getelementptr inbounds i8, ptr %defer.alloca, i32 4 store ptr null, ptr %defer.alloca.repack15, align 4 store ptr %defer.alloca, ptr %deferPtr, align 4 %setjmp = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 @@ -52,7 +51,7 @@ rundefers.loophead: ; preds = %3, %rundefers.block br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop rundefers.loop: ; preds = %rundefers.loophead - %stack.next.gep = getelementptr inbounds %runtime._defer, ptr %2, i32 0, i32 1 + %stack.next.gep = getelementptr inbounds i8, ptr %2, i32 4 %stack.next = load ptr, ptr %stack.next.gep, align 4 store ptr %stack.next, ptr %deferPtr, align 4 %callback = load i32, ptr %2, align 4 @@ -88,7 +87,7 @@ rundefers.loophead6: ; preds = %5, %lpad br i1 %stackIsNil7, label %rundefers.end3, label %rundefers.loop5 rundefers.loop5: ; preds = %rundefers.loophead6 - %stack.next.gep8 = getelementptr inbounds %runtime._defer, ptr %4, i32 0, i32 1 + %stack.next.gep8 = getelementptr inbounds i8, ptr %4, i32 4 %stack.next9 = load ptr, ptr %stack.next.gep8, align 4 store ptr %stack.next9, ptr %deferPtr, align 4 %callback11 = load i32, ptr %4, align 4 @@ -145,11 +144,11 @@ entry: %0 = call ptr @llvm.stacksave.p0() call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4 store i32 0, ptr %defer.alloca, align 4 - %defer.alloca.repack22 = getelementptr inbounds { i32, ptr }, ptr %defer.alloca, i32 0, i32 1 + %defer.alloca.repack22 = getelementptr inbounds i8, ptr %defer.alloca, i32 4 store ptr null, ptr %defer.alloca.repack22, align 4 store ptr %defer.alloca, ptr %deferPtr, align 4 store i32 1, ptr %defer.alloca2, align 4 - %defer.alloca2.repack23 = getelementptr inbounds { i32, ptr }, ptr %defer.alloca2, i32 0, i32 1 + %defer.alloca2.repack23 = getelementptr inbounds i8, ptr %defer.alloca2, i32 4 store ptr %defer.alloca, ptr %defer.alloca2.repack23, align 4 store ptr %defer.alloca2, ptr %deferPtr, align 4 %setjmp = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 @@ -173,7 +172,7 @@ rundefers.loophead: ; preds = %4, %3, %rundefers.b br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop rundefers.loop: ; preds = %rundefers.loophead - %stack.next.gep = getelementptr inbounds %runtime._defer, ptr %2, i32 0, i32 1 + %stack.next.gep = getelementptr inbounds i8, ptr %2, i32 4 %stack.next = load ptr, ptr %stack.next.gep, align 4 store ptr %stack.next, ptr %deferPtr, align 4 %callback = load i32, ptr %2, align 4 @@ -219,7 +218,7 @@ rundefers.loophead10: ; preds = %7, %6, %lpad br i1 %stackIsNil11, label %rundefers.end7, label %rundefers.loop9 rundefers.loop9: ; preds = %rundefers.loophead10 - %stack.next.gep12 = getelementptr inbounds %runtime._defer, ptr %5, i32 0, i32 1 + %stack.next.gep12 = getelementptr inbounds i8, ptr %5, i32 4 %stack.next13 = load ptr, ptr %stack.next.gep12, align 4 store ptr %stack.next13, ptr %deferPtr, align 4 %callback15 = load i32, ptr %5, align 4 @@ -271,9 +270,9 @@ entry: ret void } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } -attributes #1 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } -attributes #2 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } +attributes #1 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } +attributes #2 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } attributes #3 = { nocallback nofree nosync nounwind willreturn } attributes #4 = { nounwind } attributes #5 = { nounwind returns_twice } diff --git a/compiler/testdata/float.ll b/compiler/testdata/float.ll index 735ab19768..e894e941b0 100644 --- a/compiler/testdata/float.ll +++ b/compiler/testdata/float.ll @@ -93,6 +93,6 @@ entry: ret i8 %0 } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } diff --git a/compiler/testdata/func.ll b/compiler/testdata/func.ll index bec79bffc5..a4ad26c3dd 100644 --- a/compiler/testdata/func.ll +++ b/compiler/testdata/func.ll @@ -44,7 +44,7 @@ entry: ret void } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind } diff --git a/compiler/testdata/gc.ll b/compiler/testdata/gc.ll index 82260fbf45..314c6cd4e7 100644 --- a/compiler/testdata/gc.ll +++ b/compiler/testdata/gc.ll @@ -105,18 +105,18 @@ entry: %makeslice = call align 1 dereferenceable(5) ptr @runtime.alloc(i32 5, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %makeslice, ptr nonnull %stackalloc, ptr undef) #3 store ptr %makeslice, ptr @main.slice1, align 4 - store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice1, i32 0, i32 1), align 4 - store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice1, i32 0, i32 2), align 4 + store i32 5, ptr getelementptr inbounds (i8, ptr @main.slice1, i32 4), align 4 + store i32 5, ptr getelementptr inbounds (i8, ptr @main.slice1, i32 8), align 4 %makeslice1 = call align 4 dereferenceable(20) ptr @runtime.alloc(i32 20, ptr nonnull inttoptr (i32 67 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %makeslice1, ptr nonnull %stackalloc, ptr undef) #3 store ptr %makeslice1, ptr @main.slice2, align 4 - store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice2, i32 0, i32 1), align 4 - store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice2, i32 0, i32 2), align 4 + store i32 5, ptr getelementptr inbounds (i8, ptr @main.slice2, i32 4), align 4 + store i32 5, ptr getelementptr inbounds (i8, ptr @main.slice2, i32 8), align 4 %makeslice3 = call align 4 dereferenceable(60) ptr @runtime.alloc(i32 60, ptr nonnull inttoptr (i32 71 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %makeslice3, ptr nonnull %stackalloc, ptr undef) #3 store ptr %makeslice3, ptr @main.slice3, align 4 - store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice3, i32 0, i32 1), align 4 - store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice3, i32 0, i32 2), align 4 + store i32 5, ptr getelementptr inbounds (i8, ptr @main.slice3, i32 4), align 4 + store i32 5, ptr getelementptr inbounds (i8, ptr @main.slice3, i32 8), align 4 ret void } @@ -127,7 +127,7 @@ entry: %0 = call align 8 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #3 store double %v.r, ptr %0, align 8 - %.repack1 = getelementptr inbounds { double, double }, ptr %0, i32 0, i32 1 + %.repack1 = getelementptr inbounds i8, ptr %0, i32 8 store double %v.i, ptr %.repack1, align 8 %1 = insertvalue %runtime._interface { ptr @"reflect/types.type:basic:complex128", ptr undef }, ptr %0, 1 call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:basic:complex128", ptr nonnull %stackalloc, ptr undef) #3 @@ -135,7 +135,7 @@ entry: ret %runtime._interface %1 } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind } diff --git a/compiler/testdata/go1.20.ll b/compiler/testdata/go1.20.ll index 6ef13fb4a8..9d4f9e1dc6 100644 --- a/compiler/testdata/go1.20.ll +++ b/compiler/testdata/go1.20.ll @@ -36,7 +36,7 @@ entry: br i1 %4, label %unsafe.String.throw, label %unsafe.String.next unsafe.String.next: ; preds = %entry - %5 = zext i16 %len to i32 + %5 = zext nneg i16 %len to i32 %6 = insertvalue %runtime._string undef, ptr %ptr, 0 %7 = insertvalue %runtime._string %6, i32 %5, 1 call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #3 @@ -57,7 +57,7 @@ entry: ret ptr %s.data } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind } diff --git a/compiler/testdata/go1.21.ll b/compiler/testdata/go1.21.ll index d76ec6212c..982e4fda1a 100644 --- a/compiler/testdata/go1.21.ll +++ b/compiler/testdata/go1.21.ll @@ -171,9 +171,9 @@ declare i32 @llvm.smax.i32(i32, i32) #4 ; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) declare i32 @llvm.umax.i32(i32, i32) #4 -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nocallback nofree nounwind willreturn memory(argmem: write) } attributes #4 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } attributes #5 = { nounwind } diff --git a/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll b/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll index 819f01adbe..3f21d30a76 100644 --- a/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll +++ b/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll @@ -65,7 +65,7 @@ entry: store i32 3, ptr %n, align 4 %0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #9 store i32 5, ptr %0, align 4 - %1 = getelementptr inbounds { i32, ptr }, ptr %0, i32 0, i32 1 + %1 = getelementptr inbounds i8, ptr %0, i32 4 store ptr %n, ptr %1, align 4 %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr undef) #9 call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #9 @@ -87,7 +87,7 @@ entry: define linkonce_odr void @"main.closureFunctionGoroutine$1$gowrapper"(ptr %0) unnamed_addr #5 { entry: %1 = load i32, ptr %0, align 4 - %2 = getelementptr inbounds { i32, ptr }, ptr %0, i32 0, i32 1 + %2 = getelementptr inbounds i8, ptr %0, i32 4 %3 = load ptr, ptr %2, align 4 call void @"main.closureFunctionGoroutine$1"(i32 %1, ptr %3) ret void @@ -104,9 +104,9 @@ define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %co entry: %0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #9 store i32 5, ptr %0, align 4 - %1 = getelementptr inbounds { i32, ptr, ptr }, ptr %0, i32 0, i32 1 + %1 = getelementptr inbounds i8, ptr %0, i32 4 store ptr %fn.context, ptr %1, align 4 - %2 = getelementptr inbounds { i32, ptr, ptr }, ptr %0, i32 0, i32 2 + %2 = getelementptr inbounds i8, ptr %0, i32 8 store ptr %fn.funcptr, ptr %2, align 4 %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr undef) #9 call void @"internal/task.start"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #9 @@ -117,9 +117,9 @@ entry: define linkonce_odr void @main.funcGoroutine.gowrapper(ptr %0) unnamed_addr #6 { entry: %1 = load i32, ptr %0, align 4 - %2 = getelementptr inbounds { i32, ptr, ptr }, ptr %0, i32 0, i32 1 + %2 = getelementptr inbounds i8, ptr %0, i32 4 %3 = load ptr, ptr %2, align 4 - %4 = getelementptr inbounds { i32, ptr, ptr }, ptr %0, i32 0, i32 2 + %4 = getelementptr inbounds i8, ptr %0, i32 8 %5 = load ptr, ptr %4, align 4 call void %5(i32 %1, ptr %3) #9 ret void @@ -154,11 +154,11 @@ define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, entry: %0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #9 store ptr %itf.value, ptr %0, align 4 - %1 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 1 + %1 = getelementptr inbounds i8, ptr %0, i32 4 store ptr @"main$string", ptr %1, align 4 - %2 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 2 + %2 = getelementptr inbounds i8, ptr %0, i32 8 store i32 4, ptr %2, align 4 - %3 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 3 + %3 = getelementptr inbounds i8, ptr %0, i32 12 store ptr %itf.typecode, ptr %3, align 4 %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr undef) #9 call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #9 @@ -171,23 +171,23 @@ declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr, ptr, i define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(ptr %0) unnamed_addr #8 { entry: %1 = load ptr, ptr %0, align 4 - %2 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 1 + %2 = getelementptr inbounds i8, ptr %0, i32 4 %3 = load ptr, ptr %2, align 4 - %4 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 2 + %4 = getelementptr inbounds i8, ptr %0, i32 8 %5 = load i32, ptr %4, align 4 - %6 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 3 + %6 = getelementptr inbounds i8, ptr %0, i32 12 %7 = load ptr, ptr %6, align 4 call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, ptr %7, ptr undef) #9 ret void } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } -attributes #1 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } -attributes #2 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } -attributes #3 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="main.regularFunction" } -attributes #4 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" } -attributes #5 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="main.closureFunctionGoroutine$1" } -attributes #6 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper" } -attributes #7 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" } -attributes #8 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } +attributes #1 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } +attributes #2 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } +attributes #3 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="main.regularFunction" } +attributes #4 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" } +attributes #5 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="main.closureFunctionGoroutine$1" } +attributes #6 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper" } +attributes #7 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" } +attributes #8 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" } attributes #9 = { nounwind } diff --git a/compiler/testdata/goroutine-wasm-asyncify.ll b/compiler/testdata/goroutine-wasm-asyncify.ll index 87c7381f25..3c0db4b941 100644 --- a/compiler/testdata/goroutine-wasm-asyncify.ll +++ b/compiler/testdata/goroutine-wasm-asyncify.ll @@ -72,7 +72,7 @@ entry: %0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #9 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #9 store i32 5, ptr %0, align 4 - %1 = getelementptr inbounds { i32, ptr }, ptr %0, i32 0, i32 1 + %1 = getelementptr inbounds i8, ptr %0, i32 4 store ptr %n, ptr %1, align 4 call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #9 %2 = load i32, ptr %n, align 4 @@ -93,7 +93,7 @@ entry: define linkonce_odr void @"main.closureFunctionGoroutine$1$gowrapper"(ptr %0) unnamed_addr #5 { entry: %1 = load i32, ptr %0, align 4 - %2 = getelementptr inbounds { i32, ptr }, ptr %0, i32 0, i32 1 + %2 = getelementptr inbounds i8, ptr %0, i32 4 %3 = load ptr, ptr %2, align 4 call void @"main.closureFunctionGoroutine$1"(i32 %1, ptr %3) call void @runtime.deadlock(ptr undef) #9 @@ -113,9 +113,9 @@ entry: %0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #9 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #9 store i32 5, ptr %0, align 4 - %1 = getelementptr inbounds { i32, ptr, ptr }, ptr %0, i32 0, i32 1 + %1 = getelementptr inbounds i8, ptr %0, i32 4 store ptr %fn.context, ptr %1, align 4 - %2 = getelementptr inbounds { i32, ptr, ptr }, ptr %0, i32 0, i32 2 + %2 = getelementptr inbounds i8, ptr %0, i32 8 store ptr %fn.funcptr, ptr %2, align 4 call void @"internal/task.start"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr nonnull %0, i32 65536, ptr undef) #9 ret void @@ -125,9 +125,9 @@ entry: define linkonce_odr void @main.funcGoroutine.gowrapper(ptr %0) unnamed_addr #6 { entry: %1 = load i32, ptr %0, align 4 - %2 = getelementptr inbounds { i32, ptr, ptr }, ptr %0, i32 0, i32 1 + %2 = getelementptr inbounds i8, ptr %0, i32 4 %3 = load ptr, ptr %2, align 4 - %4 = getelementptr inbounds { i32, ptr, ptr }, ptr %0, i32 0, i32 2 + %4 = getelementptr inbounds i8, ptr %0, i32 8 %5 = load ptr, ptr %4, align 4 call void %5(i32 %1, ptr %3) #9 call void @runtime.deadlock(ptr undef) #9 @@ -165,11 +165,11 @@ entry: %0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #9 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #9 store ptr %itf.value, ptr %0, align 4 - %1 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 1 + %1 = getelementptr inbounds i8, ptr %0, i32 4 store ptr @"main$string", ptr %1, align 4 - %2 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 2 + %2 = getelementptr inbounds i8, ptr %0, i32 8 store i32 4, ptr %2, align 4 - %3 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 3 + %3 = getelementptr inbounds i8, ptr %0, i32 12 store ptr %itf.typecode, ptr %3, align 4 call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #9 ret void @@ -181,24 +181,24 @@ declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr, ptr, i define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(ptr %0) unnamed_addr #8 { entry: %1 = load ptr, ptr %0, align 4 - %2 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 1 + %2 = getelementptr inbounds i8, ptr %0, i32 4 %3 = load ptr, ptr %2, align 4 - %4 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 2 + %4 = getelementptr inbounds i8, ptr %0, i32 8 %5 = load i32, ptr %4, align 4 - %6 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 3 + %6 = getelementptr inbounds i8, ptr %0, i32 12 %7 = load ptr, ptr %6, align 4 call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, ptr %7, ptr undef) #9 call void @runtime.deadlock(ptr undef) #9 unreachable } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #3 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-gowrapper"="main.regularFunction" } -attributes #4 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" } -attributes #5 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-gowrapper"="main.closureFunctionGoroutine$1" } -attributes #6 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-gowrapper" } -attributes #7 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" } -attributes #8 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #3 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="main.regularFunction" } +attributes #4 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" } +attributes #5 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="main.closureFunctionGoroutine$1" } +attributes #6 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper" } +attributes #7 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" } +attributes #8 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" } attributes #9 = { nounwind } diff --git a/compiler/testdata/interface.ll b/compiler/testdata/interface.ll index 801f370d58..49b501da2a 100644 --- a/compiler/testdata/interface.ll +++ b/compiler/testdata/interface.ll @@ -130,11 +130,11 @@ entry: declare %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(ptr, ptr, ptr) #6 -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #3 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-methods"="reflect/methods.Error() string" } -attributes #4 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-methods"="reflect/methods.String() string" } -attributes #5 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-invoke"="main.$methods.foo(int) uint8" "tinygo-methods"="reflect/methods.String() string; main.$methods.foo(int) uint8" } -attributes #6 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-invoke"="reflect/methods.Error() string" "tinygo-methods"="reflect/methods.Error() string" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #3 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.Error() string" } +attributes #4 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.String() string" } +attributes #5 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="main.$methods.foo(int) uint8" "tinygo-methods"="reflect/methods.String() string; main.$methods.foo(int) uint8" } +attributes #6 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Error() string" "tinygo-methods"="reflect/methods.Error() string" } attributes #7 = { nounwind } diff --git a/compiler/testdata/pointer.ll b/compiler/testdata/pointer.ll index a659b21744..eb551c6872 100644 --- a/compiler/testdata/pointer.ll +++ b/compiler/testdata/pointer.ll @@ -44,7 +44,7 @@ entry: ret ptr %x } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind } diff --git a/compiler/testdata/pragma.ll b/compiler/testdata/pragma.ll index 31b9fc658a..a3cbb72c1d 100644 --- a/compiler/testdata/pragma.ll +++ b/compiler/testdata/pragma.ll @@ -93,13 +93,13 @@ entry: ret void } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #3 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "wasm-export-name"="extern_func" } -attributes #4 = { inlinehint nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #5 = { noinline nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #6 = { noinline nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "wasm-export-name"="exportedFunctionInSection" } -attributes #7 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "wasm-import-module"="modulename" "wasm-import-name"="import1" } -attributes #8 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "wasm-import-module"="foobar" "wasm-import-name"="imported" } -attributes #9 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "wasm-export-name"="exported" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #3 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-export-name"="extern_func" } +attributes #4 = { inlinehint nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #5 = { noinline nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #6 = { noinline nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-export-name"="exportedFunctionInSection" } +attributes #7 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-import-module"="modulename" "wasm-import-name"="import1" } +attributes #8 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-import-module"="foobar" "wasm-import-name"="imported" } +attributes #9 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-export-name"="exported" } diff --git a/compiler/testdata/slice.ll b/compiler/testdata/slice.ll index 092dabbff4..24623ccc68 100644 --- a/compiler/testdata/slice.ll +++ b/compiler/testdata/slice.ll @@ -51,9 +51,9 @@ entry: %varargs = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %varargs, ptr nonnull %stackalloc, ptr undef) #3 store i32 1, ptr %varargs, align 4 - %0 = getelementptr inbounds [3 x i32], ptr %varargs, i32 0, i32 1 + %0 = getelementptr inbounds i8, ptr %varargs, i32 4 store i32 2, ptr %0, align 4 - %1 = getelementptr inbounds [3 x i32], ptr %varargs, i32 0, i32 2 + %1 = getelementptr inbounds i8, ptr %varargs, i32 8 store i32 3, ptr %1, align 4 %append.new = call { ptr, i32, i32 } @runtime.sliceAppend(ptr %ints.data, ptr nonnull %varargs, i32 %ints.len, i32 %ints.cap, i32 3, i32 4, ptr undef) #3 %append.newPtr = extractvalue { ptr, i32, i32 } %append.new, 0 @@ -286,7 +286,7 @@ entry: br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next unsafe.Slice.next: ; preds = %entry - %5 = trunc i64 %len to i32 + %5 = trunc nuw i64 %len to i32 %6 = insertvalue { ptr, i32, i32 } undef, ptr %ptr, 0 %7 = insertvalue { ptr, i32, i32 } %6, i32 %5, 1 %8 = insertvalue { ptr, i32, i32 } %7, i32 %5, 2 @@ -310,7 +310,7 @@ entry: br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next unsafe.Slice.next: ; preds = %entry - %5 = trunc i64 %len to i32 + %5 = trunc nuw i64 %len to i32 %6 = insertvalue { ptr, i32, i32 } undef, ptr %ptr, 0 %7 = insertvalue { ptr, i32, i32 } %6, i32 %5, 1 %8 = insertvalue { ptr, i32, i32 } %7, i32 %5, 2 @@ -322,7 +322,7 @@ unsafe.Slice.throw: ; preds = %entry unreachable } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind } diff --git a/compiler/testdata/string.ll b/compiler/testdata/string.ll index 64582dd961..2a00955fe7 100644 --- a/compiler/testdata/string.ll +++ b/compiler/testdata/string.ll @@ -97,7 +97,7 @@ lookup.throw: ; preds = %entry unreachable } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind } diff --git a/compiler/testdata/zeromap.ll b/compiler/testdata/zeromap.ll index 4ad263130a..928809187f 100644 --- a/compiler/testdata/zeromap.ll +++ b/compiler/testdata/zeromap.ll @@ -81,7 +81,7 @@ entry: call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %hashmap.key) %s.elt = extractvalue [2 x %main.hasPadding] %s, 0 store %main.hasPadding %s.elt, ptr %hashmap.key, align 4 - %hashmap.key.repack1 = getelementptr inbounds [2 x %main.hasPadding], ptr %hashmap.key, i32 0, i32 1 + %hashmap.key.repack1 = getelementptr inbounds i8, ptr %hashmap.key, i32 12 %s.elt2 = extractvalue [2 x %main.hasPadding] %s, 1 store %main.hasPadding %s.elt2, ptr %hashmap.key.repack1, align 4 %0 = getelementptr inbounds i8, ptr %hashmap.key, i32 1 @@ -109,7 +109,7 @@ entry: call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %hashmap.key) %s.elt = extractvalue [2 x %main.hasPadding] %s, 0 store %main.hasPadding %s.elt, ptr %hashmap.key, align 4 - %hashmap.key.repack1 = getelementptr inbounds [2 x %main.hasPadding], ptr %hashmap.key, i32 0, i32 1 + %hashmap.key.repack1 = getelementptr inbounds i8, ptr %hashmap.key, i32 12 %s.elt2 = extractvalue [2 x %main.hasPadding] %s, 1 store %main.hasPadding %s.elt2, ptr %hashmap.key.repack1, align 4 %0 = getelementptr inbounds i8, ptr %hashmap.key, i32 1 @@ -132,9 +132,9 @@ entry: ret void } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #3 = { noinline nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #3 = { noinline nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #4 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } attributes #5 = { nounwind } diff --git a/go.mod b/go.mod index a4de141365..ec52702d53 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/tinygo-org/tinygo go 1.19 require ( - github.com/aykevl/go-wasm v0.0.2-0.20240312204833-50275154210c + github.com/aykevl/go-wasm v0.0.2-0.20240825160117-b76c3f9f0982 github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 github.com/chromedp/cdproto v0.0.0-20220113222801-0725d94bb6ee github.com/chromedp/chromedp v0.7.6 @@ -20,7 +20,7 @@ require ( golang.org/x/sys v0.21.0 golang.org/x/tools v0.22.1-0.20240621165957-db513b091504 gopkg.in/yaml.v2 v2.4.0 - tinygo.org/x/go-llvm v0.0.0-20240627184919-3b50c76783a8 + tinygo.org/x/go-llvm v0.0.0-20250119132755-9dca92dfb4f9 ) require ( diff --git a/go.sum b/go.sum index d3c1bd310c..f8cef17c11 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/aykevl/go-wasm v0.0.2-0.20240312204833-50275154210c h1:4T0Vj1UkGgcpkRrmn7SbokebnlfxJcMZPgWtOYACAAA= -github.com/aykevl/go-wasm v0.0.2-0.20240312204833-50275154210c/go.mod h1:7sXyiaA0WtSogCu67R2252fQpVmJMh9JWJ9ddtGkpWw= +github.com/aykevl/go-wasm v0.0.2-0.20240825160117-b76c3f9f0982 h1:cD7QfvrJdYmBw2tFP/VyKPT8ZESlcrwSwo7SvH9Y4dc= +github.com/aykevl/go-wasm v0.0.2-0.20240825160117-b76c3f9f0982/go.mod h1:7sXyiaA0WtSogCu67R2252fQpVmJMh9JWJ9ddtGkpWw= github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI= github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/chromedp/cdproto v0.0.0-20211126220118-81fa0469ad77/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U= @@ -74,5 +74,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -tinygo.org/x/go-llvm v0.0.0-20240627184919-3b50c76783a8 h1:bLsZXRUBavt++CJlMN7sppNziqu3LyamESLhFJcpqFQ= -tinygo.org/x/go-llvm v0.0.0-20240627184919-3b50c76783a8/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0= +tinygo.org/x/go-llvm v0.0.0-20250119132755-9dca92dfb4f9 h1:rMvEzuCYjyiR+pmdiCVWTQw3L6VqiSIXoL19I3lYufE= +tinygo.org/x/go-llvm v0.0.0-20250119132755-9dca92dfb4f9/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0= diff --git a/interp/memory.go b/interp/memory.go index 3b36a80b31..ba224f9d59 100644 --- a/interp/memory.go +++ b/interp/memory.go @@ -1046,6 +1046,8 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) { v.buf[i] = ptrValue.pointer } case llvm.ICmp: + // Note: constant icmp isn't supported anymore in LLVM 19. + // Once we drop support for LLVM 18, this can be removed. size := r.targetData.TypeAllocSize(llvmValue.Operand(0).Type()) lhs := newRawValue(uint32(size)) rhs := newRawValue(uint32(size)) diff --git a/interp/testdata/consteval.ll b/interp/testdata/consteval.ll index 3cbe7fcf73..3845719d55 100644 --- a/interp/testdata/consteval.ll +++ b/interp/testdata/consteval.ll @@ -3,7 +3,6 @@ target triple = "x86_64--linux" @intToPtrResult = global i8 0 @ptrToIntResult = global i8 0 -@icmpResult = global i8 0 @pointerTagResult = global i64 0 @someArray = internal global {i16, i8, i8} zeroinitializer @someArrayPointer = global ptr zeroinitializer @@ -17,7 +16,6 @@ define internal void @main.init() { call void @testIntToPtr() call void @testPtrToInt() call void @testConstGEP() - call void @testICmp() call void @testPointerTag() ret void } @@ -53,19 +51,6 @@ define internal void @testConstGEP() { ret void } -define internal void @testICmp() { - br i1 icmp eq (i64 ptrtoint (ptr @ptrToIntResult to i64), i64 0), label %equal, label %unequal -equal: - ; should not be reached - store i8 1, ptr @icmpResult - ret void -unequal: - ; should be reached - store i8 2, ptr @icmpResult - ret void - ret void -} - define internal void @testPointerTag() { %val = and i64 ptrtoint (ptr getelementptr inbounds (i8, ptr @someArray, i32 2) to i64), 3 store i64 %val, ptr @pointerTagResult diff --git a/interp/testdata/consteval.out.ll b/interp/testdata/consteval.out.ll index fff0e18f96..679f09f625 100644 --- a/interp/testdata/consteval.out.ll +++ b/interp/testdata/consteval.out.ll @@ -3,10 +3,9 @@ target triple = "x86_64--linux" @intToPtrResult = local_unnamed_addr global i8 2 @ptrToIntResult = local_unnamed_addr global i8 2 -@icmpResult = local_unnamed_addr global i8 2 @pointerTagResult = local_unnamed_addr global i64 2 @someArray = internal global { i16, i8, i8 } zeroinitializer -@someArrayPointer = local_unnamed_addr global ptr getelementptr inbounds ({ i16, i8, i8 }, ptr @someArray, i64 0, i32 1) +@someArrayPointer = local_unnamed_addr global ptr getelementptr inbounds (i8, ptr @someArray, i64 2) define void @runtime.initAll() local_unnamed_addr { ret void diff --git a/targets/cortex-m3.json b/targets/cortex-m3.json index bb11efea5d..44d992a91f 100644 --- a/targets/cortex-m3.json +++ b/targets/cortex-m3.json @@ -2,5 +2,5 @@ "inherits": ["cortex-m"], "llvm-target": "thumbv7m-unknown-unknown-eabi", "cpu": "cortex-m3", - "features": "+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + "features": "+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } diff --git a/targets/cortex-m33.json b/targets/cortex-m33.json index 556b1e2af1..a5582c0823 100644 --- a/targets/cortex-m33.json +++ b/targets/cortex-m33.json @@ -2,5 +2,5 @@ "inherits": ["cortex-m"], "llvm-target": "thumbv8m.main-unknown-unknown-eabi", "cpu": "cortex-m33", - "features": "+armv8-m.main,+dsp,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + "features": "+armv8-m.main,+dsp,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } diff --git a/targets/cortex-m4.json b/targets/cortex-m4.json index 58b1673647..80ed66d407 100644 --- a/targets/cortex-m4.json +++ b/targets/cortex-m4.json @@ -2,5 +2,5 @@ "inherits": ["cortex-m"], "llvm-target": "thumbv7em-unknown-unknown-eabi", "cpu": "cortex-m4", - "features": "+armv7e-m,+dsp,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + "features": "+armv7e-m,+dsp,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } diff --git a/targets/cortex-m7.json b/targets/cortex-m7.json index e9abf1de41..1a39c6a9ec 100644 --- a/targets/cortex-m7.json +++ b/targets/cortex-m7.json @@ -2,5 +2,5 @@ "inherits": ["cortex-m"], "llvm-target": "thumbv7em-unknown-unknown-eabi", "cpu": "cortex-m7", - "features": "+armv7e-m,+dsp,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + "features": "+armv7e-m,+dsp,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } diff --git a/targets/esp32.ld b/targets/esp32.ld index a8d161288e..6818ce3190 100644 --- a/targets/esp32.ld +++ b/targets/esp32.ld @@ -29,7 +29,7 @@ SECTIONS */ .text : ALIGN(4) { - *(.literal.text.call_start_cpu0) + *(.literal.call_start_cpu0) *(.text.call_start_cpu0) *(.literal .text) *(.literal.* .text.*) diff --git a/targets/esp32c3.json b/targets/esp32c3.json index 9d1c5cff77..900c4845eb 100644 --- a/targets/esp32c3.json +++ b/targets/esp32c3.json @@ -1,6 +1,6 @@ { "inherits": ["riscv32"], - "features": "+32bit,+c,+m,-a,-d,-e,-experimental-zacas,-experimental-zcmop,-experimental-zfbfmin,-experimental-zicfilp,-experimental-zicfiss,-experimental-zimop,-experimental-ztso,-experimental-zvfbfmin,-experimental-zvfbfwma,-f,-h,-relax,-smaia,-smepmp,-ssaia,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-za128rs,-za64rs,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmp,-zcmt,-zdinx,-zfa,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-zmmul,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-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-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", "build-tags": ["esp32c3", "esp"], "serial": "usb", "rtlib": "compiler-rt", diff --git a/targets/fe310.json b/targets/fe310.json index b96ae6d684..cd92c4fb1b 100644 --- a/targets/fe310.json +++ b/targets/fe310.json @@ -1,6 +1,6 @@ { "inherits": ["riscv32"], "cpu": "sifive-e31", - "features": "+32bit,+a,+c,+m,-d,-e,-experimental-zacas,-experimental-zcmop,-experimental-zfbfmin,-experimental-zicfilp,-experimental-zicfiss,-experimental-zimop,-experimental-ztso,-experimental-zvfbfmin,-experimental-zvfbfwma,-f,-h,-relax,-smaia,-smepmp,-ssaia,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-za128rs,-za64rs,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmp,-zcmt,-zdinx,-zfa,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-zmmul,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-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-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", "build-tags": ["fe310", "sifive"] } diff --git a/targets/k210.json b/targets/k210.json index d95a6f5113..2140f459e4 100644 --- a/targets/k210.json +++ b/targets/k210.json @@ -1,6 +1,6 @@ { "inherits": ["riscv64"], - "features": "+64bit,+a,+c,+d,+f,+m,+zicsr,+zifencei,-e,-experimental-zacas,-experimental-zcmop,-experimental-zfbfmin,-experimental-zicfilp,-experimental-zicfiss,-experimental-zimop,-experimental-ztso,-experimental-zvfbfmin,-experimental-zvfbfwma,-h,-relax,-smaia,-smepmp,-ssaia,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-za128rs,-za64rs,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmp,-zcmt,-zdinx,-zfa,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zihintntl,-zihintpause,-zihpm,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-zmmul,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-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-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", "build-tags": ["k210", "kendryte"], "code-model": "medium" } diff --git a/targets/nintendoswitch.json b/targets/nintendoswitch.json index e86cfc1712..f83f8fcc10 100644 --- a/targets/nintendoswitch.json +++ b/targets/nintendoswitch.json @@ -1,7 +1,7 @@ { "llvm-target": "aarch64", "cpu": "cortex-a57", - "features": "+aes,+crc,+fp-armv8,+neon,+sha2,+v8a,-fmv", + "features": "+aes,+crc,+fp-armv8,+neon,+perfmon,+sha2,+v8a,-fmv", "build-tags": ["nintendoswitch", "arm64"], "scheduler": "tasks", "goos": "linux", diff --git a/targets/riscv-qemu.json b/targets/riscv-qemu.json index cbe7d3c045..cb26829686 100644 --- a/targets/riscv-qemu.json +++ b/targets/riscv-qemu.json @@ -1,6 +1,6 @@ { "inherits": ["riscv32"], - "features": "+32bit,+a,+c,+m,-d,-e,-experimental-zacas,-experimental-zcmop,-experimental-zfbfmin,-experimental-zicfilp,-experimental-zicfiss,-experimental-zimop,-experimental-ztso,-experimental-zvfbfmin,-experimental-zvfbfwma,-f,-h,-relax,-smaia,-smepmp,-ssaia,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-za128rs,-za64rs,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmp,-zcmt,-zdinx,-zfa,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-zmmul,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-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-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", "build-tags": ["virt", "qemu"], "default-stack-size": 4096, "linkerscript": "targets/riscv-qemu.ld", diff --git a/targets/tkey.json b/targets/tkey.json index f450e6a1b1..4e953bcff5 100644 --- a/targets/tkey.json +++ b/targets/tkey.json @@ -1,7 +1,7 @@ { "inherits": ["riscv32"], "build-tags": ["tkey"], - "features": "+32bit,+c,+zmmul,-a,-d,-e,-experimental-zacas,-experimental-zcmop,-experimental-zfbfmin,-experimental-zicfilp,-experimental-zicfiss,-experimental-zimop,-experimental-ztso,-experimental-zvfbfmin,-experimental-zvfbfwma,-f,-h,-m,-relax,-smaia,-smepmp,-ssaia,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-za128rs,-za64rs,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmp,-zcmt,-zdinx,-zfa,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-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,+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,-m,-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", "cflags": [ "-march=rv32iczmmul" ], diff --git a/targets/wasip1.json b/targets/wasip1.json index 4181f16ee9..25ac7c3a6c 100644 --- a/targets/wasip1.json +++ b/targets/wasip1.json @@ -1,7 +1,7 @@ { "llvm-target": "wasm32-unknown-wasi", "cpu": "generic", - "features": "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext", + "features": "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types", "build-tags": ["tinygo.wasm"], "goos": "wasip1", "goarch": "wasm", @@ -14,6 +14,8 @@ "cflags": [ "-mbulk-memory", "-mnontrapping-fptoint", + "-mno-multivalue", + "-mno-reference-types", "-msign-ext" ], "ldflags": [ diff --git a/targets/wasip2.json b/targets/wasip2.json index 786536f2b0..66e79eda5a 100644 --- a/targets/wasip2.json +++ b/targets/wasip2.json @@ -1,7 +1,7 @@ { "llvm-target": "wasm32-unknown-wasi", "cpu": "generic", - "features": "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext", + "features": "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types", "build-tags": ["tinygo.wasm", "wasip2"], "buildmode": "c-shared", "goos": "linux", @@ -15,6 +15,8 @@ "cflags": [ "-mbulk-memory", "-mnontrapping-fptoint", + "-mno-multivalue", + "-mno-reference-types", "-msign-ext" ], "ldflags": [ diff --git a/targets/wasm-unknown.json b/targets/wasm-unknown.json index f07a2406ed..d64fa575cb 100644 --- a/targets/wasm-unknown.json +++ b/targets/wasm-unknown.json @@ -1,7 +1,7 @@ { "llvm-target": "wasm32-unknown-unknown", "cpu": "generic", - "features": "+mutable-globals,+nontrapping-fptoint,+sign-ext", + "features": "+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types", "build-tags": ["tinygo.wasm", "wasm_unknown"], "buildmode": "c-shared", "goos": "linux", @@ -14,6 +14,8 @@ "default-stack-size": 4096, "cflags": [ "-mnontrapping-fptoint", + "-mno-multivalue", + "-mno-reference-types", "-msign-ext" ], "ldflags": [ diff --git a/targets/wasm.json b/targets/wasm.json index 48a6788910..1333647e98 100644 --- a/targets/wasm.json +++ b/targets/wasm.json @@ -1,7 +1,7 @@ { "llvm-target": "wasm32-unknown-wasi", "cpu": "generic", - "features": "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext", + "features": "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types", "build-tags": ["tinygo.wasm"], "goos": "js", "goarch": "wasm", @@ -14,6 +14,8 @@ "cflags": [ "-mbulk-memory", "-mnontrapping-fptoint", + "-mno-multivalue", + "-mno-reference-types", "-msign-ext" ], "ldflags": [ From 74effd2fa9f1918c85dde7eef942bf8b448cabf8 Mon Sep 17 00:00:00 2001 From: Scott Feldman Date: Mon, 20 Jan 2025 04:48:51 -0800 Subject: [PATCH 349/444] targets: add support for Elecrow Pico rp2040 W5 boards (#4705) https://www.elecrow.com/pico-w5-microcontroller-development-boards-rp2040-microcontroller-board-support-wifi-2-4ghz-5ghz-bluetooth5.html Just the basics to get several test to work (blinky1, flash). This board has an rtl8720d Wifi/bluetooth chip wired to UART1, but the rtl8720d firmware installed is the AT cmd set, not the RPC interface used by the rtl8720dn driver, so no wifi support at the moment. --- GNUmakefile | 2 + src/machine/board_elecrow-rp2040-w5.go | 94 ++++++++++++++++++++++++++ targets/elecrow-rp2040.json | 12 ++++ 3 files changed, 108 insertions(+) create mode 100644 src/machine/board_elecrow-rp2040-w5.go create mode 100644 targets/elecrow-rp2040.json diff --git a/GNUmakefile b/GNUmakefile index 57d382d391..f6199ab7e2 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -861,6 +861,8 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=tkey examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=elecrow-rp2040 examples/blinky1 + @$(MD5SUM) test.hex ifneq ($(WASM), 0) $(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/export $(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/main diff --git a/src/machine/board_elecrow-rp2040-w5.go b/src/machine/board_elecrow-rp2040-w5.go new file mode 100644 index 0000000000..1ea0181d1f --- /dev/null +++ b/src/machine/board_elecrow-rp2040-w5.go @@ -0,0 +1,94 @@ +//go:build elecrow_rp2040 + +// This file contains the pin mappings for the Elecrow Pico rp2040 W5 boards. +// +// Elecrow Pico rp2040 W5 is a microcontroller using the Raspberry Pi RP2040 +// chip and rtl8720d Wifi chip. +// +// - https://www.elecrow.com/wiki/PICO_W5_RP2040_Dev_Board.html +package machine + +// GPIO pins +const ( + GP0 Pin = GPIO0 + GP1 Pin = GPIO1 + GP2 Pin = GPIO2 + GP3 Pin = GPIO3 + GP4 Pin = GPIO4 + GP5 Pin = GPIO5 + GP6 Pin = GPIO6 + GP7 Pin = GPIO7 + GP8 Pin = GPIO8 + GP9 Pin = GPIO9 + GP10 Pin = GPIO10 + GP11 Pin = GPIO11 + GP12 Pin = GPIO12 + GP13 Pin = GPIO13 + GP14 Pin = GPIO14 + GP15 Pin = GPIO15 + GP16 Pin = GPIO16 + GP17 Pin = GPIO17 + GP18 Pin = GPIO18 + GP19 Pin = GPIO19 + GP20 Pin = GPIO20 + GP21 Pin = GPIO21 + GP22 Pin = GPIO22 + GP26 Pin = GPIO26 + GP27 Pin = GPIO27 + GP28 Pin = GPIO28 + + // Onboard LED + LED Pin = GPIO25 + + // Onboard crystal oscillator frequency, in MHz. + xoscFreq = 12 // MHz +) + +// I2C Default pins on Raspberry Pico. +const ( + I2C0_SDA_PIN = GP4 + I2C0_SCL_PIN = GP5 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO18 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO19 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO16 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO10 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO11 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO12 // Rx +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO4 // Wired to rtl8720d UART1_Tx + UART1_RX_PIN = GPIO5 // Wired to rtl8720n UART1_Rx + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Pico" + usb_STRING_MANUFACTURER = "Raspberry Pi" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000A +) diff --git a/targets/elecrow-rp2040.json b/targets/elecrow-rp2040.json new file mode 100644 index 0000000000..07391a6ee0 --- /dev/null +++ b/targets/elecrow-rp2040.json @@ -0,0 +1,12 @@ +{ + "inherits": ["rp2040"], + "build-tags": ["elecrow_rp2040"], + "serial-port": ["2e8a:000a"], + "default-stack-size": 8192, + "ldflags": [ + "--defsym=__flash_size=8M" + ], + "extra-files": [ + "targets/pico-boot-stage2.S" + ] +} From 7305a44f0ccaf5ab313da24427b126e3429f0d21 Mon Sep 17 00:00:00 2001 From: Scott Feldman Date: Mon, 20 Jan 2025 06:36:03 -0800 Subject: [PATCH 350/444] targets: add support for Elecrow Pico rp2350 W5 boards (#4706) https://www.elecrow.com/pico-w5-microcontroller-development-boards-rp2350-microcontroller-board.html Just the basics to get several test to work (blinky1, flash). This board has an rtl8720d Wifi/bluetooth chip wired to UART1, but the rtl8720d firmware installed is the AT cmd set, not the RPC interface used by the rtl8720dn driver, so no wifi support at the moment. --------- Co-authored-by: Yurii Soldak --- GNUmakefile | 2 + src/machine/board_elecrow-rp2350-w5.go | 94 ++++++++++++++++++++++++++ targets/elecrow-rp2350.json | 12 ++++ 3 files changed, 108 insertions(+) create mode 100644 src/machine/board_elecrow-rp2350-w5.go create mode 100644 targets/elecrow-rp2350.json diff --git a/GNUmakefile b/GNUmakefile index f6199ab7e2..666021547e 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -863,6 +863,8 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=elecrow-rp2040 examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=elecrow-rp2350 examples/blinky1 + @$(MD5SUM) test.hex ifneq ($(WASM), 0) $(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/export $(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/main diff --git a/src/machine/board_elecrow-rp2350-w5.go b/src/machine/board_elecrow-rp2350-w5.go new file mode 100644 index 0000000000..80a8436444 --- /dev/null +++ b/src/machine/board_elecrow-rp2350-w5.go @@ -0,0 +1,94 @@ +//go:build elecrow_rp2350 + +// This file contains the pin mappings for the Elecrow Pico rp2350 W5 boards. +// +// Elecrow Pico rp2350 W5 is a microcontroller using the Raspberry Pi RP2350 +// chip and rtl8720d Wifi chip. +// +// - https://www.elecrow.com/pico-w5-microcontroller-development-boards-rp2350-microcontroller-board.html +package machine + +// GPIO pins +const ( + GP0 Pin = GPIO0 + GP1 Pin = GPIO1 + GP2 Pin = GPIO2 + GP3 Pin = GPIO3 + GP4 Pin = GPIO4 + GP5 Pin = GPIO5 + GP6 Pin = GPIO6 + GP7 Pin = GPIO7 + GP8 Pin = GPIO8 + GP9 Pin = GPIO9 + GP10 Pin = GPIO10 + GP11 Pin = GPIO11 + GP12 Pin = GPIO12 + GP13 Pin = GPIO13 + GP14 Pin = GPIO14 + GP15 Pin = GPIO15 + GP16 Pin = GPIO16 + GP17 Pin = GPIO17 + GP18 Pin = GPIO18 + GP19 Pin = GPIO19 + GP20 Pin = GPIO20 + GP21 Pin = GPIO21 + GP22 Pin = GPIO22 + GP26 Pin = GPIO26 + GP27 Pin = GPIO27 + GP28 Pin = GPIO28 + + // Onboard LED + LED Pin = GPIO25 + + // Onboard crystal oscillator frequency, in MHz. + xoscFreq = 12 // MHz +) + +// I2C Default pins on Raspberry Pico. +const ( + I2C0_SDA_PIN = GP4 + I2C0_SCL_PIN = GP5 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO18 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO19 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO16 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO10 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO11 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO12 // Rx +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO4 // Wired to rtl8720d UART1_Tx + UART1_RX_PIN = GPIO5 // Wired to rtl8720n UART1_Rx + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Pico2" + usb_STRING_MANUFACTURER = "Raspberry Pi" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000F +) diff --git a/targets/elecrow-rp2350.json b/targets/elecrow-rp2350.json new file mode 100644 index 0000000000..aecc86723b --- /dev/null +++ b/targets/elecrow-rp2350.json @@ -0,0 +1,12 @@ +{ + "inherits": ["rp2350"], + "build-tags": ["elecrow_rp2350"], + "serial-port": ["2e8a:000f"], + "default-stack-size": 8192, + "ldflags": [ + "--defsym=__flash_size=8M" + ], + "extra-files": [ + "targets/pico-boot-stage2.S" + ] +} From 4f39be9c3b79e3eeb073e5d7d32771f76d66516d Mon Sep 17 00:00:00 2001 From: vaaski Date: Mon, 20 Jan 2025 21:44:58 +0100 Subject: [PATCH 351/444] targets: esp32c3-supermini (#4518) --- GNUmakefile | 2 + src/machine/board_esp32c3-supermini.go | 57 ++++++++++++++++++++++++++ targets/esp32c3-supermini.json | 4 ++ 3 files changed, 63 insertions(+) create mode 100644 src/machine/board_esp32c3-supermini.go create mode 100644 targets/esp32c3-supermini.json diff --git a/GNUmakefile b/GNUmakefile index 666021547e..71480153f5 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -828,6 +828,8 @@ endif ifneq ($(XTENSA), 0) $(TINYGO) build -size short -o test.bin -target=esp32-mini32 examples/blinky1 @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/blinky1 + @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1 @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target m5stack-core2 examples/machinetest diff --git a/src/machine/board_esp32c3-supermini.go b/src/machine/board_esp32c3-supermini.go new file mode 100644 index 0000000000..c180ff0e3e --- /dev/null +++ b/src/machine/board_esp32c3-supermini.go @@ -0,0 +1,57 @@ +//go:build esp32c3_supermini + +// This file contains the pin mappings for the ESP32 supermini boards. +// +// - https://web.archive.org/web/20240805232453/https://dl.artronshop.co.th/ESP32-C3%20SuperMini%20datasheet.pdf + +package machine + +// Digital Pins +const ( + IO0 = GPIO0 + IO1 = GPIO1 + IO2 = GPIO2 + IO3 = GPIO3 + IO4 = GPIO4 + IO5 = GPIO5 + IO6 = GPIO6 + IO7 = GPIO7 + IO8 = GPIO8 + IO9 = GPIO9 + IO10 = GPIO10 + IO20 = GPIO20 + IO21 = GPIO21 +) + +// Built-in LED +const LED = GPIO8 + +// Analog pins +const ( + A0 = GPIO0 + A1 = GPIO1 + A2 = GPIO2 + A3 = GPIO3 + A4 = GPIO4 + A5 = GPIO5 +) + +// UART pins +const ( + UART_RX_PIN = GPIO20 + UART_TX_PIN = GPIO21 +) + +// I2C pins +const ( + SDA_PIN = GPIO8 + SCL_PIN = GPIO9 +) + +// SPI pins +const ( + SPI_MISO_PIN = GPIO5 + SPI_MOSI_PIN = GPIO6 + SPI_SS_PIN = GPIO7 + SPI_SCK_PIN = GPIO4 +) diff --git a/targets/esp32c3-supermini.json b/targets/esp32c3-supermini.json new file mode 100644 index 0000000000..3e4e40895e --- /dev/null +++ b/targets/esp32c3-supermini.json @@ -0,0 +1,4 @@ +{ + "inherits": ["esp32c3"], + "build-tags": ["esp32c3_supermini"] +} From 7417c14c2de17e837f85872ad54acee4ca0d3fe2 Mon Sep 17 00:00:00 2001 From: milkpirate Date: Mon, 20 Jan 2025 21:51:05 +0100 Subject: [PATCH 352/444] nrf: make ADC resolution changeable (#4701) Signed-off-by: Paul Schroeder --- src/machine/machine_nrf52xxx.go | 73 +++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/src/machine/machine_nrf52xxx.go b/src/machine/machine_nrf52xxx.go index ee035b74d6..2d3545b7a9 100644 --- a/src/machine/machine_nrf52xxx.go +++ b/src/machine/machine_nrf52xxx.go @@ -14,20 +14,17 @@ func CPUFrequency() uint32 { // InitADC initializes the registers needed for ADC. func InitADC() { - return // no specific setup on nrf52 machine. -} - -// Configure configures an ADC pin to be able to read analog data. -func (a ADC) Configure(config ADCConfig) { // Enable ADC. - // The ADC does not consume a noticeable amount of current simply by being - // enabled. + // The ADC does not consume a noticeable amount of current by being enabled. nrf.SAADC.ENABLE.Set(nrf.SAADC_ENABLE_ENABLE_Enabled << nrf.SAADC_ENABLE_ENABLE_Pos) +} - // Use fixed resolution of 12 bits. - // TODO: is it useful for users to change this? - nrf.SAADC.RESOLUTION.Set(nrf.SAADC_RESOLUTION_VAL_12bit) - +// Configure configures an ADC pin to be able to read analog data. +// Reference voltage can be 150, 300, 600, 1200, 1800, 2400, 3000(default), 3600 mV +// Resolution can be 8, 10, 12(default), 14 bits +// SampleTime will be ceiled to 3(default), 5, 10, 15, 20 or 40(max) µS respectively +// Samples can be 1(default), 2, 4, 8, 16, 32, 64, 128, 256 samples +func (a *ADC) Configure(config ADCConfig) { var configVal uint32 = nrf.SAADC_CH_CONFIG_RESP_Bypass< Date: Tue, 21 Jan 2025 11:33:12 +0100 Subject: [PATCH 353/444] nrf: fix adc read near zero --- src/machine/machine_nrf52xxx.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/machine/machine_nrf52xxx.go b/src/machine/machine_nrf52xxx.go index 2d3545b7a9..927458f543 100644 --- a/src/machine/machine_nrf52xxx.go +++ b/src/machine/machine_nrf52xxx.go @@ -183,7 +183,12 @@ func (a *ADC) Get() uint16 { resolutionAdjustment = 4 // 12bit } - return rawValue.Get() << resolutionAdjustment + value := int16(rawValue.Get()) + if value < 0 { + value = 0 + } + + return uint16(value << resolutionAdjustment) } // SPI on the NRF. From ec0a142190bd21ecefd91868f36df70e92e9ac2c Mon Sep 17 00:00:00 2001 From: deadprogram Date: Thu, 23 Jan 2025 09:44:21 +0100 Subject: [PATCH 354/444] build: update wasmtime used for CI to 29.0.1 to fix issue with install during CI tests Signed-off-by: deadprogram --- .github/workflows/linux.yml | 4 ++-- .github/workflows/windows.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 178467c9eb..de412bf658 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -151,7 +151,7 @@ jobs: - name: Install wasmtime uses: bytecodealliance/actions/wasmtime/setup@v1 with: - version: "19.0.1" + version: "29.0.1" - name: Install wasm-tools uses: bytecodealliance/actions/wasm-tools/setup@v1 - name: Download release artifact @@ -198,7 +198,7 @@ jobs: - name: Install wasmtime uses: bytecodealliance/actions/wasmtime/setup@v1 with: - version: "19.0.1" + version: "29.0.1" - name: Setup `wasm-tools` uses: bytecodealliance/actions/wasm-tools/setup@v1 - name: Restore LLVM source cache diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d547579434..e1794ef13b 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -109,7 +109,7 @@ jobs: C:/Users/runneradmin/go/pkg/mod - name: Install wasmtime run: | - scoop install wasmtime@14.0.4 + scoop install wasmtime@29.0.1 - name: make gen-device run: make -j3 gen-device - name: Test TinyGo @@ -216,7 +216,7 @@ jobs: - name: Install Dependencies shell: bash run: | - scoop install binaryen && scoop install wasmtime@14.0.4 + scoop install binaryen && scoop install wasmtime@29.0.1 - name: Checkout uses: actions/checkout@v4 - name: Install Go From 080a6648e972443482ebd1092e35778fcf25474f Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Wed, 22 Jan 2025 20:41:52 +0100 Subject: [PATCH 355/444] targets: match Pico2 stack size to Pico Took me a while to debug weird crashes after switching from Pico to Pico2. --- targets/pico2.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/targets/pico2.json b/targets/pico2.json index 3c91fa47bd..dfdac71ee0 100644 --- a/targets/pico2.json +++ b/targets/pico2.json @@ -3,5 +3,6 @@ "rp2350" ], "build-tags": ["pico2"], - "serial-port": ["2e8a:000A"] + "serial-port": ["2e8a:000A"], + "default-stack-size": 8192 } From d55a89f96a0f21105c575847e1bee11dabaed283 Mon Sep 17 00:00:00 2001 From: HattoriHanzo031 Date: Thu, 30 Jan 2025 05:46:52 +0100 Subject: [PATCH 356/444] board: support for NRF51 HW-651 (#4712) NRF51: Support for NRF51 HW-651 board * smoketest * removed redundant flash-method * smoketest fix * description and links * link fix --- GNUmakefile | 4 ++ src/machine/board_hw-651.go | 77 +++++++++++++++++++++++++++++++++++++ targets/hw-651-s110v8.json | 3 ++ targets/hw-651.json | 7 ++++ 4 files changed, 91 insertions(+) create mode 100644 src/machine/board_hw-651.go create mode 100644 targets/hw-651-s110v8.json create mode 100644 targets/hw-651.json diff --git a/GNUmakefile b/GNUmakefile index 71480153f5..2661fbd528 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -867,6 +867,10 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=elecrow-rp2350 examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=hw-651 examples/machinetest + @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=hw-651-s110v8 examples/machinetest + @$(MD5SUM) test.hex ifneq ($(WASM), 0) $(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/export $(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/main diff --git a/src/machine/board_hw-651.go b/src/machine/board_hw-651.go new file mode 100644 index 0000000000..54731502e1 --- /dev/null +++ b/src/machine/board_hw-651.go @@ -0,0 +1,77 @@ +//go:build hw_651 + +package machine + +// No-name brand board based on the nRF51822 chip with low frequency crystal on board. +// Pinout (reverse engineered from the board) can be found here: +// https://aviatorahmet.blogspot.com/2020/12/pinout-of-nrf51822-board.html +// https://cr0wg4n.medium.com/pinout-nrf51822-board-hw-651-78da2eda8894 + +const HasLowFrequencyCrystal = true + +var DefaultUART = UART0 + +// GPIO pins on header J1 +const ( + J1_01 = P0_21 + J1_03 = P0_23 + J1_04 = P0_22 + J1_05 = P0_25 + J1_06 = P0_24 + J1_09 = P0_29 + J1_10 = P0_28 + J1_11 = P0_30 + J1_13 = P0_00 + J1_15 = P0_02 + J1_17 = P0_04 + J1_16 = P0_01 + J1_18 = P0_03 +) + +// GPIO pins on header J2 +const ( + J2_01 = P0_20 + J2_03 = P0_18 + J2_04 = P0_19 + J2_07 = P0_16 + J2_08 = P0_15 + J2_09 = P0_14 + J2_10 = P0_13 + J2_11 = P0_12 + J2_12 = P0_11 + J2_13 = P0_10 + J2_14 = P0_09 + J2_15 = P0_08 + J2_16 = P0_07 + J2_17 = P0_06 + J2_18 = P0_05 +) + +// UART pins +const ( + UART_TX_PIN = P0_24 // J1_06 on the board + UART_RX_PIN = P0_25 // J1_05 on the board +) + +// ADC pins +const ( + ADC0 = P0_03 // J1_18 on the board + ADC1 = P0_02 // J1_15 on the board + ADC2 = P0_01 // J1_16 on the board + ADC3 = P0_04 // J1_17 on the board + ADC4 = P0_05 // J2_18 on the board + ADC5 = P0_06 // J2_17 on the board +) + +// I2C pins +const ( + SDA_PIN = P0_30 // J1_11 on the board + SCL_PIN = P0_00 // J1_13 on the board +) + +// SPI pins +const ( + SPI0_SCK_PIN = P0_23 // J1_03 on the board + SPI0_SDO_PIN = P0_21 // J1_01 on the board + SPI0_SDI_PIN = P0_22 // J1_04 on the board +) diff --git a/targets/hw-651-s110v8.json b/targets/hw-651-s110v8.json new file mode 100644 index 0000000000..50030cecad --- /dev/null +++ b/targets/hw-651-s110v8.json @@ -0,0 +1,3 @@ +{ + "inherits": ["hw-651", "nrf51-s110v8"] +} diff --git a/targets/hw-651.json b/targets/hw-651.json new file mode 100644 index 0000000000..a5ed5eebc4 --- /dev/null +++ b/targets/hw-651.json @@ -0,0 +1,7 @@ +{ + "inherits": ["nrf51"], + "build-tags": ["hw_651"], + "serial": "uart", + "flash-method": "openocd", + "openocd-interface": "cmsis-dap" +} From 2044f6fbcca48fc21733b67b666c607c26eb4e7f Mon Sep 17 00:00:00 2001 From: m00874241 Date: Fri, 24 Jan 2025 18:48:37 +0000 Subject: [PATCH 357/444] Added VersionTLS constants and VersionName(version uint16) method that turns it into a string, copied from big go --- src/crypto/tls/common.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index caf0198e15..21a1a20f50 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -17,6 +17,37 @@ import ( "time" ) +const ( + VersionTLS10 = 0x0301 + VersionTLS11 = 0x0302 + VersionTLS12 = 0x0303 + VersionTLS13 = 0x0304 + + // Deprecated: SSLv3 is cryptographically broken, and is no longer + // supported by this package. See golang.org/issue/32716. + VersionSSL30 = 0x0300 +) + +// VersionName returns the name for the provided TLS version number +// (e.g. "TLS 1.3"), or a fallback representation of the value if the +// version is not implemented by this package. +func VersionName(version uint16) string { + switch version { + case VersionSSL30: + return "SSLv3" + case VersionTLS10: + return "TLS 1.0" + case VersionTLS11: + return "TLS 1.1" + case VersionTLS12: + return "TLS 1.2" + case VersionTLS13: + return "TLS 1.3" + default: + return fmt.Sprintf("0x%04X", version) + } +} + // CurveID is the type of a TLS identifier for an elliptic curve. See // https://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-8. // From f24cd31895ab4d0e6b1ff86da66b8c6c7b272d2c Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 12 Feb 2025 12:49:55 +0100 Subject: [PATCH 358/444] build: update Linux builds to run on ubuntu-latest since 20.04 is being retired Signed-off-by: deadprogram --- .github/workflows/linux.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index de412bf658..139b7d03b7 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -169,7 +169,7 @@ jobs: assert-test-linux: # Run all tests that can run on Linux, with LLVM assertions enabled to catch # potential bugs. - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 @@ -297,7 +297,7 @@ jobs: - goarch: arm toolchain: arm-linux-gnueabihf libc: armhf - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest needs: build-linux steps: - name: Checkout From d04eea718572de9706e87dde2d980ca3c4a711c7 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 12 Feb 2025 14:08:05 +0100 Subject: [PATCH 359/444] fix: add NoSandbox flag to chrome headless that is run during WASM tests, since this is now required for Ubuntu 23+ and we are using Ubuntu 24+ when running Github Actions. See https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md Signed-off-by: deadprogram --- tests/wasm/setup_test.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/wasm/setup_test.go b/tests/wasm/setup_test.go index b56b6f3363..f282aa134e 100644 --- a/tests/wasm/setup_test.go +++ b/tests/wasm/setup_test.go @@ -33,8 +33,16 @@ func runargs(t *testing.T, args ...string) error { } func chromectx(t *testing.T) context.Context { + // see https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md + opts := append(chromedp.DefaultExecAllocatorOptions[:], + chromedp.NoSandbox, + ) + + allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...) + t.Cleanup(cancel) + // looks for locally installed Chrome - ctx, ccancel := chromedp.NewContext(context.Background(), chromedp.WithErrorf(t.Errorf), chromedp.WithDebugf(t.Logf), chromedp.WithLogf(t.Logf)) + ctx, ccancel := chromedp.NewContext(allocCtx, chromedp.WithErrorf(t.Errorf), chromedp.WithDebugf(t.Logf), chromedp.WithLogf(t.Logf)) t.Cleanup(ccancel) // Wait for browser to be ready. From 190c20825f81eb4f8cb75bf11dcb496a14b5e875 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Tue, 11 Feb 2025 13:06:49 +0100 Subject: [PATCH 360/444] targets: turn on GC for TKey1 device, since it does in fact work Signed-off-by: deadprogram --- targets/tkey.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/tkey.json b/targets/tkey.json index 4e953bcff5..3a52cae284 100644 --- a/targets/tkey.json +++ b/targets/tkey.json @@ -7,7 +7,7 @@ ], "linkerscript": "targets/tkey.ld", "scheduler": "none", - "gc": "leaking", + "gc": "conservative", "flash-command": "tkey-runapp {bin}", "serial": "uart" } From c5879c682c834e242aa86f86df2ba4984dfd9ba9 Mon Sep 17 00:00:00 2001 From: leongross Date: Fri, 14 Feb 2025 16:53:34 +0100 Subject: [PATCH 361/444] fix: implement testing {Skip,Fail}Now PR https://github.com/tinygo-org/tinygo/pull/4623 implements runtime.GoExit() with which we can improve the testing package and align it more to upstream testing behavour https://cs.opensource.google/go/go/+/refs/tags/go1.24.0:src/testing/testing.go;l=1150 Signed-off-by: leongross --- src/testing/testing.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/testing/testing.go b/src/testing/testing.go index 9058892d28..cd19ada710 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -17,6 +17,7 @@ import ( "io/fs" "math/rand" "os" + "runtime" "strconv" "strings" "time" @@ -207,9 +208,8 @@ func (c *common) Failed() bool { // current goroutine). func (c *common) FailNow() { c.Fail() - c.finished = true - c.Error("FailNow is incomplete, requires runtime.Goexit()") + runtime.Goexit() } // log generates the output. @@ -281,7 +281,7 @@ func (c *common) Skipf(format string, args ...interface{}) { func (c *common) SkipNow() { c.skip() c.finished = true - c.Error("SkipNow is incomplete, requires runtime.Goexit()") + runtime.Goexit() } func (c *common) skip() { From f02c56c9e0ce0a863df4a388057e6228aa7e3643 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sat, 4 Jan 2025 23:40:30 +0100 Subject: [PATCH 362/444] net: update to latest submodule with httptest subpackage and ResolveIPAddress implementation. Signed-off-by: deadprogram --- src/net | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/net b/src/net index a237059610..ca7cd08f85 160000 --- a/src/net +++ b/src/net @@ -1 +1 @@ -Subproject commit a2370596106a621a9b9dd6ad930f0ec24cfee8d0 +Subproject commit ca7cd08f851a1f3dde5fca2e217b7e06d17842ae From c1b267a208d996dcf3210b4b7fc06910ac0581f3 Mon Sep 17 00:00:00 2001 From: JP Hastings-Spital Date: Sat, 18 Jan 2025 18:53:09 +0000 Subject: [PATCH 363/444] Ensure build output directory is created `tinygo build -o /path/to/out .` expected `/path/to/out` to already exist, which is different behaviour to `go build`. This change ensures tinygo creates any directories needed to be able to build to the specified output dir. --- builder/build.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/builder/build.go b/builder/build.go index 87c342a323..3ae23b021d 100644 --- a/builder/build.go +++ b/builder/build.go @@ -604,6 +604,11 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe }, } + // Create the output directory, if needed + if err := os.MkdirAll(filepath.Dir(outpath), 0777); err != nil { + return result, err + } + // Check whether we only need to create an object file. // If so, we don't need to link anything and will be finished quickly. outext := filepath.Ext(outpath) From 2d17dec7bf5687b8d584a72c38863e7e022f8a3f Mon Sep 17 00:00:00 2001 From: leongross Date: Fri, 14 Feb 2025 17:15:41 +0100 Subject: [PATCH 364/444] os/file: add file.Chmod Signed-off-by: leongross --- src/os/file.go | 11 +++++++++++ src/os/file_other.go | 4 ++++ src/os/file_unix.go | 15 +++++++++++++++ src/os/file_windows.go | 4 ++++ 4 files changed, 34 insertions(+) diff --git a/src/os/file.go b/src/os/file.go index b47bf748a7..0e20d1bf10 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -307,6 +307,17 @@ func (f *File) Sync() (err error) { return } +// Chmod changes the mode of the file to mode. If there is an error, it will be +// of type *PathError. +func (f *File) Chmod(mode FileMode) (err error) { + if f.handle == nil { + err = ErrClosed + } else { + err = f.chmod(mode) + } + return +} + // LinkError records an error during a link or symlink or rename system call and // the paths that caused it. type LinkError struct { diff --git a/src/os/file_other.go b/src/os/file_other.go index 4e8f859b7a..75c0332817 100644 --- a/src/os/file_other.go +++ b/src/os/file_other.go @@ -149,3 +149,7 @@ func (f *File) Truncate(size int64) (err error) { return Truncate(f.name, size) } + +func (f *File) chmod(mode FileMode) error { + return ErrUnsupported +} diff --git a/src/os/file_unix.go b/src/os/file_unix.go index d94b80cc3d..fd4d464f5b 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -151,6 +151,21 @@ func (f *File) Truncate(size int64) (err error) { return Truncate(f.name, size) } +func (f *File) chmod(mode FileMode) error { + if f.handle == nil { + return ErrClosed + } + + longName := fixLongPath(f.name) + e := ignoringEINTR(func() error { + return syscall.Chmod(longName, syscallMode(mode)) + }) + if e != nil { + return &PathError{Op: "chmod", Path: f.name, Err: e} + } + return nil +} + // ReadAt reads up to len(b) bytes from the File starting at the given absolute offset. // It returns the number of bytes read and any error encountered, possibly io.EOF. // At end of file, Pread returns 0, io.EOF. diff --git a/src/os/file_windows.go b/src/os/file_windows.go index 9588cac763..022381deed 100644 --- a/src/os/file_windows.go +++ b/src/os/file_windows.go @@ -142,3 +142,7 @@ func isWindowsNulName(name string) bool { } return true } + +func (f *File) chmod(mode FileMode) error { + return ErrNotImplemented +} From 8fe039156b427bb212e3885d688e362e56e4fdb6 Mon Sep 17 00:00:00 2001 From: Laurent Demailly Date: Sat, 20 Jul 2024 15:29:52 -0700 Subject: [PATCH 365/444] fix: Avoid total failure on wasm finalizer call --- targets/wasm_exec.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/targets/wasm_exec.js b/targets/wasm_exec.js index defc73ba82..e30f3256f4 100644 --- a/targets/wasm_exec.js +++ b/targets/wasm_exec.js @@ -303,9 +303,22 @@ // func finalizeRef(v ref) "syscall/js.finalizeRef": (v_ref) => { - // Note: TinyGo does not support finalizers so this should never be - // called. - console.error('syscall/js.finalizeRef not implemented'); + // Note: TinyGo does not support finalizers so this is only called + // for one specific case, by js.go:jsString. and can/might leak memory. + const id = mem().getUint32(unboxValue(v_ref), true); + // Note that this if is so far seemingly never true. Someone should investigate why. + if (this._goRefCounts?.[id] !== undefined) { + this._goRefCounts[id]--; + if (this._goRefCounts[id] === 0) { + const v = this._values[id]; + this._values[id] = null; + this._ids.delete(v); + this._idPool.push(id); + } + } else { + // Log as a hint and reminder that something is probably off. + console.log("syscall/js.finalizeRef: unknown id", id); + } }, // func stringVal(value string) ref From 150d9d1c6bf04fd635916c35b9f5f4db24af8eb9 Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Thu, 20 Feb 2025 17:55:01 +0100 Subject: [PATCH 366/444] fix: correctly handle id lookup for finalizeRef call Modify the ID used for looking up the reference, based on suggestion made by @prochac Also use console.error in the caase that the reference is not found, since it is now actually known to be an error. --- targets/wasm_exec.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/targets/wasm_exec.js b/targets/wasm_exec.js index e30f3256f4..e61fa52ff7 100644 --- a/targets/wasm_exec.js +++ b/targets/wasm_exec.js @@ -305,8 +305,7 @@ "syscall/js.finalizeRef": (v_ref) => { // Note: TinyGo does not support finalizers so this is only called // for one specific case, by js.go:jsString. and can/might leak memory. - const id = mem().getUint32(unboxValue(v_ref), true); - // Note that this if is so far seemingly never true. Someone should investigate why. + const id = v_ref & 0xffffffffn; if (this._goRefCounts?.[id] !== undefined) { this._goRefCounts[id]--; if (this._goRefCounts[id] === 0) { @@ -316,8 +315,7 @@ this._idPool.push(id); } } else { - // Log as a hint and reminder that something is probably off. - console.log("syscall/js.finalizeRef: unknown id", id); + console.error("syscall/js.finalizeRef: unknown id", id); } }, From e7118e5bdab678dea1366364e3209ba7ecf19992 Mon Sep 17 00:00:00 2001 From: Scott Feldman Date: Thu, 20 Feb 2025 10:58:22 -0800 Subject: [PATCH 367/444] add comboat_fw tag for elecrow W5 boards with Combo-AT Wifi firmware --- targets/elecrow-rp2040.json | 2 +- targets/elecrow-rp2350.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/targets/elecrow-rp2040.json b/targets/elecrow-rp2040.json index 07391a6ee0..46feadc4de 100644 --- a/targets/elecrow-rp2040.json +++ b/targets/elecrow-rp2040.json @@ -1,6 +1,6 @@ { "inherits": ["rp2040"], - "build-tags": ["elecrow_rp2040"], + "build-tags": ["elecrow_rp2040", "comboat_fw"], "serial-port": ["2e8a:000a"], "default-stack-size": 8192, "ldflags": [ diff --git a/targets/elecrow-rp2350.json b/targets/elecrow-rp2350.json index aecc86723b..75876ed5ff 100644 --- a/targets/elecrow-rp2350.json +++ b/targets/elecrow-rp2350.json @@ -1,6 +1,6 @@ { "inherits": ["rp2350"], - "build-tags": ["elecrow_rp2350"], + "build-tags": ["elecrow_rp2350", "comboat_fw"], "serial-port": ["2e8a:000f"], "default-stack-size": 8192, "ldflags": [ From 0ec1cb1e19b33d0d5f3ae788f26af0c28f46a24e Mon Sep 17 00:00:00 2001 From: soypat Date: Sun, 9 Feb 2025 21:12:22 -0300 Subject: [PATCH 368/444] machine/rp2350: extending support to include the rp2350b --- src/machine/machine_rp2.go | 2 + src/machine/machine_rp2_2040.go | 10 ++++ src/machine/machine_rp2_2350.go | 20 +++++++- src/machine/machine_rp2_2350b.go | 48 +++++++++++++++++++ src/machine/machine_rp2_adc.go | 44 +++-------------- src/machine/machine_rp2_clocks.go | 6 --- src/machine/machine_rp2_gpio.go | 39 +++++++++++---- src/machine/machine_rp2_pins.go | 8 +--- ...chine_rp2040_pwm.go => machine_rp2_pwm.go} | 20 ++++---- targets/rp2350b.json | 5 ++ 10 files changed, 133 insertions(+), 69 deletions(-) create mode 100644 src/machine/machine_rp2_2350b.go rename src/machine/{machine_rp2040_pwm.go => machine_rp2_pwm.go} (95%) create mode 100644 targets/rp2350b.json diff --git a/src/machine/machine_rp2.go b/src/machine/machine_rp2.go index 6c09fedfea..9afefa5387 100644 --- a/src/machine/machine_rp2.go +++ b/src/machine/machine_rp2.go @@ -16,6 +16,8 @@ const ( // Note: On RP2350, most spinlocks are unusable due to Errata 2 _NUMSPINLOCKS = 32 _PICO_SPINLOCK_ID_IRQ = 9 + // is48Pin notes whether the chip is RP2040 with 32 pins or RP2350 with 48 pins. + is48Pin = _NUMBANK0_GPIOS == 48 ) // UART on the RP2040 diff --git a/src/machine/machine_rp2_2040.go b/src/machine/machine_rp2_2040.go index 484d8e923a..0691b8d77f 100644 --- a/src/machine/machine_rp2_2040.go +++ b/src/machine/machine_rp2_2040.go @@ -45,6 +45,16 @@ const ( PinPIO1 ) +// Analog pins on RP2040. +const ( + ADC0 Pin = GPIO26 + ADC1 Pin = GPIO27 + ADC2 Pin = GPIO28 + ADC3 Pin = GPIO29 + + thermADC = 30 +) + const ( clkGPOUT0 clockIndex = iota // GPIO Muxing 0 clkGPOUT1 // GPIO Muxing 1 diff --git a/src/machine/machine_rp2_2350.go b/src/machine/machine_rp2_2350.go index 4e12bebe35..32b739c629 100644 --- a/src/machine/machine_rp2_2350.go +++ b/src/machine/machine_rp2_2350.go @@ -33,6 +33,16 @@ const ( rp.PADS_BANK0_GPIO0_ISO_Msk ) +// Analog pins on RP2350. +const ( + ADC0 Pin = GPIO26 + ADC1 Pin = GPIO27 + ADC2 Pin = GPIO28 + ADC3 Pin = GPIO29 + + thermADC = 30 +) + const ( PinOutput PinMode = iota PinInput @@ -126,11 +136,17 @@ func (p Pin) Configure(config PinConfig) { return } p.init() - mask := uint32(1) << p + switch config.Mode { case PinOutput: p.setFunc(fnSIO) - rp.SIO.GPIO_OE_SET.Set(mask) + if is48Pin && p >= 32 { + mask := uint32(1) << (p % 32) + rp.SIO.GPIO_HI_OE_SET.Set(mask) + } else { + mask := uint32(1) << p + rp.SIO.GPIO_OE_SET.Set(mask) + } case PinInput: p.setFunc(fnSIO) p.pulloff() diff --git a/src/machine/machine_rp2_2350b.go b/src/machine/machine_rp2_2350b.go new file mode 100644 index 0000000000..c1e4a3a669 --- /dev/null +++ b/src/machine/machine_rp2_2350b.go @@ -0,0 +1,48 @@ +//go:build rp2350b + +package machine + +// RP2350B has additional pins. + +const ( + GPIO30 Pin = 30 // peripherals: PWM7 channel A + GPIO31 Pin = 31 // peripherals: PWM7 channel B + GPIO32 Pin = 32 // peripherals: PWM8 channel A + GPIO33 Pin = 33 // peripherals: PWM8 channel B + GPIO34 Pin = 34 // peripherals: PWM9 channel A + GPIO35 Pin = 35 // peripherals: PWM9 channel B + GPIO36 Pin = 36 // peripherals: PWM10 channel A + GPIO37 Pin = 37 // peripherals: PWM10 channel B + GPIO38 Pin = 38 // peripherals: PWM11 channel A + GPIO39 Pin = 39 // peripherals: PWM11 channel B + GPIO40 Pin = 40 // peripherals: PWM8 channel A + GPIO41 Pin = 41 // peripherals: PWM8 channel B + GPIO42 Pin = 42 // peripherals: PWM9 channel A + GPIO43 Pin = 43 // peripherals: PWM9 channel B + GPIO44 Pin = 44 // peripherals: PWM10 channel A + GPIO45 Pin = 45 // peripherals: PWM10 channel B + GPIO46 Pin = 46 // peripherals: PWM11 channel A + GPIO47 Pin = 47 // peripherals: PWM11 channel B +) + +// Analog pins on 2350b. +const ( + ADC0 Pin = GPIO40 + ADC1 Pin = GPIO41 + ADC2 Pin = GPIO42 + ADC3 Pin = GPIO43 + ADC4 Pin = GPIO44 + ADC5 Pin = GPIO45 + ADC6 Pin = GPIO46 + ADC7 Pin = GPIO47 + + thermADC = 48 +) + +// Additional PWMs on the RP2350B. +var ( + PWM8 = getPWMGroup(8) + PWM9 = getPWMGroup(9) + PWM10 = getPWMGroup(10) + PWM11 = getPWMGroup(11) +) diff --git a/src/machine/machine_rp2_adc.go b/src/machine/machine_rp2_adc.go index 13504d719f..fc9a8268e7 100644 --- a/src/machine/machine_rp2_adc.go +++ b/src/machine/machine_rp2_adc.go @@ -11,15 +11,6 @@ import ( // ADCChannel is the ADC peripheral mux channel. 0-4. type ADCChannel uint8 -// ADC channels. Only ADC_TEMP_SENSOR is public. The other channels are accessed via Machine.ADC objects -const ( - adc0_CH ADCChannel = iota - adc1_CH - adc2_CH - adc3_CH // Note: GPIO29 not broken out on pico board - adcTempSensor // Internal temperature sensor channel -) - // Used to serialise ADC sampling var adcLock sync.Mutex @@ -58,20 +49,10 @@ func (a ADC) Get() uint16 { // GetADCChannel returns the channel associated with the ADC pin. func (a ADC) GetADCChannel() (c ADCChannel, err error) { - err = nil - switch a.Pin { - case ADC0: - c = adc0_CH - case ADC1: - c = adc1_CH - case ADC2: - c = adc2_CH - case ADC3: - c = adc3_CH - default: - err = errors.New("no ADC channel for pin value") + if a.Pin < ADC0 { + return 0, errors.New("no ADC channel for pin value") } - return c, err + return ADCChannel(a.Pin - ADC0), nil } // Configure sets the channel's associated pin to analog input mode. @@ -113,12 +94,12 @@ func ReadTemperature() (millicelsius int32) { if rp.ADC.CS.Get()&rp.ADC_CS_EN == 0 { InitADC() } - + thermChan, _ := ADC{Pin: thermADC}.GetADCChannel() // Enable temperature sensor bias source rp.ADC.CS.SetBits(rp.ADC_CS_TS_EN) // T = 27 - (ADC_voltage - 0.706)/0.001721 - return (27000<<16 - (int32(adcTempSensor.getVoltage())-706<<16)*581) >> 16 + return (27000<<16 - (int32(thermChan.getVoltage())-706<<16)*581) >> 16 } // waitForReady spins waiting for the ADC peripheral to become ready. @@ -129,18 +110,5 @@ func waitForReady() { // The Pin method returns the GPIO Pin associated with the ADC mux channel, if it has one. func (c ADCChannel) Pin() (p Pin, err error) { - err = nil - switch c { - case adc0_CH: - p = ADC0 - case adc1_CH: - p = ADC1 - case adc2_CH: - p = ADC2 - case adc3_CH: - p = ADC3 - default: - err = errors.New("no associated pin for channel") - } - return p, err + return Pin(c) + ADC0, nil } diff --git a/src/machine/machine_rp2_clocks.go b/src/machine/machine_rp2_clocks.go index cc152a7f82..ba5bb67af2 100644 --- a/src/machine/machine_rp2_clocks.go +++ b/src/machine/machine_rp2_clocks.go @@ -13,12 +13,6 @@ func CPUFrequency() uint32 { return 125 * MHz } -// Returns the period of a clock cycle for the raspberry pi pico in nanoseconds. -// Used in PWM API. -func cpuPeriod() uint32 { - return 1e9 / CPUFrequency() -} - // clockIndex identifies a hardware clock type clockIndex uint8 diff --git a/src/machine/machine_rp2_gpio.go b/src/machine/machine_rp2_gpio.go index d49d12f2ba..6854f8aad4 100644 --- a/src/machine/machine_rp2_gpio.go +++ b/src/machine/machine_rp2_gpio.go @@ -63,8 +63,13 @@ func (p Pin) PortMaskSet() (*uint32, uint32) { // set drives the pin high func (p Pin) set() { - mask := uint32(1) << p - rp.SIO.GPIO_OUT_SET.Set(mask) + if is48Pin && p >= 32 { + mask := uint32(1) << (p % 32) + rp.SIO.GPIO_HI_OUT_SET.Set(mask) + } else { + mask := uint32(1) << p + rp.SIO.GPIO_OUT_SET.Set(mask) + } } func (p Pin) PortMaskClear() (*uint32, uint32) { @@ -73,18 +78,31 @@ func (p Pin) PortMaskClear() (*uint32, uint32) { // clr drives the pin low func (p Pin) clr() { - mask := uint32(1) << p - rp.SIO.GPIO_OUT_CLR.Set(mask) + if is48Pin && p >= 32 { + mask := uint32(1) << (p % 32) + rp.SIO.GPIO_HI_OUT_CLR.Set(mask) + } else { + mask := uint32(1) << p + rp.SIO.GPIO_OUT_CLR.Set(mask) + } } // xor toggles the pin func (p Pin) xor() { - mask := uint32(1) << p - rp.SIO.GPIO_OUT_XOR.Set(mask) + if is48Pin && p >= 32 { + mask := uint32(1) << (p % 32) + rp.SIO.GPIO_HI_OUT_XOR.Set(mask) + } else { + mask := uint32(1) << p + rp.SIO.GPIO_OUT_XOR.Set(mask) + } } // get returns the pin value func (p Pin) get() bool { + if is48Pin && p >= 32 { + return rp.SIO.GPIO_HI_IN.HasBits(1 << (p % 32)) + } return rp.SIO.GPIO_IN.HasBits(1 << p) } @@ -134,8 +152,13 @@ func (p Pin) setFunc(fn pinFunc) { // init initializes the gpio pin func (p Pin) init() { - mask := uint32(1) << p - rp.SIO.GPIO_OE_CLR.Set(mask) + if is48Pin && p >= 32 { + mask := uint32(1) << (p % 32) + rp.SIO.GPIO_HI_OE_CLR.Set(mask) + } else { + mask := uint32(1) << p + rp.SIO.GPIO_OE_CLR.Set(mask) + } p.clr() } diff --git a/src/machine/machine_rp2_pins.go b/src/machine/machine_rp2_pins.go index 93f2d50a01..43b31f938d 100644 --- a/src/machine/machine_rp2_pins.go +++ b/src/machine/machine_rp2_pins.go @@ -1,4 +1,4 @@ -//go:build rp2040 || rp2350 || ae_rp2040 || badger2040 || challenger_rp2040 || feather_rp2040 || gopher_badge || kb2040 || macropad_rp2040 || nano_rp2040 || pico || qtpy_rp2040 || thingplus_rp2040 || thumby || tufty2040 || waveshare_rp2040_zero || xiao_rp2040 +//go:build rp2040 || rp2350 || gopher_badge package machine @@ -34,10 +34,4 @@ const ( GPIO27 Pin = 27 // peripherals: PWM5 channel B GPIO28 Pin = 28 // peripherals: PWM6 channel A GPIO29 Pin = 29 // peripherals: PWM6 channel B - - // Analog pins - ADC0 Pin = GPIO26 - ADC1 Pin = GPIO27 - ADC2 Pin = GPIO28 - ADC3 Pin = GPIO29 ) diff --git a/src/machine/machine_rp2040_pwm.go b/src/machine/machine_rp2_pwm.go similarity index 95% rename from src/machine/machine_rp2040_pwm.go rename to src/machine/machine_rp2_pwm.go index f72d6dd031..1a511daf19 100644 --- a/src/machine/machine_rp2040_pwm.go +++ b/src/machine/machine_rp2_pwm.go @@ -1,4 +1,4 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine @@ -15,7 +15,7 @@ var ( ) const ( - maxPWMPins = 29 + maxPWMPins = _NUMBANK0_GPIOS - 1 ) // pwmGroup is one PWM peripheral, which consists of a counter and two output @@ -146,12 +146,13 @@ func (p *pwmGroup) Counter() uint32 { // Period returns the used PWM period in nanoseconds. func (p *pwmGroup) Period() uint64 { - periodPerCycle := cpuPeriod() + freq := CPUFrequency() top := p.getWrap() phc := p.getPhaseCorrect() Int, frac := p.getClockDiv() - // Line below can overflow if operations done without care. - return (16*uint64(Int) + uint64(frac)) * uint64((top+1)*(phc+1)*periodPerCycle) / 16 // cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16) + // Lines below can overflow if operations done without care. + term2 := 16 * uint64((top+1)*(phc+1)) * 1e9 / uint64(freq) // 1e9/freq == CPU period in nanoseconds. + return (uint64(Int) + uint64(frac)) * term2 / 16 // cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16) } // SetInverting sets whether to invert the output of this channel. @@ -279,9 +280,9 @@ func (pwm *pwmGroup) setPeriod(period uint64) error { // DIV_INT + DIV_FRAC/16 = cycles / ( (TOP+1) * (CSRPHCorrect+1) ) // DIV_FRAC/16 is always 0 in this equation // where cycles must be converted to time: // target_period = cycles * period_per_cycle ==> cycles = target_period/period_per_cycle - periodPerCycle := uint64(cpuPeriod()) + freq := uint64(CPUFrequency()) phc := uint64(pwm.getPhaseCorrect()) - rhs := 16 * period / ((1 + phc) * periodPerCycle * (1 + topStart)) // right-hand-side of equation, scaled so frac is not divided + rhs := 16e9 * period / ((1 + phc) * freq * (1 + topStart)) // right-hand-side of equation, scaled so frac is not divided whole := rhs / 16 frac := rhs % 16 switch { @@ -296,7 +297,7 @@ func (pwm *pwmGroup) setPeriod(period uint64) error { // Step 2 is acquiring a better top value. Clearing the equation: // TOP = cycles / ( (DIVINT+DIVFRAC/16) * (CSRPHCorrect+1) ) - 1 - top := 16*period/((16*whole+frac)*periodPerCycle*(1+phc)) - 1 + top := 16e9*period/((16*whole+frac)*freq*(1+phc)) - 1 if top > maxTop { top = maxTop } @@ -400,6 +401,9 @@ func (pwm *pwmGroup) getClockDiv() (Int, frac uint8) { // pwmGPIOToSlice Determine the PWM channel that is attached to the specified GPIO. // gpio must be less than 30. Returns the PWM slice number that controls the specified GPIO. func pwmGPIOToSlice(gpio Pin) (slicenum uint8) { + if is48Pin && gpio >= 32 { + return uint8(8 + ((gpio-32)/2)%4) + } return (uint8(gpio) >> 1) & 7 } diff --git a/targets/rp2350b.json b/targets/rp2350b.json new file mode 100644 index 0000000000..5db4d48492 --- /dev/null +++ b/targets/rp2350b.json @@ -0,0 +1,5 @@ +{ + "inherits": ["rp2350"], + "build-tags": ["rp2350b"], + "serial-port": ["2e8a:000f"] +} \ No newline at end of file From 9c7bbce02982e80e660e01d9c213c0bf35992a2d Mon Sep 17 00:00:00 2001 From: soypat Date: Thu, 13 Feb 2025 18:51:04 -0300 Subject: [PATCH 369/444] rp2350: add pll generalized solution; fix ADC handles; pwm period fix --- GNUmakefile | 2 + src/machine/board_pga2350.go | 98 ++++++++++++++++ src/machine/machine_rp2_2350.go | 10 -- src/machine/machine_rp2_2350a.go | 14 +++ src/machine/machine_rp2_2350b.go | 2 +- src/machine/machine_rp2_clocks.go | 6 +- src/machine/machine_rp2_pll.go | 187 +++++++++++++++++++++++++++++- src/machine/machine_rp2_pwm.go | 26 +++-- targets/pga2350.json | 4 + 9 files changed, 322 insertions(+), 27 deletions(-) create mode 100644 src/machine/board_pga2350.go create mode 100644 src/machine/machine_rp2_2350a.go create mode 100644 targets/pga2350.json diff --git a/GNUmakefile b/GNUmakefile index 2661fbd528..de298bbe50 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -523,6 +523,8 @@ smoketest: testchdir # regression test for #2563 cd tests/os/smoke && $(TINYGO) test -c -target=pybadge && rm smoke.test # test all examples (except pwm) + $(TINYGO) build -size short -o test.hex -target=pga2350 examples/echo + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/adc diff --git a/src/machine/board_pga2350.go b/src/machine/board_pga2350.go new file mode 100644 index 0000000000..710f14d85a --- /dev/null +++ b/src/machine/board_pga2350.go @@ -0,0 +1,98 @@ +//go:build pga2350 + +package machine + +// PGA2350 pin definitions. +const ( + GP0 = GPIO0 + GP1 = GPIO1 + GP2 = GPIO2 + GP3 = GPIO3 + GP4 = GPIO4 + GP5 = GPIO5 + GP6 = GPIO6 + GP7 = GPIO7 + GP8 = GPIO8 + GP9 = GPIO9 + GP10 = GPIO10 + GP11 = GPIO11 + GP12 = GPIO12 + GP13 = GPIO13 + GP14 = GPIO14 + GP15 = GPIO15 + GP16 = GPIO16 + GP17 = GPIO17 + GP18 = GPIO18 + GP19 = GPIO19 + GP20 = GPIO20 + GP21 = GPIO21 + GP22 = GPIO22 + GP26 = GPIO26 + GP27 = GPIO27 + GP28 = GPIO28 + GP29 = GPIO29 + GP30 = GPIO30 // peripherals: PWM7 channel A + GP31 = GPIO31 // peripherals: PWM7 channel B + GP32 = GPIO32 // peripherals: PWM8 channel A + GP33 = GPIO33 // peripherals: PWM8 channel B + GP34 = GPIO34 // peripherals: PWM9 channel A + GP35 = GPIO35 // peripherals: PWM9 channel B + GP36 = GPIO36 // peripherals: PWM10 channel A + GP37 = GPIO37 // peripherals: PWM10 channel B + GP38 = GPIO38 // peripherals: PWM11 channel A + GP39 = GPIO39 // peripherals: PWM11 channel B + GP40 = GPIO40 // peripherals: PWM8 channel A + GP41 = GPIO41 // peripherals: PWM8 channel B + GP42 = GPIO42 // peripherals: PWM9 channel A + GP43 = GPIO43 // peripherals: PWM9 channel B + GP44 = GPIO44 // peripherals: PWM10 channel A + GP45 = GPIO45 // peripherals: PWM10 channel B + GP46 = GPIO46 // peripherals: PWM11 channel A + GP47 = GPIO47 // peripherals: PWM11 channel B + +) + +var DefaultUART = UART0 + +// Peripheral defaults. +const ( + xoscFreq = 12 // MHz + + I2C0_SDA_PIN = GP4 + I2C0_SCL_PIN = GP5 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 + + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO18 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO19 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO16 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO10 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO11 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO12 // Rx + + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO8 + UART1_RX_PIN = GPIO9 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +// USB identifiers +const ( + usb_STRING_PRODUCT = "PGA2350" + usb_STRING_MANUFACTURER = "Pimoroni" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000A +) diff --git a/src/machine/machine_rp2_2350.go b/src/machine/machine_rp2_2350.go index 32b739c629..5ef5098c6e 100644 --- a/src/machine/machine_rp2_2350.go +++ b/src/machine/machine_rp2_2350.go @@ -33,16 +33,6 @@ const ( rp.PADS_BANK0_GPIO0_ISO_Msk ) -// Analog pins on RP2350. -const ( - ADC0 Pin = GPIO26 - ADC1 Pin = GPIO27 - ADC2 Pin = GPIO28 - ADC3 Pin = GPIO29 - - thermADC = 30 -) - const ( PinOutput PinMode = iota PinInput diff --git a/src/machine/machine_rp2_2350a.go b/src/machine/machine_rp2_2350a.go new file mode 100644 index 0000000000..09ec8a1190 --- /dev/null +++ b/src/machine/machine_rp2_2350a.go @@ -0,0 +1,14 @@ +//go:build rp2350 && !rp2350b + +package machine + +// Analog pins on RP2350a. +const ( + ADC0 Pin = GPIO26 + ADC1 Pin = GPIO27 + ADC2 Pin = GPIO28 + ADC3 Pin = GPIO29 + + // fifth ADC channel. + thermADC = 30 +) diff --git a/src/machine/machine_rp2_2350b.go b/src/machine/machine_rp2_2350b.go index c1e4a3a669..bd5ceebe4c 100644 --- a/src/machine/machine_rp2_2350b.go +++ b/src/machine/machine_rp2_2350b.go @@ -35,7 +35,7 @@ const ( ADC5 Pin = GPIO45 ADC6 Pin = GPIO46 ADC7 Pin = GPIO47 - + // Ninth ADC channel. thermADC = 48 ) diff --git a/src/machine/machine_rp2_clocks.go b/src/machine/machine_rp2_clocks.go index ba5bb67af2..9dd2774b9c 100644 --- a/src/machine/machine_rp2_clocks.go +++ b/src/machine/machine_rp2_clocks.go @@ -137,6 +137,8 @@ func (clk *clock) configure(src, auxsrc, srcFreq, freq uint32) { } +const pllsysFB, pllsysPD1, pllsysPD2 uint32 = 125, 6, 2 // RP2040 running 125MHz with 1500MHz VCO. + // init initializes the clock hardware. // // Must be called before any other clock function. @@ -163,8 +165,8 @@ func (clks *clocksType) init() { // REF FBDIV VCO POSTDIV // pllSys: 12 / 1 = 12MHz * 125 = 1500MHZ / 6 / 2 = 125MHz // pllUSB: 12 / 1 = 12MHz * 40 = 480 MHz / 5 / 2 = 48MHz - pllSys.init(1, 1500*MHz, 6, 2) - pllUSB.init(1, 480*MHz, 5, 2) + pllSys.init(1, uint32(pllsysFB), uint32(pllsysPD1), uint32(pllsysPD2)) + pllUSB.init(1, 40, 5, 2) // Configure clocks // clkRef = xosc (12MHz) / 1 = 12MHz diff --git a/src/machine/machine_rp2_pll.go b/src/machine/machine_rp2_pll.go index f409768ab3..dcc654ab06 100644 --- a/src/machine/machine_rp2_pll.go +++ b/src/machine/machine_rp2_pll.go @@ -4,6 +4,9 @@ package machine import ( "device/rp" + "errors" + "math" + "math/bits" "runtime/volatile" "unsafe" ) @@ -29,12 +32,11 @@ var ( // Post Divider 1, postDiv1 with range 1-7 and be >= postDiv2. // // Post Divider 2, postDiv2 with range 1-7. -func (pll *pll) init(refdiv, vcoFreq, postDiv1, postDiv2 uint32) { +func (pll *pll) init(refdiv, fbdiv, postDiv1, postDiv2 uint32) { refFreq := xoscFreq / refdiv // What are we multiplying the reference clock by to get the vco freq // (The regs are called div, because you divide the vco output and compare it to the refclk) - fbdiv := vcoFreq / (refFreq * MHz) // Check fbdiv range if !(fbdiv >= 16 && fbdiv <= 320) { @@ -54,13 +56,14 @@ func (pll *pll) init(refdiv, vcoFreq, postDiv1, postDiv2 uint32) { panic("postdiv1 should be greater than or equal to postdiv2") } - // Check that reference frequency is no greater than vco / 16 + // Check that reference frequency is no greater than vcoFreq / 16 + vcoFreq := calcVCO(xoscFreq, fbdiv, refdiv) if refFreq > vcoFreq/16 { panic("reference frequency should not be greater than vco frequency divided by 16") } // div1 feeds into div2 so if div1 is 5 and div2 is 2 then you get a divide by 10 - pdiv := postDiv1< maxVCO { + break + } + calcPD12 := vco / targetFreq + if calcPD12 < 1 { + calcPD12 = 1 + } else if calcPD12 > 49 { + calcPD12 = 49 + } + iters++ + pd1 = pdTable[calcPD12].hivco[0] + pd2 = pdTable[calcPD12].hivco[1] + fout, err := pllFreqOutPostdiv(xoscRef, fbdiv, MHz, refdiv, pd1, pd2) + found := false + margin := abs(int64(fout) - int64(targetFreq)) + if err == nil && margin <= bestMargin { + found = true + bestFreq = fout + bestFbdiv = fbdiv + bestpd1 = pd1 + bestpd2 = pd2 + bestRefdiv = refdiv + bestMargin = margin + } + pd1 = pdTable[calcPD12].lovco[0] + pd2 = pdTable[calcPD12].lovco[1] + fout, err = pllFreqOutPostdiv(xoscRef, fbdiv, MHz, refdiv, pd1, pd2) + margin = abs(int64(fout) - int64(targetFreq)) + if err == nil && margin <= bestMargin { + found = true + bestFreq = fout + bestFbdiv = fbdiv + bestpd1 = pd1 + bestpd2 = pd2 + bestRefdiv = refdiv + bestMargin = margin + } + if found && ps.LowerVCO { + break + } + } + } + if bestFreq == 0 { + return fbdiv, refdiv, pd1, pd2, errors.New("no best frequency found") + } + return bestFbdiv, bestRefdiv, bestpd1, bestpd2, nil +} + +func abs(a int64) int64 { + if a == math.MinInt64 { + return math.MaxInt64 + } else if a < 0 { + return -a + } + return a +} + +func pllFreqOutPostdiv(xosc, fbdiv, MHz uint64, refdiv, postdiv1, postdiv2 uint8) (foutpostdiv uint64, err error) { + // testing grounds. + const ( + mhz = 1 + cfref = 12 * mhz // given by crystal oscillator selection. + crefd = 1 + cfbdiv = 100 + cvco = cfref * cfbdiv / crefd + cpd1 = 6 + cpd2 = 2 + foutpd = (cfref / crefd) * cfbdiv / (cpd1 * cpd2) + ) + refFreq := xosc / uint64(refdiv) + overflow, vco := bits.Mul64(xosc, fbdiv) + vco /= uint64(refdiv) + foutpostdiv = vco / uint64(postdiv1*postdiv2) + switch { + case refdiv < 1 || refdiv > 63: + err = errors.New("reference divider out of range") + case fbdiv < 16 || fbdiv > 320: + err = errors.New("feedback divider out of range") + case postdiv1 < 1 || postdiv1 > 7: + err = errors.New("postdiv1 out of range") + case postdiv2 < 1 || postdiv2 > 7: + err = errors.New("postdiv2 out of range") + case postdiv1 < postdiv2: + err = errors.New("user error: use higher value for postdiv1 for lower power consumption") + case vco < 750*MHz || vco > 1600*MHz: + err = errors.New("VCO out of range") + case refFreq < 5*MHz: + err = errors.New("minimum reference frequency breach") + case refFreq > vco/16: + err = errors.New("maximum reference frequency breach") + case vco > 1200*MHz && vco < 1600*MHz && xosc < 75*MHz && refdiv != 1: + err = errors.New("refdiv should be 1 for given VCO and reference frequency") + case overflow != 0: + err = errVCOOverflow + } + if err != nil { + return 0, err + } + return foutpostdiv, nil +} + +func calcVCO(xoscFreq, fbdiv, refdiv uint32) uint32 { + const maxXoscMHz = math.MaxUint32 / 320 / MHz // 13MHz maximum xosc apparently. + if fbdiv > 320 || xoscFreq > math.MaxUint32/320 { + panic("invalid VCO calculation args") + } + return xoscFreq * fbdiv / refdiv +} + +var pdTable = [50]struct { + hivco [2]uint8 + lovco [2]uint8 +}{} + +func genTable() { + if pdTable[1].hivco[1] != 0 { + return // Already generated. + } + for product := 1; product < len(pdTable); product++ { + bestProdhi := 255 + bestProdlo := 255 + for pd1 := 7; pd1 > 0; pd1-- { + for pd2 := pd1; pd2 > 0; pd2-- { + gotprod := pd1 * pd2 + if abs(int64(gotprod-product)) < abs(int64(bestProdlo-product)) { + bestProdlo = gotprod + pdTable[product].lovco[0] = uint8(pd1) + pdTable[product].lovco[1] = uint8(pd2) + } + } + } + for pd1 := 1; pd1 < 8; pd1++ { + for pd2 := 1; pd2 <= pd1; pd2++ { + gotprod := pd1 * pd2 + if abs(int64(gotprod-product)) < abs(int64(bestProdhi-product)) { + bestProdhi = gotprod + pdTable[product].hivco[0] = uint8(pd1) + pdTable[product].hivco[1] = uint8(pd2) + } + } + } + } +} diff --git a/src/machine/machine_rp2_pwm.go b/src/machine/machine_rp2_pwm.go index 1a511daf19..772811e368 100644 --- a/src/machine/machine_rp2_pwm.go +++ b/src/machine/machine_rp2_pwm.go @@ -146,13 +146,14 @@ func (p *pwmGroup) Counter() uint32 { // Period returns the used PWM period in nanoseconds. func (p *pwmGroup) Period() uint64 { - freq := CPUFrequency() + // Lines below can overflow if operations done without care. + // maxInt=255, maxFrac=15, maxTop=65536, maxPHC=1 => maxProduct= (16*255+15) * (65536*2*1e9) = 5.3673e17 < MaxUint64=1.8e19 (close call.) + const compileTimeCheckPeriod uint64 = (255*16 + 15) * (65535 + 1) * 2 * 1e9 + freq := uint64(CPUFrequency()) top := p.getWrap() phc := p.getPhaseCorrect() Int, frac := p.getClockDiv() - // Lines below can overflow if operations done without care. - term2 := 16 * uint64((top+1)*(phc+1)) * 1e9 / uint64(freq) // 1e9/freq == CPU period in nanoseconds. - return (uint64(Int) + uint64(frac)) * term2 / 16 // cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16) + return (16*uint64(Int) + uint64(frac)) * uint64((top+1)*(phc+1)*1e9) / (16 * freq) // cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16) } // SetInverting sets whether to invert the output of this channel. @@ -267,6 +268,9 @@ func (pwm *pwmGroup) setPeriod(period uint64) error { // Maximum Period is 268369920ns on rp2040, given by (16*255+15)*8*(1+0xffff)*(1+1)/16 // With no phase shift max period is half of this value. maxPeriod = 268 * milliseconds + // This will be a compile time error if this method is at risk of overflowing. cpufreq=155MHz for typical RP2350. + maxCPUFreq = 4 * GHz // Can go up to 4GHz without overflowing :) + compileTimeCheckSetPeriod uint64 = 16 * maxPeriod * maxCPUFreq ) if period > maxPeriod || period < 8 { @@ -280,11 +284,13 @@ func (pwm *pwmGroup) setPeriod(period uint64) error { // DIV_INT + DIV_FRAC/16 = cycles / ( (TOP+1) * (CSRPHCorrect+1) ) // DIV_FRAC/16 is always 0 in this equation // where cycles must be converted to time: // target_period = cycles * period_per_cycle ==> cycles = target_period/period_per_cycle - freq := uint64(CPUFrequency()) - phc := uint64(pwm.getPhaseCorrect()) - rhs := 16e9 * period / ((1 + phc) * freq * (1 + topStart)) // right-hand-side of equation, scaled so frac is not divided - whole := rhs / 16 - frac := rhs % 16 + var ( + freq = uint64(CPUFrequency()) + phc = uint64(pwm.getPhaseCorrect()) + rhs = 16 * period * freq / ((1 + phc) * 1e9 * (1 + topStart)) // right-hand-side of equation, scaled so frac is not divided + whole = rhs / 16 + frac = rhs % 16 + ) switch { case whole > 0xff: whole = 0xff @@ -297,7 +303,7 @@ func (pwm *pwmGroup) setPeriod(period uint64) error { // Step 2 is acquiring a better top value. Clearing the equation: // TOP = cycles / ( (DIVINT+DIVFRAC/16) * (CSRPHCorrect+1) ) - 1 - top := 16e9*period/((16*whole+frac)*freq*(1+phc)) - 1 + top := 16*period*freq/((1+phc)*1e9*(16*whole+frac)) - 1 if top > maxTop { top = maxTop } diff --git a/targets/pga2350.json b/targets/pga2350.json new file mode 100644 index 0000000000..3ff72c26c2 --- /dev/null +++ b/targets/pga2350.json @@ -0,0 +1,4 @@ +{ + "inherits": ["rp2350b"], + "build-tags": ["pga2350"] +} \ No newline at end of file From 6e97079367b875ad5d93e31f3db828a8dac00d40 Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Thu, 13 Feb 2025 22:54:51 +0100 Subject: [PATCH 370/444] target: add Pimoroni Pico Plus2 (#4735) * machine: separate definitions for ADC pins on rp2350/rp2350b Signed-off-by: deadprogram * targets: add support for Pimoroni Pico Plus2 Signed-off-by: deadprogram --------- Signed-off-by: deadprogram Co-authored-by: Patricio Whittingslow --- GNUmakefile | 2 + src/machine/board_pico_plus2.go | 93 +++++++++++++++++++++++++++++++++ targets/pico-plus2.json | 11 ++++ 3 files changed, 106 insertions(+) create mode 100644 src/machine/board_pico_plus2.go create mode 100644 targets/pico-plus2.json diff --git a/GNUmakefile b/GNUmakefile index de298bbe50..423a2a20e0 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -747,6 +747,8 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=tiny2350 examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=pico-plus2 examples/blinky1 + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=waveshare-rp2040-tiny examples/echo @$(MD5SUM) test.hex # test pwm diff --git a/src/machine/board_pico_plus2.go b/src/machine/board_pico_plus2.go new file mode 100644 index 0000000000..c21c9ea25d --- /dev/null +++ b/src/machine/board_pico_plus2.go @@ -0,0 +1,93 @@ +//go:build pico_plus2 + +package machine + +// GPIO pins +const ( + GP0 Pin = GPIO0 + GP1 Pin = GPIO1 + GP2 Pin = GPIO2 + GP3 Pin = GPIO3 + GP4 Pin = GPIO4 + GP5 Pin = GPIO5 + GP6 Pin = GPIO6 + GP7 Pin = GPIO7 + GP8 Pin = GPIO8 + GP9 Pin = GPIO9 + GP10 Pin = GPIO10 + GP11 Pin = GPIO11 + GP12 Pin = GPIO12 + GP13 Pin = GPIO13 + GP14 Pin = GPIO14 + GP15 Pin = GPIO15 + GP16 Pin = GPIO16 + GP17 Pin = GPIO17 + GP18 Pin = GPIO18 + GP19 Pin = GPIO19 + GP20 Pin = GPIO20 + GP21 Pin = GPIO21 + GP22 Pin = GPIO22 + GP26 Pin = GPIO26 + GP27 Pin = GPIO27 + GP28 Pin = GPIO28 + GP32 Pin = GPIO32 + GP33 Pin = GPIO33 + GP34 Pin = GPIO34 + GP35 Pin = GPIO35 + GP36 Pin = GPIO36 + + // Onboard LED + LED Pin = GPIO25 + + // Onboard crystal oscillator frequency, in MHz. + xoscFreq = 12 // MHz +) + +// I2C Default pins on Raspberry Pico. +const ( + I2C0_SDA_PIN = GP4 + I2C0_SCL_PIN = GP5 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO18 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO19 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO16 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO10 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO11 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO12 // Rx +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO8 + UART1_RX_PIN = GPIO9 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Pico Plus2" + usb_STRING_MANUFACTURER = "Pimoroni" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000F +) diff --git a/targets/pico-plus2.json b/targets/pico-plus2.json new file mode 100644 index 0000000000..307c1b72da --- /dev/null +++ b/targets/pico-plus2.json @@ -0,0 +1,11 @@ +{ + "inherits": [ + "rp2350b" + ], + "build-tags": ["pico_plus2"], + "ldflags": [ + "--defsym=__flash_size=16M" + ], + "serial-port": ["2e8a:000F"], + "default-stack-size": 8192 +} From 811b13a9d3c4d1e5e769a6c83e2766b0c8f85ee2 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 18 Feb 2025 13:59:28 +0100 Subject: [PATCH 371/444] interp: correctly mark functions as modifying memory Previously, if a function couldn't be interpreted in the interp pass, it would just be run at runtime and the parameters would be marked as potentially being modified. However, this is incorrect: the function itself can also modify memory. So the function itself also needs to be marked (recursively). This fixes a number of regressions while upgrading to Go 1.24. This results in a significant size regression that is mostly worked around in the next commit. --- interp/interpreter.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interp/interpreter.go b/interp/interpreter.go index 20d56c69e0..3813035e1d 100644 --- a/interp/interpreter.go +++ b/interp/interpreter.go @@ -970,9 +970,9 @@ func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, me case llvm.Call: llvmFn := operands[len(operands)-1] args := operands[:len(operands)-1] - for _, arg := range args { - if arg.Type().TypeKind() == llvm.PointerTypeKind { - err := mem.markExternalStore(arg) + for _, op := range operands { + if op.Type().TypeKind() == llvm.PointerTypeKind { + err := mem.markExternalStore(op) if err != nil { return r.errorAt(inst, err) } From f4fd79f7be3694c588cf81d7350f30e5014c7e54 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 19 Feb 2025 15:34:18 +0100 Subject: [PATCH 372/444] runtime: manually initialize xorshift state This ensures: 1. The xorshift state is initialized during interp. 2. The xorshift state gets initialized to a real random number on hardware that supports it at runtime. This fixes a big binary size regression from the previous commit. It's still not perfect: most programs increase binary size by a few bytes. But it's not nearly as bad as before. --- builder/sizes_test.go | 4 ++-- src/runtime/algorithm.go | 6 +++--- src/runtime/runtime_wasmentry.go | 1 + src/runtime/scheduler_cooperative.go | 1 + src/runtime/scheduler_none.go | 1 + 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/builder/sizes_test.go b/builder/sizes_test.go index 8c87e22917..2b2b08fe6f 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -43,8 +43,8 @@ func TestBinarySize(t *testing.T) { tests := []sizeTest{ // microcontrollers {"hifive1b", "examples/echo", 4560, 280, 0, 2268}, - {"microbit", "examples/serial", 2908, 388, 8, 2272}, - {"wioterminal", "examples/pininterrupt", 7293, 1487, 116, 6912}, + {"microbit", "examples/serial", 2916, 388, 8, 2272}, + {"wioterminal", "examples/pininterrupt", 7315, 1489, 116, 6912}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the diff --git a/src/runtime/algorithm.go b/src/runtime/algorithm.go index 24571498b2..d8cd1ca43a 100644 --- a/src/runtime/algorithm.go +++ b/src/runtime/algorithm.go @@ -23,13 +23,13 @@ func fastrand() uint32 { return xorshift32State } -func init() { +func initRand() { r, _ := hardwareRand() xorshift64State = uint64(r | 1) // protect against 0 xorshift32State = uint32(xorshift64State) } -var xorshift32State uint32 +var xorshift32State uint32 = 1 func xorshift32(x uint32) uint32 { // Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs". @@ -49,7 +49,7 @@ func fastrand64() uint64 { return xorshift64State } -var xorshift64State uint64 +var xorshift64State uint64 = 1 // 64-bit xorshift multiply rng from http://vigna.di.unimi.it/ftp/papers/xorshift.pdf func xorshiftMult64(x uint64) uint64 { diff --git a/src/runtime/runtime_wasmentry.go b/src/runtime/runtime_wasmentry.go index 1d2cec6cae..807590eb71 100644 --- a/src/runtime/runtime_wasmentry.go +++ b/src/runtime/runtime_wasmentry.go @@ -35,6 +35,7 @@ func wasmEntryReactor() { heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) initHeap() + initRand() if hasScheduler { // A package initializer might do funky stuff like start a goroutine and diff --git a/src/runtime/scheduler_cooperative.go b/src/runtime/scheduler_cooperative.go index 91ba86409f..9f80e060ce 100644 --- a/src/runtime/scheduler_cooperative.go +++ b/src/runtime/scheduler_cooperative.go @@ -247,6 +247,7 @@ func sleep(duration int64) { // With a scheduler, init and the main function are invoked in a goroutine before starting the scheduler. func run() { initHeap() + initRand() go func() { initAll() callMain() diff --git a/src/runtime/scheduler_none.go b/src/runtime/scheduler_none.go index a5acfd4309..25bd7eb9c1 100644 --- a/src/runtime/scheduler_none.go +++ b/src/runtime/scheduler_none.go @@ -13,6 +13,7 @@ const hasParallelism = false // With the "none" scheduler, init and the main function are invoked directly. func run() { initHeap() + initRand() initAll() callMain() mainExited = true From 17bb1fec44c1cca5fd8bc09a0e78f9aafb262ea2 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 12 Feb 2025 17:55:12 +0100 Subject: [PATCH 373/444] feature: add buildmode=wasi-legacy to support existing base of users who expected the older behavior for wasi modules to not return an exit code as if they were reactors. See #4726 for some details on what this is intended to address. Signed-off-by: deadprogram --- builder/build.go | 10 ++++++++++ compileopts/options.go | 2 +- compiler/symbol.go | 5 +++++ main.go | 2 +- main_test.go | 9 +++++++++ src/runtime/runtime_wasmentry.go | 9 +++++++++ 6 files changed, 35 insertions(+), 2 deletions(-) diff --git a/builder/build.go b/builder/build.go index 3ae23b021d..2d7156b9b7 100644 --- a/builder/build.go +++ b/builder/build.go @@ -672,6 +672,16 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe ldflags = append(ldflags, "--no-entry") } + if config.Options.BuildMode == "wasi-legacy" { + if !strings.HasPrefix(config.Triple(), "wasm32-") { + return result, fmt.Errorf("buildmode wasi-legacy is only supported on wasm") + } + + if config.Options.Scheduler != "none" { + return result, fmt.Errorf("buildmode wasi-legacy only supports scheduler=none") + } + } + // Add compiler-rt dependency if needed. Usually this is a simple load from // a cache. if config.Target.RTLib == "compiler-rt" { diff --git a/compileopts/options.go b/compileopts/options.go index 30e0e4dbed..e698cb3cb7 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -8,7 +8,7 @@ import ( ) var ( - validBuildModeOptions = []string{"default", "c-shared"} + validBuildModeOptions = []string{"default", "c-shared", "wasi-legacy"} validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise"} validSchedulerOptions = []string{"none", "tasks", "asyncify"} validSerialOptions = []string{"none", "uart", "usb", "rtt"} diff --git a/compiler/symbol.go b/compiler/symbol.go index ff7ef05508..5ecf68e54a 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -283,6 +283,11 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo { info.wasmName = "_start" info.exported = true } + if info.linkName == "runtime.wasmEntryLegacy" && c.BuildMode == "wasi-legacy" { + info.linkName = "_start" + info.wasmName = "_start" + info.exported = true + } // Check for //go: pragmas, which may change the link name (among others). c.parsePragmas(&info, f) diff --git a/main.go b/main.go index dee0165582..1f2fc95b14 100644 --- a/main.go +++ b/main.go @@ -1501,7 +1501,7 @@ func main() { var tags buildutil.TagsFlag flag.Var(&tags, "tags", "a space-separated list of extra build tags") target := flag.String("target", "", "chip/board name or JSON target specification file") - buildMode := flag.String("buildmode", "", "build mode to use (default, c-shared)") + buildMode := flag.String("buildmode", "", "build mode to use (default, c-shared, wasi-legacy)") var stackSize uint64 flag.Func("stack-size", "goroutine stack size (if unknown at compile time)", func(s string) error { size, err := bytesize.Parse(s) diff --git a/main_test.go b/main_test.go index 22ac549de7..f193f46799 100644 --- a/main_test.go +++ b/main_test.go @@ -594,6 +594,15 @@ func TestWasmExport(t *testing.T) { noOutput: true, // wasm-unknown cannot produce output command: true, }, + // Test buildmode=wasi-legacy with WASI. + { + name: "WASIp1-legacy", + target: "wasip1", + buildMode: "wasi-legacy", + scheduler: "none", + file: "wasmexport-noscheduler.go", + command: true, + }, } for _, tc := range tests { diff --git a/src/runtime/runtime_wasmentry.go b/src/runtime/runtime_wasmentry.go index 807590eb71..b33a19d40b 100644 --- a/src/runtime/runtime_wasmentry.go +++ b/src/runtime/runtime_wasmentry.go @@ -52,6 +52,15 @@ func wasmEntryReactor() { } } +// This is the _start entry point, when using -buildmode=wasi-legacy. +func wasmEntryLegacy() { + // These need to be initialized early so that the heap can be initialized. + initializeCalled = true + heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) + heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) + run() +} + // Whether the runtime was initialized by a call to _initialize or _start. var initializeCalled bool From 4372dbdd41bbccf5cf4df5c7c1c41799e179459d Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 24 Feb 2025 16:47:14 +0100 Subject: [PATCH 374/444] ci: use older image for cross-compiling builds This ensures that the resulting binaries are compatible with a wide range of Linux systems, not just the most recent ones. --- .github/workflows/linux.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 139b7d03b7..c94b3582e3 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -297,7 +297,7 @@ jobs: - goarch: arm toolchain: arm-linux-gnueabihf libc: armhf - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 # note: use the oldest image available! (see above) needs: build-linux steps: - name: Checkout @@ -346,7 +346,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-19-linux-${{ matrix.goarch }}-v2 + key: llvm-build-19-linux-${{ matrix.goarch }}-v3 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' From a6cd072fa1e044f44506d582c7f5798a534abf77 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 25 Feb 2025 11:27:19 +0100 Subject: [PATCH 375/444] Revert "fix: implement testing {Skip,Fail}Now" This reverts commit c5879c682c834e242aa86f86df2ba4984dfd9ba9. It doesn't look like this is working (see https://github.com/tinygo-org/tinygo/pull/4736#issuecomment-2679670226), and it doesn't have any tests anyway to prove that it does work. So I think it's best to revert it for now and add a working implementation with tests in the future. --- src/testing/testing.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/testing/testing.go b/src/testing/testing.go index cd19ada710..9058892d28 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -17,7 +17,6 @@ import ( "io/fs" "math/rand" "os" - "runtime" "strconv" "strings" "time" @@ -208,8 +207,9 @@ func (c *common) Failed() bool { // current goroutine). func (c *common) FailNow() { c.Fail() + c.finished = true - runtime.Goexit() + c.Error("FailNow is incomplete, requires runtime.Goexit()") } // log generates the output. @@ -281,7 +281,7 @@ func (c *common) Skipf(format string, args ...interface{}) { func (c *common) SkipNow() { c.skip() c.finished = true - runtime.Goexit() + c.Error("SkipNow is incomplete, requires runtime.Goexit()") } func (c *common) skip() { From ea53ace270669995b0ea478f51d84c507d156536 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 16 Feb 2025 14:29:32 +0100 Subject: [PATCH 376/444] cgo: mangle identifier names This mangles CGo identifier names to something like "_Cgo_foo" instead of using literal identifiers like "C.foo". This works around https://github.com/golang/go/issues/71777. I don't like this solution, but I hope we'll find a better solution in the future. In that case we can revert this commit. --- cgo/cgo.go | 109 ++++++++----------- cgo/libclang.go | 54 +++++----- cgo/testdata/basic.out.go | 62 +++++------ cgo/testdata/const.out.go | 76 ++++++------- cgo/testdata/errors.out.go | 92 ++++++++-------- cgo/testdata/flags.out.go | 66 ++++++------ cgo/testdata/symbols.out.go | 84 +++++++-------- cgo/testdata/types.out.go | 210 ++++++++++++++++++------------------ compiler/symbol.go | 5 +- 9 files changed, 371 insertions(+), 387 deletions(-) diff --git a/cgo/cgo.go b/cgo/cgo.go index cb822a38ec..6b8ea4373d 100644 --- a/cgo/cgo.go +++ b/cgo/cgo.go @@ -92,18 +92,18 @@ type noescapingFunc struct { // cgoAliases list type aliases between Go and C, for types that are equivalent // in both languages. See addTypeAliases. var cgoAliases = map[string]string{ - "C.int8_t": "int8", - "C.int16_t": "int16", - "C.int32_t": "int32", - "C.int64_t": "int64", - "C.uint8_t": "uint8", - "C.uint16_t": "uint16", - "C.uint32_t": "uint32", - "C.uint64_t": "uint64", - "C.uintptr_t": "uintptr", - "C.float": "float32", - "C.double": "float64", - "C._Bool": "bool", + "_Cgo_int8_t": "int8", + "_Cgo_int16_t": "int16", + "_Cgo_int32_t": "int32", + "_Cgo_int64_t": "int64", + "_Cgo_uint8_t": "uint8", + "_Cgo_uint16_t": "uint16", + "_Cgo_uint32_t": "uint32", + "_Cgo_uint64_t": "uint64", + "_Cgo_uintptr_t": "uintptr", + "_Cgo_float": "float32", + "_Cgo_double": "float64", + "_Cgo__Bool": "bool", } // builtinAliases are handled specially because they only exist on the Go side @@ -145,48 +145,46 @@ typedef unsigned long long _Cgo_ulonglong; // The string/bytes functions below implement C.CString etc. To make sure the // runtime doesn't need to know the C int type, lengths are converted to uintptr // first. -// These functions will be modified to get a "C." prefix, so the source below -// doesn't reflect the final AST. const generatedGoFilePrefixBase = ` import "syscall" import "unsafe" var _ unsafe.Pointer -//go:linkname C.CString runtime.cgo_CString -func CString(string) *C.char +//go:linkname _Cgo_CString runtime.cgo_CString +func _Cgo_CString(string) *_Cgo_char -//go:linkname C.GoString runtime.cgo_GoString -func GoString(*C.char) string +//go:linkname _Cgo_GoString runtime.cgo_GoString +func _Cgo_GoString(*_Cgo_char) string -//go:linkname C.__GoStringN runtime.cgo_GoStringN -func __GoStringN(*C.char, uintptr) string +//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN +func _Cgo___GoStringN(*_Cgo_char, uintptr) string -func GoStringN(cstr *C.char, length C.int) string { - return C.__GoStringN(cstr, uintptr(length)) +func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { + return _Cgo___GoStringN(cstr, uintptr(length)) } -//go:linkname C.__GoBytes runtime.cgo_GoBytes -func __GoBytes(unsafe.Pointer, uintptr) []byte +//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes +func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte -func GoBytes(ptr unsafe.Pointer, length C.int) []byte { - return C.__GoBytes(ptr, uintptr(length)) +func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { + return _Cgo___GoBytes(ptr, uintptr(length)) } -//go:linkname C.__CBytes runtime.cgo_CBytes -func __CBytes([]byte) unsafe.Pointer +//go:linkname _Cgo___CBytes runtime.cgo_CBytes +func _Cgo___CBytes([]byte) unsafe.Pointer -func CBytes(b []byte) unsafe.Pointer { - return C.__CBytes(b) +func _Cgo_CBytes(b []byte) unsafe.Pointer { + return _Cgo___CBytes(b) } -//go:linkname C.__get_errno_num runtime.cgo_errno -func __get_errno_num() uintptr +//go:linkname _Cgo___get_errno_num runtime.cgo_errno +func _Cgo___get_errno_num() uintptr ` const generatedGoFilePrefixOther = generatedGoFilePrefixBase + ` -func __get_errno() error { - return syscall.Errno(C.__get_errno_num()) +func _Cgo___get_errno() error { + return syscall.Errno(_Cgo___get_errno_num()) } ` @@ -197,7 +195,7 @@ func __get_errno() error { // map the errno values to match the values in the syscall package. // Source of the errno values: lib/mingw-w64/mingw-w64-headers/crt/errno.h const generatedGoFilePrefixWindows = generatedGoFilePrefixBase + ` -var __errno_mapping = [...]syscall.Errno{ +var _Cgo___errno_mapping = [...]syscall.Errno{ 1: syscall.EPERM, 2: syscall.ENOENT, 3: syscall.ESRCH, @@ -238,10 +236,10 @@ var __errno_mapping = [...]syscall.Errno{ 42: syscall.EILSEQ, } -func __get_errno() error { - num := C.__get_errno_num() - if num < uintptr(len(__errno_mapping)) { - if mapped := __errno_mapping[num]; mapped != 0 { +func _Cgo___get_errno() error { + num := _Cgo___get_errno_num() + if num < uintptr(len(_Cgo___errno_mapping)) { + if mapped := _Cgo___errno_mapping[num]; mapped != 0 { return mapped } } @@ -304,23 +302,6 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl // If the Comments field is not set to nil, the go/format package will get // confused about where comments should go. p.generated.Comments = nil - // Adjust some of the functions in there. - for _, decl := range p.generated.Decls { - switch decl := decl.(type) { - case *ast.FuncDecl: - switch decl.Name.Name { - case "CString", "GoString", "GoStringN", "__GoStringN", "GoBytes", "__GoBytes", "CBytes", "__CBytes", "__get_errno_num", "__get_errno", "__errno_mapping": - // Adjust the name to have a "C." prefix so it is correctly - // resolved. - decl.Name.Name = "C." + decl.Name.Name - } - } - } - // Patch some types, for example *C.char in C.CString. - cf := p.newCGoFile(nil, -1) // dummy *cgoFile for the walker - astutil.Apply(p.generated, func(cursor *astutil.Cursor) bool { - return cf.walker(cursor, nil) - }, nil) // Find `import "C"` C fragments in the file. p.cgoHeaders = make([]string, len(files)) // combined CGo header fragment for each file @@ -399,7 +380,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl Tok: token.TYPE, } for _, name := range builtinAliases { - typeSpec := p.getIntegerType("C."+name, names["_Cgo_"+name]) + typeSpec := p.getIntegerType("_Cgo_"+name, names["_Cgo_"+name]) gen.Specs = append(gen.Specs, typeSpec) } p.generated.Decls = append(p.generated.Decls, gen) @@ -1272,7 +1253,7 @@ func (p *cgoPackage) getUnnamedDeclName(prefix string, itf interface{}) string { func (f *cgoFile) getASTDeclName(name string, found clangCursor, iscall bool) string { // Some types are defined in stdint.h and map directly to a particular Go // type. - if alias := cgoAliases["C."+name]; alias != "" { + if alias := cgoAliases["_Cgo_"+name]; alias != "" { return alias } node := f.getASTDeclNode(name, found) @@ -1282,7 +1263,7 @@ func (f *cgoFile) getASTDeclName(name string, found clangCursor, iscall bool) st } return node.Name.Name } - return "C." + name + return "_Cgo_" + name } // getASTDeclNode will declare the given C AST node (if not already defined) and @@ -1382,8 +1363,8 @@ extern __typeof(%s) %s __attribute__((alias(%#v))); case *elaboratedTypeInfo: // Add struct bitfields. for _, bitfield := range elaboratedType.bitfields { - f.createBitfieldGetter(bitfield, "C."+name) - f.createBitfieldSetter(bitfield, "C."+name) + f.createBitfieldGetter(bitfield, "_Cgo_"+name) + f.createBitfieldSetter(bitfield, "_Cgo_"+name) } if elaboratedType.unionSize != 0 { // Create union getters/setters. @@ -1392,7 +1373,7 @@ extern __typeof(%s) %s __attribute__((alias(%#v))); f.addError(elaboratedType.pos, fmt.Sprintf("union must have field with a single name, it has %d names", len(field.Names))) continue } - f.createUnionAccessor(field, "C."+name) + f.createUnionAccessor(field, "_Cgo_"+name) } } } @@ -1441,7 +1422,7 @@ func (f *cgoFile) walker(cursor *astutil.Cursor, names map[string]clangCursor) b node.Rhs = append(node.Rhs, &ast.CallExpr{ Fun: &ast.Ident{ NamePos: node.Lhs[1].End(), - Name: "C.__get_errno", + Name: "_Cgo___get_errno", }, }) } @@ -1466,7 +1447,7 @@ func (f *cgoFile) walker(cursor *astutil.Cursor, names map[string]clangCursor) b return true } if x.Name == "C" { - name := "C." + node.Sel.Name + name := "_Cgo_" + node.Sel.Name if found, ok := names[node.Sel.Name]; ok { name = f.getASTDeclName(node.Sel.Name, found, false) } diff --git a/cgo/libclang.go b/cgo/libclang.go index 4da77ff6e7..759417a6ea 100644 --- a/cgo/libclang.go +++ b/cgo/libclang.go @@ -219,7 +219,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c)) obj := &ast.Object{ Kind: ast.Fun, - Name: "C." + name, + Name: "_Cgo_" + name, } exportName := name localName := name @@ -257,7 +257,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { }, Name: &ast.Ident{ NamePos: pos, - Name: "C." + localName, + Name: "_Cgo_" + localName, Obj: obj, }, Type: &ast.FuncType{ @@ -319,7 +319,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { return decl, stringSignature case C.CXCursor_StructDecl, C.CXCursor_UnionDecl: typ := f.makeASTRecordType(c, pos) - typeName := "C." + name + typeName := "_Cgo_" + name typeExpr := typ.typeExpr if typ.unionSize != 0 { // Convert to a single-field struct type. @@ -340,7 +340,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { obj.Decl = typeSpec return typeSpec, typ case C.CXCursor_TypedefDecl: - typeName := "C." + name + typeName := "_Cgo_" + name underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c) obj := &ast.Object{ Kind: ast.Typ, @@ -378,12 +378,12 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { } obj := &ast.Object{ Kind: ast.Var, - Name: "C." + name, + Name: "_Cgo_" + name, } valueSpec := &ast.ValueSpec{ Names: []*ast.Ident{{ NamePos: pos, - Name: "C." + name, + Name: "_Cgo_" + name, Obj: obj, }}, Type: typeExpr, @@ -407,12 +407,12 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { } obj := &ast.Object{ Kind: ast.Con, - Name: "C." + name, + Name: "_Cgo_" + name, } valueSpec := &ast.ValueSpec{ Names: []*ast.Ident{{ NamePos: pos, - Name: "C." + name, + Name: "_Cgo_" + name, Obj: obj, }}, Values: []ast.Expr{expr}, @@ -423,7 +423,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { case C.CXCursor_EnumDecl: obj := &ast.Object{ Kind: ast.Typ, - Name: "C." + name, + Name: "_Cgo_" + name, } underlying := C.tinygo_clang_getEnumDeclIntegerType(c) // TODO: gc's CGo implementation uses types such as `uint32` for enums @@ -431,7 +431,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { typeSpec := &ast.TypeSpec{ Name: &ast.Ident{ NamePos: pos, - Name: "C." + name, + Name: "_Cgo_" + name, Obj: obj, }, Assign: pos, @@ -454,12 +454,12 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { } obj := &ast.Object{ Kind: ast.Con, - Name: "C." + name, + Name: "_Cgo_" + name, } valueSpec := &ast.ValueSpec{ Names: []*ast.Ident{{ NamePos: pos, - Name: "C." + name, + Name: "_Cgo_" + name, Obj: obj, }}, Values: []ast.Expr{expr}, @@ -745,27 +745,27 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr { var typeName string switch typ.kind { case C.CXType_Char_S, C.CXType_Char_U: - typeName = "C.char" + typeName = "_Cgo_char" case C.CXType_SChar: - typeName = "C.schar" + typeName = "_Cgo_schar" case C.CXType_UChar: - typeName = "C.uchar" + typeName = "_Cgo_uchar" case C.CXType_Short: - typeName = "C.short" + typeName = "_Cgo_short" case C.CXType_UShort: - typeName = "C.ushort" + typeName = "_Cgo_ushort" case C.CXType_Int: - typeName = "C.int" + typeName = "_Cgo_int" case C.CXType_UInt: - typeName = "C.uint" + typeName = "_Cgo_uint" case C.CXType_Long: - typeName = "C.long" + typeName = "_Cgo_long" case C.CXType_ULong: - typeName = "C.ulong" + typeName = "_Cgo_ulong" case C.CXType_LongLong: - typeName = "C.longlong" + typeName = "_Cgo_longlong" case C.CXType_ULongLong: - typeName = "C.ulonglong" + typeName = "_Cgo_ulonglong" case C.CXType_Bool: typeName = "bool" case C.CXType_Float, C.CXType_Double, C.CXType_LongDouble: @@ -896,7 +896,7 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr { typeSpelling := getString(C.clang_getTypeSpelling(typ)) typeKindSpelling := getString(C.clang_getTypeKindSpelling(typ.kind)) f.addError(pos, fmt.Sprintf("unknown C type: %v (libclang type kind %s)", typeSpelling, typeKindSpelling)) - typeName = "C." + typeName = "_Cgo_" } return &ast.Ident{ NamePos: pos, @@ -913,7 +913,7 @@ func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSp var goName string typeSize := C.clang_Type_getSizeOf(underlyingType) switch name { - case "C.char": + case "_Cgo_char": if typeSize != 1 { // This happens for some very special purpose architectures // (DSPs etc.) that are not currently targeted. @@ -926,7 +926,7 @@ func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSp case C.CXType_Char_U: goName = "uint8" } - case "C.schar", "C.short", "C.int", "C.long", "C.longlong": + case "_Cgo_schar", "_Cgo_short", "_Cgo_int", "_Cgo_long", "_Cgo_longlong": switch typeSize { case 1: goName = "int8" @@ -937,7 +937,7 @@ func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSp case 8: goName = "int64" } - case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong": + case "_Cgo_uchar", "_Cgo_ushort", "_Cgo_uint", "_Cgo_ulong", "_Cgo_ulonglong": switch typeSize { case 1: goName = "uint8" diff --git a/cgo/testdata/basic.out.go b/cgo/testdata/basic.out.go index 7348ee3791..191cfba38f 100644 --- a/cgo/testdata/basic.out.go +++ b/cgo/testdata/basic.out.go @@ -5,50 +5,50 @@ import "unsafe" var _ unsafe.Pointer -//go:linkname C.CString runtime.cgo_CString -func C.CString(string) *C.char +//go:linkname _Cgo_CString runtime.cgo_CString +func _Cgo_CString(string) *_Cgo_char -//go:linkname C.GoString runtime.cgo_GoString -func C.GoString(*C.char) string +//go:linkname _Cgo_GoString runtime.cgo_GoString +func _Cgo_GoString(*_Cgo_char) string -//go:linkname C.__GoStringN runtime.cgo_GoStringN -func C.__GoStringN(*C.char, uintptr) string +//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN +func _Cgo___GoStringN(*_Cgo_char, uintptr) string -func C.GoStringN(cstr *C.char, length C.int) string { - return C.__GoStringN(cstr, uintptr(length)) +func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { + return _Cgo___GoStringN(cstr, uintptr(length)) } -//go:linkname C.__GoBytes runtime.cgo_GoBytes -func C.__GoBytes(unsafe.Pointer, uintptr) []byte +//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes +func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte -func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte { - return C.__GoBytes(ptr, uintptr(length)) +func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { + return _Cgo___GoBytes(ptr, uintptr(length)) } -//go:linkname C.__CBytes runtime.cgo_CBytes -func C.__CBytes([]byte) unsafe.Pointer +//go:linkname _Cgo___CBytes runtime.cgo_CBytes +func _Cgo___CBytes([]byte) unsafe.Pointer -func C.CBytes(b []byte) unsafe.Pointer { - return C.__CBytes(b) +func _Cgo_CBytes(b []byte) unsafe.Pointer { + return _Cgo___CBytes(b) } -//go:linkname C.__get_errno_num runtime.cgo_errno -func C.__get_errno_num() uintptr +//go:linkname _Cgo___get_errno_num runtime.cgo_errno +func _Cgo___get_errno_num() uintptr -func C.__get_errno() error { - return syscall.Errno(C.__get_errno_num()) +func _Cgo___get_errno() error { + return syscall.Errno(_Cgo___get_errno_num()) } type ( - C.char uint8 - C.schar int8 - C.uchar uint8 - C.short int16 - C.ushort uint16 - C.int int32 - C.uint uint32 - C.long int32 - C.ulong uint32 - C.longlong int64 - C.ulonglong uint64 + _Cgo_char uint8 + _Cgo_schar int8 + _Cgo_uchar uint8 + _Cgo_short int16 + _Cgo_ushort uint16 + _Cgo_int int32 + _Cgo_uint uint32 + _Cgo_long int32 + _Cgo_ulong uint32 + _Cgo_longlong int64 + _Cgo_ulonglong uint64 ) diff --git a/cgo/testdata/const.out.go b/cgo/testdata/const.out.go index 21705afc4f..0329ba5985 100644 --- a/cgo/testdata/const.out.go +++ b/cgo/testdata/const.out.go @@ -5,58 +5,58 @@ import "unsafe" var _ unsafe.Pointer -//go:linkname C.CString runtime.cgo_CString -func C.CString(string) *C.char +//go:linkname _Cgo_CString runtime.cgo_CString +func _Cgo_CString(string) *_Cgo_char -//go:linkname C.GoString runtime.cgo_GoString -func C.GoString(*C.char) string +//go:linkname _Cgo_GoString runtime.cgo_GoString +func _Cgo_GoString(*_Cgo_char) string -//go:linkname C.__GoStringN runtime.cgo_GoStringN -func C.__GoStringN(*C.char, uintptr) string +//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN +func _Cgo___GoStringN(*_Cgo_char, uintptr) string -func C.GoStringN(cstr *C.char, length C.int) string { - return C.__GoStringN(cstr, uintptr(length)) +func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { + return _Cgo___GoStringN(cstr, uintptr(length)) } -//go:linkname C.__GoBytes runtime.cgo_GoBytes -func C.__GoBytes(unsafe.Pointer, uintptr) []byte +//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes +func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte -func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte { - return C.__GoBytes(ptr, uintptr(length)) +func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { + return _Cgo___GoBytes(ptr, uintptr(length)) } -//go:linkname C.__CBytes runtime.cgo_CBytes -func C.__CBytes([]byte) unsafe.Pointer +//go:linkname _Cgo___CBytes runtime.cgo_CBytes +func _Cgo___CBytes([]byte) unsafe.Pointer -func C.CBytes(b []byte) unsafe.Pointer { - return C.__CBytes(b) +func _Cgo_CBytes(b []byte) unsafe.Pointer { + return _Cgo___CBytes(b) } -//go:linkname C.__get_errno_num runtime.cgo_errno -func C.__get_errno_num() uintptr +//go:linkname _Cgo___get_errno_num runtime.cgo_errno +func _Cgo___get_errno_num() uintptr -func C.__get_errno() error { - return syscall.Errno(C.__get_errno_num()) +func _Cgo___get_errno() error { + return syscall.Errno(_Cgo___get_errno_num()) } type ( - C.char uint8 - C.schar int8 - C.uchar uint8 - C.short int16 - C.ushort uint16 - C.int int32 - C.uint uint32 - C.long int32 - C.ulong uint32 - C.longlong int64 - C.ulonglong uint64 + _Cgo_char uint8 + _Cgo_schar int8 + _Cgo_uchar uint8 + _Cgo_short int16 + _Cgo_ushort uint16 + _Cgo_int int32 + _Cgo_uint uint32 + _Cgo_long int32 + _Cgo_ulong uint32 + _Cgo_longlong int64 + _Cgo_ulonglong uint64 ) -const C.foo = 3 -const C.bar = C.foo -const C.unreferenced = 4 -const C.referenced = C.unreferenced -const C.fnlike_val = 5 -const C.square_val = (20 * 20) -const C.add_val = (3 + 5) +const _Cgo_foo = 3 +const _Cgo_bar = _Cgo_foo +const _Cgo_unreferenced = 4 +const _Cgo_referenced = _Cgo_unreferenced +const _Cgo_fnlike_val = 5 +const _Cgo_square_val = (20 * 20) +const _Cgo_add_val = (3 + 5) diff --git a/cgo/testdata/errors.out.go b/cgo/testdata/errors.out.go index e0f7d1f541..0ae794087f 100644 --- a/cgo/testdata/errors.out.go +++ b/cgo/testdata/errors.out.go @@ -14,16 +14,16 @@ // testdata/errors.go:3:1: function "unusedFunction" in #cgo noescape line is not used // Type checking errors after CGo processing: -// testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as C.char value in variable declaration (overflows) +// testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as _Cgo_char value in variable declaration (overflows) // testdata/errors.go:105: unknown field z in struct literal -// testdata/errors.go:108: undefined: C.SOME_CONST_1 -// testdata/errors.go:110: cannot use C.SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows) -// testdata/errors.go:112: undefined: C.SOME_CONST_4 -// testdata/errors.go:114: undefined: C.SOME_CONST_b -// testdata/errors.go:116: undefined: C.SOME_CONST_startspace -// testdata/errors.go:119: undefined: C.SOME_PARAM_CONST_invalid -// testdata/errors.go:122: undefined: C.add_toomuch -// testdata/errors.go:123: undefined: C.add_toolittle +// testdata/errors.go:108: undefined: _Cgo_SOME_CONST_1 +// testdata/errors.go:110: cannot use _Cgo_SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows) +// testdata/errors.go:112: undefined: _Cgo_SOME_CONST_4 +// testdata/errors.go:114: undefined: _Cgo_SOME_CONST_b +// testdata/errors.go:116: undefined: _Cgo_SOME_CONST_startspace +// testdata/errors.go:119: undefined: _Cgo_SOME_PARAM_CONST_invalid +// testdata/errors.go:122: undefined: _Cgo_add_toomuch +// testdata/errors.go:123: undefined: _Cgo_add_toolittle package main @@ -32,58 +32,58 @@ import "unsafe" var _ unsafe.Pointer -//go:linkname C.CString runtime.cgo_CString -func C.CString(string) *C.char +//go:linkname _Cgo_CString runtime.cgo_CString +func _Cgo_CString(string) *_Cgo_char -//go:linkname C.GoString runtime.cgo_GoString -func C.GoString(*C.char) string +//go:linkname _Cgo_GoString runtime.cgo_GoString +func _Cgo_GoString(*_Cgo_char) string -//go:linkname C.__GoStringN runtime.cgo_GoStringN -func C.__GoStringN(*C.char, uintptr) string +//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN +func _Cgo___GoStringN(*_Cgo_char, uintptr) string -func C.GoStringN(cstr *C.char, length C.int) string { - return C.__GoStringN(cstr, uintptr(length)) +func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { + return _Cgo___GoStringN(cstr, uintptr(length)) } -//go:linkname C.__GoBytes runtime.cgo_GoBytes -func C.__GoBytes(unsafe.Pointer, uintptr) []byte +//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes +func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte -func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte { - return C.__GoBytes(ptr, uintptr(length)) +func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { + return _Cgo___GoBytes(ptr, uintptr(length)) } -//go:linkname C.__CBytes runtime.cgo_CBytes -func C.__CBytes([]byte) unsafe.Pointer +//go:linkname _Cgo___CBytes runtime.cgo_CBytes +func _Cgo___CBytes([]byte) unsafe.Pointer -func C.CBytes(b []byte) unsafe.Pointer { - return C.__CBytes(b) +func _Cgo_CBytes(b []byte) unsafe.Pointer { + return _Cgo___CBytes(b) } -//go:linkname C.__get_errno_num runtime.cgo_errno -func C.__get_errno_num() uintptr +//go:linkname _Cgo___get_errno_num runtime.cgo_errno +func _Cgo___get_errno_num() uintptr -func C.__get_errno() error { - return syscall.Errno(C.__get_errno_num()) +func _Cgo___get_errno() error { + return syscall.Errno(_Cgo___get_errno_num()) } type ( - C.char uint8 - C.schar int8 - C.uchar uint8 - C.short int16 - C.ushort uint16 - C.int int32 - C.uint uint32 - C.long int32 - C.ulong uint32 - C.longlong int64 - C.ulonglong uint64 + _Cgo_char uint8 + _Cgo_schar int8 + _Cgo_uchar uint8 + _Cgo_short int16 + _Cgo_ushort uint16 + _Cgo_int int32 + _Cgo_uint uint32 + _Cgo_long int32 + _Cgo_ulong uint32 + _Cgo_longlong int64 + _Cgo_ulonglong uint64 ) -type C.struct_point_t struct { - x C.int - y C.int +type _Cgo_struct_point_t struct { + x _Cgo_int + y _Cgo_int } -type C.point_t = C.struct_point_t +type _Cgo_point_t = _Cgo_struct_point_t -const C.SOME_CONST_3 = 1234 -const C.SOME_PARAM_CONST_valid = 3 + 4 +const _Cgo_SOME_CONST_3 = 1234 +const _Cgo_SOME_PARAM_CONST_valid = 3 + 4 diff --git a/cgo/testdata/flags.out.go b/cgo/testdata/flags.out.go index 8662412296..ac5cf546db 100644 --- a/cgo/testdata/flags.out.go +++ b/cgo/testdata/flags.out.go @@ -10,53 +10,53 @@ import "unsafe" var _ unsafe.Pointer -//go:linkname C.CString runtime.cgo_CString -func C.CString(string) *C.char +//go:linkname _Cgo_CString runtime.cgo_CString +func _Cgo_CString(string) *_Cgo_char -//go:linkname C.GoString runtime.cgo_GoString -func C.GoString(*C.char) string +//go:linkname _Cgo_GoString runtime.cgo_GoString +func _Cgo_GoString(*_Cgo_char) string -//go:linkname C.__GoStringN runtime.cgo_GoStringN -func C.__GoStringN(*C.char, uintptr) string +//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN +func _Cgo___GoStringN(*_Cgo_char, uintptr) string -func C.GoStringN(cstr *C.char, length C.int) string { - return C.__GoStringN(cstr, uintptr(length)) +func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { + return _Cgo___GoStringN(cstr, uintptr(length)) } -//go:linkname C.__GoBytes runtime.cgo_GoBytes -func C.__GoBytes(unsafe.Pointer, uintptr) []byte +//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes +func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte -func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte { - return C.__GoBytes(ptr, uintptr(length)) +func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { + return _Cgo___GoBytes(ptr, uintptr(length)) } -//go:linkname C.__CBytes runtime.cgo_CBytes -func C.__CBytes([]byte) unsafe.Pointer +//go:linkname _Cgo___CBytes runtime.cgo_CBytes +func _Cgo___CBytes([]byte) unsafe.Pointer -func C.CBytes(b []byte) unsafe.Pointer { - return C.__CBytes(b) +func _Cgo_CBytes(b []byte) unsafe.Pointer { + return _Cgo___CBytes(b) } -//go:linkname C.__get_errno_num runtime.cgo_errno -func C.__get_errno_num() uintptr +//go:linkname _Cgo___get_errno_num runtime.cgo_errno +func _Cgo___get_errno_num() uintptr -func C.__get_errno() error { - return syscall.Errno(C.__get_errno_num()) +func _Cgo___get_errno() error { + return syscall.Errno(_Cgo___get_errno_num()) } type ( - C.char uint8 - C.schar int8 - C.uchar uint8 - C.short int16 - C.ushort uint16 - C.int int32 - C.uint uint32 - C.long int32 - C.ulong uint32 - C.longlong int64 - C.ulonglong uint64 + _Cgo_char uint8 + _Cgo_schar int8 + _Cgo_uchar uint8 + _Cgo_short int16 + _Cgo_ushort uint16 + _Cgo_int int32 + _Cgo_uint uint32 + _Cgo_long int32 + _Cgo_ulong uint32 + _Cgo_longlong int64 + _Cgo_ulonglong uint64 ) -const C.BAR = 3 -const C.FOO_H = 1 +const _Cgo_BAR = 3 +const _Cgo_FOO_H = 1 diff --git a/cgo/testdata/symbols.out.go b/cgo/testdata/symbols.out.go index 2ca80c1e65..8a603cfd7e 100644 --- a/cgo/testdata/symbols.out.go +++ b/cgo/testdata/symbols.out.go @@ -5,80 +5,80 @@ import "unsafe" var _ unsafe.Pointer -//go:linkname C.CString runtime.cgo_CString -func C.CString(string) *C.char +//go:linkname _Cgo_CString runtime.cgo_CString +func _Cgo_CString(string) *_Cgo_char -//go:linkname C.GoString runtime.cgo_GoString -func C.GoString(*C.char) string +//go:linkname _Cgo_GoString runtime.cgo_GoString +func _Cgo_GoString(*_Cgo_char) string -//go:linkname C.__GoStringN runtime.cgo_GoStringN -func C.__GoStringN(*C.char, uintptr) string +//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN +func _Cgo___GoStringN(*_Cgo_char, uintptr) string -func C.GoStringN(cstr *C.char, length C.int) string { - return C.__GoStringN(cstr, uintptr(length)) +func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { + return _Cgo___GoStringN(cstr, uintptr(length)) } -//go:linkname C.__GoBytes runtime.cgo_GoBytes -func C.__GoBytes(unsafe.Pointer, uintptr) []byte +//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes +func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte -func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte { - return C.__GoBytes(ptr, uintptr(length)) +func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { + return _Cgo___GoBytes(ptr, uintptr(length)) } -//go:linkname C.__CBytes runtime.cgo_CBytes -func C.__CBytes([]byte) unsafe.Pointer +//go:linkname _Cgo___CBytes runtime.cgo_CBytes +func _Cgo___CBytes([]byte) unsafe.Pointer -func C.CBytes(b []byte) unsafe.Pointer { - return C.__CBytes(b) +func _Cgo_CBytes(b []byte) unsafe.Pointer { + return _Cgo___CBytes(b) } -//go:linkname C.__get_errno_num runtime.cgo_errno -func C.__get_errno_num() uintptr +//go:linkname _Cgo___get_errno_num runtime.cgo_errno +func _Cgo___get_errno_num() uintptr -func C.__get_errno() error { - return syscall.Errno(C.__get_errno_num()) +func _Cgo___get_errno() error { + return syscall.Errno(_Cgo___get_errno_num()) } type ( - C.char uint8 - C.schar int8 - C.uchar uint8 - C.short int16 - C.ushort uint16 - C.int int32 - C.uint uint32 - C.long int32 - C.ulong uint32 - C.longlong int64 - C.ulonglong uint64 + _Cgo_char uint8 + _Cgo_schar int8 + _Cgo_uchar uint8 + _Cgo_short int16 + _Cgo_ushort uint16 + _Cgo_int int32 + _Cgo_uint uint32 + _Cgo_long int32 + _Cgo_ulong uint32 + _Cgo_longlong int64 + _Cgo_ulonglong uint64 ) //export foo -func C.foo(a C.int, b C.int) C.int +func _Cgo_foo(a _Cgo_int, b _Cgo_int) _Cgo_int -var C.foo$funcaddr unsafe.Pointer +var _Cgo_foo$funcaddr unsafe.Pointer //export variadic0 //go:variadic -func C.variadic0() +func _Cgo_variadic0() -var C.variadic0$funcaddr unsafe.Pointer +var _Cgo_variadic0$funcaddr unsafe.Pointer //export variadic2 //go:variadic -func C.variadic2(x C.int, y C.int) +func _Cgo_variadic2(x _Cgo_int, y _Cgo_int) -var C.variadic2$funcaddr unsafe.Pointer +var _Cgo_variadic2$funcaddr unsafe.Pointer //export _Cgo_static_173c95a79b6df1980521_staticfunc -func C.staticfunc!symbols.go(x C.int) +func _Cgo_staticfunc!symbols.go(x _Cgo_int) -var C.staticfunc!symbols.go$funcaddr unsafe.Pointer +var _Cgo_staticfunc!symbols.go$funcaddr unsafe.Pointer //export notEscapingFunction //go:noescape -func C.notEscapingFunction(a *C.int) +func _Cgo_notEscapingFunction(a *_Cgo_int) -var C.notEscapingFunction$funcaddr unsafe.Pointer +var _Cgo_notEscapingFunction$funcaddr unsafe.Pointer //go:extern someValue -var C.someValue C.int +var _Cgo_someValue _Cgo_int diff --git a/cgo/testdata/types.out.go b/cgo/testdata/types.out.go index b3fe414b07..3eaa53f1fb 100644 --- a/cgo/testdata/types.out.go +++ b/cgo/testdata/types.out.go @@ -5,158 +5,162 @@ import "unsafe" var _ unsafe.Pointer -//go:linkname C.CString runtime.cgo_CString -func C.CString(string) *C.char +//go:linkname _Cgo_CString runtime.cgo_CString +func _Cgo_CString(string) *_Cgo_char -//go:linkname C.GoString runtime.cgo_GoString -func C.GoString(*C.char) string +//go:linkname _Cgo_GoString runtime.cgo_GoString +func _Cgo_GoString(*_Cgo_char) string -//go:linkname C.__GoStringN runtime.cgo_GoStringN -func C.__GoStringN(*C.char, uintptr) string +//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN +func _Cgo___GoStringN(*_Cgo_char, uintptr) string -func C.GoStringN(cstr *C.char, length C.int) string { - return C.__GoStringN(cstr, uintptr(length)) +func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { + return _Cgo___GoStringN(cstr, uintptr(length)) } -//go:linkname C.__GoBytes runtime.cgo_GoBytes -func C.__GoBytes(unsafe.Pointer, uintptr) []byte +//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes +func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte -func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte { - return C.__GoBytes(ptr, uintptr(length)) +func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { + return _Cgo___GoBytes(ptr, uintptr(length)) } -//go:linkname C.__CBytes runtime.cgo_CBytes -func C.__CBytes([]byte) unsafe.Pointer +//go:linkname _Cgo___CBytes runtime.cgo_CBytes +func _Cgo___CBytes([]byte) unsafe.Pointer -func C.CBytes(b []byte) unsafe.Pointer { - return C.__CBytes(b) +func _Cgo_CBytes(b []byte) unsafe.Pointer { + return _Cgo___CBytes(b) } -//go:linkname C.__get_errno_num runtime.cgo_errno -func C.__get_errno_num() uintptr +//go:linkname _Cgo___get_errno_num runtime.cgo_errno +func _Cgo___get_errno_num() uintptr -func C.__get_errno() error { - return syscall.Errno(C.__get_errno_num()) +func _Cgo___get_errno() error { + return syscall.Errno(_Cgo___get_errno_num()) } type ( - C.char uint8 - C.schar int8 - C.uchar uint8 - C.short int16 - C.ushort uint16 - C.int int32 - C.uint uint32 - C.long int32 - C.ulong uint32 - C.longlong int64 - C.ulonglong uint64 + _Cgo_char uint8 + _Cgo_schar int8 + _Cgo_uchar uint8 + _Cgo_short int16 + _Cgo_ushort uint16 + _Cgo_int int32 + _Cgo_uint uint32 + _Cgo_long int32 + _Cgo_ulong uint32 + _Cgo_longlong int64 + _Cgo_ulonglong uint64 ) -type C.myint = C.int -type C.struct_point2d_t struct { - x C.int - y C.int -} -type C.point2d_t = C.struct_point2d_t -type C.struct_point3d struct { - x C.int - y C.int - z C.int -} -type C.point3d_t = C.struct_point3d -type C.struct_type1 struct { - _type C.int - __type C.int - ___type C.int -} -type C.struct_type2 struct{ _type C.int } -type C.union_union1_t struct{ i C.int } -type C.union1_t = C.union_union1_t -type C.union_union3_t struct{ $union uint64 } - -func (union *C.union_union3_t) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) } -func (union *C.union_union3_t) unionfield_d() *float64 { +type _Cgo_myint = _Cgo_int +type _Cgo_struct_point2d_t struct { + x _Cgo_int + y _Cgo_int +} +type _Cgo_point2d_t = _Cgo_struct_point2d_t +type _Cgo_struct_point3d struct { + x _Cgo_int + y _Cgo_int + z _Cgo_int +} +type _Cgo_point3d_t = _Cgo_struct_point3d +type _Cgo_struct_type1 struct { + _type _Cgo_int + __type _Cgo_int + ___type _Cgo_int +} +type _Cgo_struct_type2 struct{ _type _Cgo_int } +type _Cgo_union_union1_t struct{ i _Cgo_int } +type _Cgo_union1_t = _Cgo_union_union1_t +type _Cgo_union_union3_t struct{ $union uint64 } + +func (union *_Cgo_union_union3_t) unionfield_i() *_Cgo_int { + return (*_Cgo_int)(unsafe.Pointer(&union.$union)) +} +func (union *_Cgo_union_union3_t) unionfield_d() *float64 { return (*float64)(unsafe.Pointer(&union.$union)) } -func (union *C.union_union3_t) unionfield_s() *C.short { - return (*C.short)(unsafe.Pointer(&union.$union)) +func (union *_Cgo_union_union3_t) unionfield_s() *_Cgo_short { + return (*_Cgo_short)(unsafe.Pointer(&union.$union)) } -type C.union3_t = C.union_union3_t -type C.union_union2d struct{ $union [2]uint64 } +type _Cgo_union3_t = _Cgo_union_union3_t +type _Cgo_union_union2d struct{ $union [2]uint64 } -func (union *C.union_union2d) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) } -func (union *C.union_union2d) unionfield_d() *[2]float64 { +func (union *_Cgo_union_union2d) unionfield_i() *_Cgo_int { + return (*_Cgo_int)(unsafe.Pointer(&union.$union)) +} +func (union *_Cgo_union_union2d) unionfield_d() *[2]float64 { return (*[2]float64)(unsafe.Pointer(&union.$union)) } -type C.union2d_t = C.union_union2d -type C.union_unionarray_t struct{ arr [10]C.uchar } -type C.unionarray_t = C.union_unionarray_t -type C._Ctype_union___0 struct{ $union [3]uint32 } +type _Cgo_union2d_t = _Cgo_union_union2d +type _Cgo_union_unionarray_t struct{ arr [10]_Cgo_uchar } +type _Cgo_unionarray_t = _Cgo_union_unionarray_t +type _Cgo__Ctype_union___0 struct{ $union [3]uint32 } -func (union *C._Ctype_union___0) unionfield_area() *C.point2d_t { - return (*C.point2d_t)(unsafe.Pointer(&union.$union)) +func (union *_Cgo__Ctype_union___0) unionfield_area() *_Cgo_point2d_t { + return (*_Cgo_point2d_t)(unsafe.Pointer(&union.$union)) } -func (union *C._Ctype_union___0) unionfield_solid() *C.point3d_t { - return (*C.point3d_t)(unsafe.Pointer(&union.$union)) +func (union *_Cgo__Ctype_union___0) unionfield_solid() *_Cgo_point3d_t { + return (*_Cgo_point3d_t)(unsafe.Pointer(&union.$union)) } -type C.struct_struct_nested_t struct { - begin C.point2d_t - end C.point2d_t - tag C.int +type _Cgo_struct_struct_nested_t struct { + begin _Cgo_point2d_t + end _Cgo_point2d_t + tag _Cgo_int - coord C._Ctype_union___0 + coord _Cgo__Ctype_union___0 } -type C.struct_nested_t = C.struct_struct_nested_t -type C.union_union_nested_t struct{ $union [2]uint64 } +type _Cgo_struct_nested_t = _Cgo_struct_struct_nested_t +type _Cgo_union_union_nested_t struct{ $union [2]uint64 } -func (union *C.union_union_nested_t) unionfield_point() *C.point3d_t { - return (*C.point3d_t)(unsafe.Pointer(&union.$union)) +func (union *_Cgo_union_union_nested_t) unionfield_point() *_Cgo_point3d_t { + return (*_Cgo_point3d_t)(unsafe.Pointer(&union.$union)) } -func (union *C.union_union_nested_t) unionfield_array() *C.unionarray_t { - return (*C.unionarray_t)(unsafe.Pointer(&union.$union)) +func (union *_Cgo_union_union_nested_t) unionfield_array() *_Cgo_unionarray_t { + return (*_Cgo_unionarray_t)(unsafe.Pointer(&union.$union)) } -func (union *C.union_union_nested_t) unionfield_thing() *C.union3_t { - return (*C.union3_t)(unsafe.Pointer(&union.$union)) +func (union *_Cgo_union_union_nested_t) unionfield_thing() *_Cgo_union3_t { + return (*_Cgo_union3_t)(unsafe.Pointer(&union.$union)) } -type C.union_nested_t = C.union_union_nested_t -type C.enum_option = C.int -type C.option_t = C.enum_option -type C.enum_option2_t = C.uint -type C.option2_t = C.enum_option2_t -type C.struct_types_t struct { +type _Cgo_union_nested_t = _Cgo_union_union_nested_t +type _Cgo_enum_option = _Cgo_int +type _Cgo_option_t = _Cgo_enum_option +type _Cgo_enum_option2_t = _Cgo_uint +type _Cgo_option2_t = _Cgo_enum_option2_t +type _Cgo_struct_types_t struct { f float32 d float64 - ptr *C.int + ptr *_Cgo_int } -type C.types_t = C.struct_types_t -type C.myIntArray = [10]C.int -type C.struct_bitfield_t struct { - start C.uchar - __bitfield_1 C.uchar +type _Cgo_types_t = _Cgo_struct_types_t +type _Cgo_myIntArray = [10]_Cgo_int +type _Cgo_struct_bitfield_t struct { + start _Cgo_uchar + __bitfield_1 _Cgo_uchar - d C.uchar - e C.uchar + d _Cgo_uchar + e _Cgo_uchar } -func (s *C.struct_bitfield_t) bitfield_a() C.uchar { return s.__bitfield_1 & 0x1f } -func (s *C.struct_bitfield_t) set_bitfield_a(value C.uchar) { +func (s *_Cgo_struct_bitfield_t) bitfield_a() _Cgo_uchar { return s.__bitfield_1 & 0x1f } +func (s *_Cgo_struct_bitfield_t) set_bitfield_a(value _Cgo_uchar) { s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0 } -func (s *C.struct_bitfield_t) bitfield_b() C.uchar { +func (s *_Cgo_struct_bitfield_t) bitfield_b() _Cgo_uchar { return s.__bitfield_1 >> 5 & 0x1 } -func (s *C.struct_bitfield_t) set_bitfield_b(value C.uchar) { +func (s *_Cgo_struct_bitfield_t) set_bitfield_b(value _Cgo_uchar) { s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5 } -func (s *C.struct_bitfield_t) bitfield_c() C.uchar { +func (s *_Cgo_struct_bitfield_t) bitfield_c() _Cgo_uchar { return s.__bitfield_1 >> 6 } -func (s *C.struct_bitfield_t) set_bitfield_c(value C.uchar, +func (s *_Cgo_struct_bitfield_t) set_bitfield_c(value _Cgo_uchar, ) { s.__bitfield_1 = s.__bitfield_1&0x3f | value<<6 } -type C.bitfield_t = C.struct_bitfield_t +type _Cgo_bitfield_t = _Cgo_struct_bitfield_t diff --git a/compiler/symbol.go b/compiler/symbol.go index 5ecf68e54a..1de3c6f39d 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -432,9 +432,8 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { // pass for C variadic functions. This includes both explicit // (with ...) and implicit (no parameters in signature) // functions. - if strings.HasPrefix(f.Name(), "C.") { - // This prefix cannot naturally be created, it must have - // been created as a result of CGo preprocessing. + if strings.HasPrefix(f.Name(), "_Cgo_") { + // This prefix was created as a result of CGo preprocessing. info.variadic = true } } From 52b9fcd57f73d358de2f8d86874cd16d909de166 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 16 Feb 2025 15:11:39 +0100 Subject: [PATCH 377/444] machine: remove bytes package dependency in flash code This also moves flash padding code to a single place, since it was copied 5 times. This change is necessary in Go 1.24 to avoid an import cycle. --- src/machine/flash.go | 11 +++++++++++ src/machine/machine_atsamd21.go | 14 +------------- src/machine/machine_atsamd51.go | 14 +------------- src/machine/machine_nrf.go | 14 +------------- src/machine/machine_rp2040_flash.go | 12 ------------ src/machine/machine_rp2040_rom.go | 2 +- src/machine/machine_stm32_flash.go | 15 ++------------- 7 files changed, 17 insertions(+), 65 deletions(-) diff --git a/src/machine/flash.go b/src/machine/flash.go index 716fc4a53b..da832afdb8 100644 --- a/src/machine/flash.go +++ b/src/machine/flash.go @@ -64,3 +64,14 @@ type BlockDevice interface { // EraseBlockSize to map addresses to blocks. EraseBlocks(start, len int64) error } + +// pad data if needed so it is long enough for correct byte alignment on writes. +func flashPad(p []byte, writeBlockSize int) []byte { + overflow := len(p) % writeBlockSize + if overflow != 0 { + for i := 0; i < writeBlockSize-overflow; i++ { + p = append(p, 0xff) + } + } + return p +} diff --git a/src/machine/machine_atsamd21.go b/src/machine/machine_atsamd21.go index 3d0abc0fa8..0a5c320ca0 100644 --- a/src/machine/machine_atsamd21.go +++ b/src/machine/machine_atsamd21.go @@ -7,7 +7,6 @@ package machine import ( - "bytes" "device/arm" "device/sam" "errors" @@ -1917,7 +1916,7 @@ func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { f.ensureInitComplete() address := FlashDataStart() + uintptr(off) - padded := f.pad(p) + padded := flashPad(p, int(f.WriteBlockSize())) waitWhileFlashBusy() @@ -1992,17 +1991,6 @@ func (f flashBlockDevice) EraseBlocks(start, len int64) error { return nil } -// pad data if needed so it is long enough for correct byte alignment on writes. -func (f flashBlockDevice) pad(p []byte) []byte { - overflow := int64(len(p)) % f.WriteBlockSize() - if overflow == 0 { - return p - } - - padding := bytes.Repeat([]byte{0xff}, int(f.WriteBlockSize()-overflow)) - return append(p, padding...) -} - func (f flashBlockDevice) ensureInitComplete() { if f.initComplete { return diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go index bcaaec7212..5cd1d314a2 100644 --- a/src/machine/machine_atsamd51.go +++ b/src/machine/machine_atsamd51.go @@ -7,7 +7,6 @@ package machine import ( - "bytes" "device/arm" "device/sam" "errors" @@ -2174,7 +2173,7 @@ func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { } address := FlashDataStart() + uintptr(off) - padded := f.pad(p) + padded := flashPad(p, int(f.WriteBlockSize())) settings := disableFlashCache() defer restoreFlashCache(settings) @@ -2263,17 +2262,6 @@ func (f flashBlockDevice) EraseBlocks(start, len int64) error { return nil } -// pad data if needed so it is long enough for correct byte alignment on writes. -func (f flashBlockDevice) pad(p []byte) []byte { - overflow := int64(len(p)) % f.WriteBlockSize() - if overflow == 0 { - return p - } - - padding := bytes.Repeat([]byte{0xff}, int(f.WriteBlockSize()-overflow)) - return append(p, padding...) -} - func disableFlashCache() uint16 { settings := sam.NVMCTRL.CTRLA.Get() diff --git a/src/machine/machine_nrf.go b/src/machine/machine_nrf.go index 4da6645352..d6d6349f29 100644 --- a/src/machine/machine_nrf.go +++ b/src/machine/machine_nrf.go @@ -3,7 +3,6 @@ package machine import ( - "bytes" "device/nrf" "internal/binary" "runtime/interrupt" @@ -386,7 +385,7 @@ func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { } address := FlashDataStart() + uintptr(off) - padded := f.pad(p) + padded := flashPad(p, int(f.WriteBlockSize())) waitWhileFlashBusy() @@ -444,17 +443,6 @@ func (f flashBlockDevice) EraseBlocks(start, len int64) error { return nil } -// pad data if needed so it is long enough for correct byte alignment on writes. -func (f flashBlockDevice) pad(p []byte) []byte { - overflow := int64(len(p)) % f.WriteBlockSize() - if overflow == 0 { - return p - } - - padding := bytes.Repeat([]byte{0xff}, int(f.WriteBlockSize()-overflow)) - return append(p, padding...) -} - func waitWhileFlashBusy() { for nrf.NVMC.GetREADY() != nrf.NVMC_READY_READY_Ready { } diff --git a/src/machine/machine_rp2040_flash.go b/src/machine/machine_rp2040_flash.go index 8ee881e19a..1317c0926a 100644 --- a/src/machine/machine_rp2040_flash.go +++ b/src/machine/machine_rp2040_flash.go @@ -3,7 +3,6 @@ package machine import ( - "bytes" "unsafe" ) @@ -101,17 +100,6 @@ func (f flashBlockDevice) EraseBlocks(start, length int64) error { return f.eraseBlocks(start, length) } -// pad data if needed so it is long enough for correct byte alignment on writes. -func (f flashBlockDevice) pad(p []byte) []byte { - overflow := int64(len(p)) % f.WriteBlockSize() - if overflow == 0 { - return p - } - - padding := bytes.Repeat([]byte{0xff}, int(f.WriteBlockSize()-overflow)) - return append(p, padding...) -} - // return the correct address to be used for write func writeAddress(off int64) uintptr { return readAddress(off) - uintptr(memoryStart) diff --git a/src/machine/machine_rp2040_rom.go b/src/machine/machine_rp2040_rom.go index eea461882d..5541e2a9bf 100644 --- a/src/machine/machine_rp2040_rom.go +++ b/src/machine/machine_rp2040_rom.go @@ -230,7 +230,7 @@ func (f flashBlockDevice) writeAt(p []byte, off int64) (n int, err error) { // e.g. real address 0x10003000 is written to at // 0x00003000 address := writeAddress(off) - padded := f.pad(p) + padded := flashPad(p, int(f.WriteBlockSize())) C.flash_range_write(C.uint32_t(address), (*C.uint8_t)(unsafe.Pointer(&padded[0])), diff --git a/src/machine/machine_stm32_flash.go b/src/machine/machine_stm32_flash.go index 710aa05d00..280dc8987e 100644 --- a/src/machine/machine_stm32_flash.go +++ b/src/machine/machine_stm32_flash.go @@ -5,7 +5,6 @@ package machine import ( "device/stm32" - "bytes" "unsafe" ) @@ -41,7 +40,8 @@ func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { unlockFlash() defer lockFlash() - return writeFlashData(FlashDataStart()+uintptr(off), f.pad(p)) + p = flashPad(p, int(f.WriteBlockSize())) + return writeFlashData(FlashDataStart()+uintptr(off), p) } // Size returns the number of bytes in this block device. @@ -90,17 +90,6 @@ func (f flashBlockDevice) EraseBlocks(start, len int64) error { return nil } -// pad data if needed so it is long enough for correct byte alignment on writes. -func (f flashBlockDevice) pad(p []byte) []byte { - overflow := int64(len(p)) % f.WriteBlockSize() - if overflow == 0 { - return p - } - - padding := bytes.Repeat([]byte{0xff}, int(f.WriteBlockSize()-overflow)) - return append(p, padding...) -} - const memoryStart = 0x08000000 func unlockFlash() { From a1be8cd9fefb5f085d84ce8f2b1115ec287be7d5 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 16 Feb 2025 15:29:55 +0100 Subject: [PATCH 378/444] machine/usb/descriptor: avoid bytes package We can't use the bytes package in Go 1.24 since it would result in an import cycle. Therefore, use bytealg.Index instead. (I'm not sure this code is correct - just searching for a range of bytes seems brittle. But at least this commit shouldn't change the code). --- src/machine/usb/descriptor/hid.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/machine/usb/descriptor/hid.go b/src/machine/usb/descriptor/hid.go index ea65f1ed97..06b9801530 100644 --- a/src/machine/usb/descriptor/hid.go +++ b/src/machine/usb/descriptor/hid.go @@ -1,9 +1,9 @@ package descriptor import ( - "bytes" "errors" "internal/binary" + "internal/bytealg" ) var configurationCDCHID = [configurationTypeLen]byte{ @@ -87,7 +87,7 @@ func FindClassHIDType(des, class []byte) (ClassHIDType, error) { // search only for ClassHIDType without the ClassLength, // in case it has already been set. - idx := bytes.Index(des, class[:ClassHIDTypeLen-2]) + idx := bytealg.Index(des, class[:ClassHIDTypeLen-2]) if idx == -1 { return ClassHIDType{}, errNoClassHIDFound } From 07c7eff3c37d46ac86808df5839dc6d76df67ee5 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 18 Feb 2025 14:19:46 +0100 Subject: [PATCH 379/444] runtime: add FIPS helper functions Not entirely sure what they're for, but the Go runtime also stores this information per goroutine so let's go with that. --- src/internal/task/task.go | 3 +++ src/runtime/panic.go | 5 +++++ src/runtime/scheduler.go | 11 +++++++++++ 3 files changed, 19 insertions(+) diff --git a/src/internal/task/task.go b/src/internal/task/task.go index 546f5ba117..58c02fe846 100644 --- a/src/internal/task/task.go +++ b/src/internal/task/task.go @@ -21,6 +21,9 @@ type Task struct { // state is the underlying running state of the task. state state + // This is needed for some crypto packages. + FipsIndicator uint8 + // DeferFrame stores a pointer to the (stack allocated) defer frame of the // goroutine that is used for the recover builtin. DeferFrame unsafe.Pointer diff --git a/src/runtime/panic.go b/src/runtime/panic.go index ec33a4469c..9ae1f982b9 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -226,3 +226,8 @@ func divideByZeroPanic() { func blockingPanic() { runtimePanicAt(returnAddress(0), "trying to do blocking operation in exported function") } + +//go:linkname fips_fatal crypto/internal/fips140.fatal +func fips_fatal(msg string) { + runtimePanic(msg) +} diff --git a/src/runtime/scheduler.go b/src/runtime/scheduler.go index 727c7f5f2c..40740da310 100644 --- a/src/runtime/scheduler.go +++ b/src/runtime/scheduler.go @@ -31,3 +31,14 @@ func scheduleLogChan(msg string, ch *channel, t *task.Task) { func Goexit() { panicOrGoexit(nil, panicGoexit) } + +//go:linkname fips_getIndicator crypto/internal/fips140.getIndicator +func fips_getIndicator() uint8 { + return task.Current().FipsIndicator +} + +//go:linkname fips_setIndicator crypto/internal/fips140.setIndicator +func fips_setIndicator(indicator uint8) { + // This indicator is stored per goroutine. + task.Current().FipsIndicator = indicator +} From 3476100dd08ad0a91ee7cde1710cb08e88f072d9 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 18 Feb 2025 14:21:44 +0100 Subject: [PATCH 380/444] syscall: add wasip1 RandomGet This function is needed starting with Go 1.24. --- src/syscall/syscall_libc_wasi.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/syscall/syscall_libc_wasi.go b/src/syscall/syscall_libc_wasi.go index bbf81cd059..583e7d8ef4 100644 --- a/src/syscall/syscall_libc_wasi.go +++ b/src/syscall/syscall_libc_wasi.go @@ -438,6 +438,13 @@ type RawSockaddrInet6 struct { // stub } +func RandomGet(b []byte) error { + if len(b) > 0 { + libc_arc4random_buf(unsafe.Pointer(&b[0]), uint(len(b))) + } + return nil +} + // This is a stub, it is not functional. func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno) @@ -483,3 +490,8 @@ func libc_fdclosedir(unsafe.Pointer) int32 // //export readdir func libc_readdir(unsafe.Pointer) *Dirent + +// void arc4random_buf(void *buf, size_t buflen); +// +//export arc4random_buf +func libc_arc4random_buf(buf unsafe.Pointer, buflen uint) From c2eaa495dfa561b6e3f2183dc3d3009388d9de39 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 24 Feb 2025 16:25:59 +0100 Subject: [PATCH 381/444] os: implement stub Chdir for non-OS systems --- src/os/file_other.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/os/file_other.go b/src/os/file_other.go index 75c0332817..e0705c78dc 100644 --- a/src/os/file_other.go +++ b/src/os/file_other.go @@ -42,6 +42,12 @@ func NewFile(fd uintptr, name string) *File { return &File{&file{handle: stdioFileHandle(fd), name: name}} } +// Chdir changes the current working directory to the named directory. +// If there is an error, it will be of type *PathError. +func Chdir(dir string) error { + return ErrNotImplemented +} + // Rename renames (moves) oldpath to newpath. // If newpath already exists and is not a directory, Rename replaces it. // OS-specific restrictions may apply when oldpath and newpath are in different directories. From cdea833b5c74aa79a01fe67f05e71ac5f9e63312 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 24 Feb 2025 15:42:29 +0100 Subject: [PATCH 382/444] os: add File.Chdir support We should really be using syscall.Fchdir here, but this is a fix to get Go 1.24 working. --- src/os/file.go | 11 +++++++++++ src/os/file_other.go | 4 ++++ src/os/file_unix.go | 16 ++++++++++++++++ src/os/file_windows.go | 4 ++++ 4 files changed, 35 insertions(+) diff --git a/src/os/file.go b/src/os/file.go index 0e20d1bf10..e7dd214b73 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -318,6 +318,17 @@ func (f *File) Chmod(mode FileMode) (err error) { return } +// Chdir changes the current working directory to the file, which must be a +// directory. If there is an error, it will be of type *PathError. +func (f *File) Chdir() (err error) { + if f.handle == nil { + err = ErrClosed + } else { + err = f.chdir() + } + return +} + // LinkError records an error during a link or symlink or rename system call and // the paths that caused it. type LinkError struct { diff --git a/src/os/file_other.go b/src/os/file_other.go index e0705c78dc..8e7d33e00e 100644 --- a/src/os/file_other.go +++ b/src/os/file_other.go @@ -159,3 +159,7 @@ func (f *File) Truncate(size int64) (err error) { func (f *File) chmod(mode FileMode) error { return ErrUnsupported } + +func (f *File) chdir() error { + return ErrNotImplemented +} diff --git a/src/os/file_unix.go b/src/os/file_unix.go index fd4d464f5b..9dc3a91e09 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -166,6 +166,22 @@ func (f *File) chmod(mode FileMode) error { return nil } +func (f *File) chdir() error { + if f.handle == nil { + return ErrClosed + } + + // TODO: use syscall.Fchdir instead + longName := fixLongPath(f.name) + e := ignoringEINTR(func() error { + return syscall.Chdir(longName) + }) + if e != nil { + return &PathError{Op: "chdir", Path: f.name, Err: e} + } + return nil +} + // ReadAt reads up to len(b) bytes from the File starting at the given absolute offset. // It returns the number of bytes read and any error encountered, possibly io.EOF. // At end of file, Pread returns 0, io.EOF. diff --git a/src/os/file_windows.go b/src/os/file_windows.go index 022381deed..50c5ee7a82 100644 --- a/src/os/file_windows.go +++ b/src/os/file_windows.go @@ -146,3 +146,7 @@ func isWindowsNulName(name string) bool { func (f *File) chmod(mode FileMode) error { return ErrNotImplemented } + +func (f *File) chdir() error { + return ErrNotImplemented +} From c180d6fe2fe46f159cdec4ef1da1e5c0a7c5dcd8 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 24 Feb 2025 15:43:41 +0100 Subject: [PATCH 383/444] testing: add Chdir This method was added in Go 1.24. --- src/testing/testing.go | 45 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/testing/testing.go b/src/testing/testing.go index 9058892d28..c4449cbb0a 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -17,6 +17,8 @@ import ( "io/fs" "math/rand" "os" + "path/filepath" + "runtime" "strconv" "strings" "time" @@ -390,6 +392,49 @@ func (c *common) Setenv(key, value string) { } } +// Chdir calls os.Chdir(dir) and uses Cleanup to restore the current +// working directory to its original value after the test. On Unix, it +// also sets PWD environment variable for the duration of the test. +// +// Because Chdir affects the whole process, it cannot be used +// in parallel tests or tests with parallel ancestors. +func (c *common) Chdir(dir string) { + // Note: function copied from the Go 1.24.0 source tree. + + oldwd, err := os.Open(".") + if err != nil { + c.Fatal(err) + } + if err := os.Chdir(dir); err != nil { + c.Fatal(err) + } + // On POSIX platforms, PWD represents “an absolute pathname of the + // current working directory.” Since we are changing the working + // directory, we should also set or update PWD to reflect that. + switch runtime.GOOS { + case "windows", "plan9": + // Windows and Plan 9 do not use the PWD variable. + default: + if !filepath.IsAbs(dir) { + dir, err = os.Getwd() + if err != nil { + c.Fatal(err) + } + } + c.Setenv("PWD", dir) + } + c.Cleanup(func() { + err := oldwd.Chdir() + oldwd.Close() + if err != nil { + // It's not safe to continue with tests if we can't + // get back to the original working directory. Since + // we are holding a dirfd, this is highly unlikely. + panic("testing.Chdir: " + err.Error()) + } + }) +} + // runCleanup is called at the end of the test. func (c *common) runCleanup() { for { From 66da29e89f0f40199af348471b5b4b1c4f150f55 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 24 Feb 2025 15:49:20 +0100 Subject: [PATCH 384/444] wasip2: add stubs to get internal/syscall/unix to work This fixes lots of broken tests in stdlib packages in Go 1.24. --- src/syscall/syscall_libc.go | 4 ++++ src/syscall/syscall_libc_wasi.go | 3 +++ 2 files changed, 7 insertions(+) diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index 0ef9784283..86c756383e 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -233,6 +233,10 @@ func (w WaitStatus) Continued() bool { return false } func (w WaitStatus) StopSignal() Signal { return 0 } func (w WaitStatus) TrapCause() int { return 0 } +// Purely here for compatibility. +type Rusage struct { +} + // since rusage is quite a big struct and we stub it out anyway no need to define it here func Wait4(pid int, wstatus *WaitStatus, options int, rusage uintptr) (wpid int, err error) { return 0, ENOSYS // TODO diff --git a/src/syscall/syscall_libc_wasi.go b/src/syscall/syscall_libc_wasi.go index 583e7d8ef4..479377877b 100644 --- a/src/syscall/syscall_libc_wasi.go +++ b/src/syscall/syscall_libc_wasi.go @@ -120,8 +120,11 @@ const ( SYS_FCNTL64 SYS_FSTATAT64 SYS_IOCTL + SYS_MKDIRAT SYS_OPENAT + SYS_READLINKAT SYS_UNLINKAT + SYS_WAITID PATH_MAX = 4096 ) From 38e3d55e6438af503b5f87fc35604b5cf962e323 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 12 Feb 2025 16:11:08 +0100 Subject: [PATCH 385/444] all: add Go 1.24 support --- .circleci/config.yml | 6 +++--- .github/workflows/build-macos.yml | 4 ++-- .github/workflows/linux.yml | 8 ++++---- .github/workflows/windows.yml | 8 ++++---- Dockerfile | 4 ++-- GNUmakefile | 14 ++++---------- builder/config.go | 4 ++-- builder/sizes_test.go | 2 +- compiler/alias.go | 11 ++++++----- src/internal/abi/type.go | 7 +++++++ src/reflect/value.go | 4 ++++ src/runtime/synctest.go | 15 +++++++++++++++ src/runtime/time.go | 13 +++++++++++++ 13 files changed, 67 insertions(+), 33 deletions(-) create mode 100644 src/internal/abi/type.go create mode 100644 src/runtime/synctest.go diff --git a/.circleci/config.yml b/.circleci/config.yml index e2069e3485..a3a052c28c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -109,9 +109,9 @@ jobs: # "make lint" fails before go 1.21 because internal/tools/go.mod specifies packages that require go 1.21 fmt-check: false resource_class: large - test-llvm19-go123: + test-llvm19-go124: docker: - - image: golang:1.23-bullseye + - image: golang:1.24-bullseye steps: - test-linux: llvm: "19" @@ -124,4 +124,4 @@ workflows: # least the smoke tests still pass. - test-llvm15-go119 # This tests LLVM 19 support when linking against system libraries. - - test-llvm19-go123 + - test-llvm19-go124 diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 07b0d307d0..25b5971783 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.23' + go-version: '1.24' cache: true - name: Restore LLVM source cache uses: actions/cache/restore@v4 @@ -143,7 +143,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.23' + go-version: '1.24' 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 c94b3582e3..cbb0a7c554 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.23-alpine + image: golang:1.24-alpine outputs: version: ${{ steps.version.outputs.version }} steps: @@ -146,7 +146,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.23' + go-version: '1.24' cache: true - name: Install wasmtime uses: bytecodealliance/actions/wasmtime/setup@v1 @@ -189,7 +189,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.23' + go-version: '1.24' cache: true - name: Install Node.js uses: actions/setup-node@v4 @@ -315,7 +315,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.23' + go-version: '1.24' 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 e1794ef13b..42365f59b7 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.23' + go-version: '1.24' cache: true - name: Restore cached LLVM source uses: actions/cache/restore@v4 @@ -156,7 +156,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.23' + go-version: '1.24' cache: true - name: Download TinyGo build uses: actions/download-artifact@v4 @@ -186,7 +186,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.23' + go-version: '1.24' cache: true - name: Download TinyGo build uses: actions/download-artifact@v4 @@ -222,7 +222,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.23' + go-version: '1.24' cache: true - name: Download TinyGo build uses: actions/download-artifact@v4 diff --git a/Dockerfile b/Dockerfile index 9a9effac2b..520ad7c9b5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # tinygo-llvm stage obtains the llvm source for TinyGo -FROM golang:1.23 AS tinygo-llvm +FROM golang:1.24 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.23 AS tinygo-compiler +FROM golang:1.24 AS tinygo-compiler # Copy tinygo build. COPY --from=tinygo-compiler-build /tinygo/build/release/tinygo /tinygo diff --git a/GNUmakefile b/GNUmakefile index 423a2a20e0..28031ffec5 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -309,11 +309,9 @@ TEST_PACKAGES_FAST = \ container/heap \ container/list \ container/ring \ - crypto/des \ crypto/ecdsa \ crypto/elliptic \ crypto/md5 \ - crypto/rc4 \ crypto/sha1 \ crypto/sha256 \ crypto/sha512 \ @@ -355,17 +353,11 @@ TEST_PACKAGES_FAST = \ unique \ $(nil) -# Assume this will go away before Go2, so only check minor version. -ifeq ($(filter $(shell $(GO) env GOVERSION | cut -f 2 -d.), 16 17 18), ) -TEST_PACKAGES_FAST += crypto/internal/nistec/fiat -else -TEST_PACKAGES_FAST += crypto/elliptic/internal/fiat -endif - # archive/zip requires os.ReadAt, which is not yet supported on windows # bytes requires mmap # compress/flate appears to hang on wasi # crypto/aes fails on wasi, needs panic()/recover() +# crypto/des fails on wasi, needs panic()/recover() # crypto/hmac fails on wasi, it exits with a "slice out of range" panic # debug/plan9obj requires os.ReadAt, which is not yet supported on windows # image requires recover(), which is not yet supported on wasi @@ -386,6 +378,7 @@ TEST_PACKAGES_LINUX := \ archive/zip \ compress/flate \ crypto/aes \ + crypto/des \ crypto/hmac \ debug/dwarf \ debug/plan9obj \ @@ -405,10 +398,11 @@ TEST_PACKAGES_LINUX := \ TEST_PACKAGES_DARWIN := $(TEST_PACKAGES_LINUX) +# os/user requires t.Skip() support TEST_PACKAGES_WINDOWS := \ compress/flate \ + crypto/des \ crypto/hmac \ - os/user \ strconv \ text/template/parse \ $(nil) diff --git a/builder/config.go b/builder/config.go index d1d0a2713b..b36b9333f3 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 = 23 + const minorMax = 24 // Check that we support this Go toolchain version. gorootMajor, gorootMinor, err := goenv.GetGorootVersion() @@ -36,7 +36,7 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) { if gorootMajor != 1 || gorootMinor < minorMin || gorootMinor > minorMax { // 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.19 through 1.23, got go%d.%d", gorootMajor, gorootMinor) + return nil, fmt.Errorf("requires go version 1.%d through 1.%d, got go%d.%d", minorMin, minorMax, gorootMajor, gorootMinor) } // Check that the Go toolchain version isn't too new, if we haven't been diff --git a/builder/sizes_test.go b/builder/sizes_test.go index 2b2b08fe6f..a96ce9e6f6 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -44,7 +44,7 @@ func TestBinarySize(t *testing.T) { // microcontrollers {"hifive1b", "examples/echo", 4560, 280, 0, 2268}, {"microbit", "examples/serial", 2916, 388, 8, 2272}, - {"wioterminal", "examples/pininterrupt", 7315, 1489, 116, 6912}, + {"wioterminal", "examples/pininterrupt", 7359, 1489, 116, 6912}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the diff --git a/compiler/alias.go b/compiler/alias.go index b0191a7a11..9d57a587e7 100644 --- a/compiler/alias.go +++ b/compiler/alias.go @@ -18,11 +18,12 @@ var stdlibAliases = map[string]string{ // crypto packages "crypto/ed25519/internal/edwards25519/field.feMul": "crypto/ed25519/internal/edwards25519/field.feMulGeneric", "crypto/internal/edwards25519/field.feSquare": "crypto/ed25519/internal/edwards25519/field.feSquareGeneric", - "crypto/md5.block": "crypto/md5.blockGeneric", - "crypto/sha1.block": "crypto/sha1.blockGeneric", - "crypto/sha1.blockAMD64": "crypto/sha1.blockGeneric", - "crypto/sha256.block": "crypto/sha256.blockGeneric", - "crypto/sha512.blockAMD64": "crypto/sha512.blockGeneric", + "crypto/md5.block": "crypto/md5.blockGeneric", + "crypto/sha1.block": "crypto/sha1.blockGeneric", + "crypto/sha1.blockAMD64": "crypto/sha1.blockGeneric", + "crypto/sha256.block": "crypto/sha256.blockGeneric", + "crypto/sha512.blockAMD64": "crypto/sha512.blockGeneric", + "internal/chacha8rand.block": "internal/chacha8rand.block_generic", // AES "crypto/aes.decryptBlockAsm": "crypto/aes.decryptBlock", diff --git a/src/internal/abi/type.go b/src/internal/abi/type.go new file mode 100644 index 0000000000..d1853e0f3d --- /dev/null +++ b/src/internal/abi/type.go @@ -0,0 +1,7 @@ +package abi + +type Type struct { + // Intentionally left empty. TinyGo uses a different way to represent types, + // so this is unimplementable. The type definition here is purely for + // compatibility. +} diff --git a/src/reflect/value.go b/src/reflect/value.go index 69b57fd1cc..7ac71aa184 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -271,6 +271,10 @@ func (v Value) Comparable() bool { } } +func (v Value) Equal(u Value) bool { + panic("unimplemented: reflect.Value.Equal") +} + func (v Value) Addr() Value { if !v.CanAddr() { panic("reflect.Value.Addr of unaddressable value") diff --git a/src/runtime/synctest.go b/src/runtime/synctest.go new file mode 100644 index 0000000000..fa11c991fc --- /dev/null +++ b/src/runtime/synctest.go @@ -0,0 +1,15 @@ +package runtime + +// Dummy implementation of synctest functions (we don't support synctest at the +// moment). + +//go:linkname synctest_acquire internal/synctest.acquire +func synctest_acquire() any { + // Dummy: we don't support synctest. + return nil +} + +//go:linkname synctest_release internal/synctest.release +func synctest_release(sg any) { + // Dummy: we don't support synctest. +} diff --git a/src/runtime/time.go b/src/runtime/time.go index 50bf61cf3c..3935b4486e 100644 --- a/src/runtime/time.go +++ b/src/runtime/time.go @@ -1,5 +1,18 @@ package runtime +//go:linkname time_runtimeNano time.runtimeNano +func time_runtimeNano() int64 { + // Note: we're ignoring sync groups here (package testing/synctest). + // See: https://github.com/golang/go/issues/67434 + return nanotime() +} + +//go:linkname time_runtimeNow time.runtimeNow +func time_runtimeNow() (sec int64, nsec int32, mono int64) { + // Also ignoring the sync group here, like time_runtimeNano above. + return now() +} + // timerNode is an element in a linked list of timers. type timerNode struct { next *timerNode From 154565981749bd5aa0c8363147004bc67932af8d Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 26 Feb 2025 13:35:58 +0100 Subject: [PATCH 386/444] internal/syscall/unix: use our own version of this package The upstream one assumes it's running on a Unix system (which makes sense), but this package is also used on baremetal. So replace it on systems that need a replaced syscall package. --- loader/goroot.go | 2 ++ src/internal/syscall/unix/constants.go | 7 +++++++ src/internal/syscall/unix/eaccess.go | 10 ++++++++++ src/internal/syscall/unix/getrandom.go | 12 ++++++++++++ 4 files changed, 31 insertions(+) create mode 100644 src/internal/syscall/unix/constants.go create mode 100644 src/internal/syscall/unix/eaccess.go create mode 100644 src/internal/syscall/unix/getrandom.go diff --git a/loader/goroot.go b/loader/goroot.go index 5442df9b5d..00a7124d80 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -269,6 +269,8 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { if needsSyscallPackage { paths["syscall/"] = true // include syscall/js + paths["internal/syscall/"] = true + paths["internal/syscall/unix/"] = false } return paths } diff --git a/src/internal/syscall/unix/constants.go b/src/internal/syscall/unix/constants.go new file mode 100644 index 0000000000..46fc1d0598 --- /dev/null +++ b/src/internal/syscall/unix/constants.go @@ -0,0 +1,7 @@ +package unix + +const ( + R_OK = 0x4 + W_OK = 0x2 + X_OK = 0x1 +) diff --git a/src/internal/syscall/unix/eaccess.go b/src/internal/syscall/unix/eaccess.go new file mode 100644 index 0000000000..53f105f065 --- /dev/null +++ b/src/internal/syscall/unix/eaccess.go @@ -0,0 +1,10 @@ +package unix + +import "syscall" + +func Eaccess(path string, mode uint32) error { + // We don't support this syscall on baremetal or wasm. + // Callers are generally able to deal with this since unix.Eaccess also + // isn't available on Android. + return syscall.ENOSYS +} diff --git a/src/internal/syscall/unix/getrandom.go b/src/internal/syscall/unix/getrandom.go new file mode 100644 index 0000000000..7ffab77e69 --- /dev/null +++ b/src/internal/syscall/unix/getrandom.go @@ -0,0 +1,12 @@ +package unix + +type GetRandomFlag uintptr + +const ( + GRND_NONBLOCK GetRandomFlag = 0x0001 + GRND_RANDOM GetRandomFlag = 0x0002 +) + +func GetRandom(p []byte, flags GetRandomFlag) (n int, err error) { + panic("todo: unix.GetRandom") +} From 056394e24b720fe4891b3540fc593a21b96e3750 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 27 Feb 2025 10:13:23 +0100 Subject: [PATCH 387/444] make: add test-corpus-wasip2 I wanted to run the test corpus with WASIp2 so I added these lines. --- GNUmakefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/GNUmakefile b/GNUmakefile index 28031ffec5..a43b14b5c3 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -491,6 +491,8 @@ test-corpus-fast: CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus -short . -corpus=testdata/corpus.yaml test-corpus-wasi: wasi-libc CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasip1 +test-corpus-wasip2: wasi-libc + CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasip2 tinygo-baremetal: # Regression tests that run on a baremetal target and don't fit in either main_test.go or smoketest. From b0aef96340bed9dcaefcb2cf8ae2d2ce5718bd72 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Fri, 21 Feb 2025 10:33:54 +0100 Subject: [PATCH 388/444] fix: only infer target for wasm when GOOS and GOARCH are set correctly, not just based on file extension Signed-off-by: deadprogram --- builder/builder_test.go | 1 - compileopts/target.go | 31 +++---------------------------- main.go | 20 ++++++++++++++++++-- 3 files changed, 21 insertions(+), 31 deletions(-) diff --git a/builder/builder_test.go b/builder/builder_test.go index 1d4584c347..ccccef30ba 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -69,7 +69,6 @@ func TestClangAttributes(t *testing.T) { {GOOS: "darwin", GOARCH: "arm64"}, {GOOS: "windows", GOARCH: "amd64"}, {GOOS: "windows", GOARCH: "arm64"}, - {GOOS: "wasip1", GOARCH: "wasm"}, } { name := "GOOS=" + options.GOOS + ",GOARCH=" + options.GOARCH if options.GOARCH == "arm" { diff --git a/compileopts/target.go b/compileopts/target.go index 7893e58290..64cbc2daa9 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -356,17 +356,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) { return nil, fmt.Errorf("invalid GOMIPS=%s: must be hardfloat or softfloat", options.GOMIPS) } case "wasm": - llvmarch = "wasm32" - spec.CPU = "generic" - spec.Features = "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" - spec.BuildTags = append(spec.BuildTags, "tinygo.wasm") - spec.CFlags = append(spec.CFlags, - "-mbulk-memory", - "-mnontrapping-fptoint", - "-mno-multivalue", - "-mno-reference-types", - "-msign-ext", - ) + return nil, fmt.Errorf("GOARCH=wasm but GOOS is unset. Please set GOOS to wasm, wasip1, or wasip2.") default: return nil, fmt.Errorf("unknown GOARCH=%s", options.GOARCH) } @@ -446,23 +436,8 @@ func defaultTarget(options *Options) (*TargetSpec, error) { "--no-insert-timestamp", "--no-dynamicbase", ) - case "wasip1": - spec.GC = "" // use default GC - spec.Scheduler = "asyncify" - spec.Linker = "wasm-ld" - spec.RTLib = "compiler-rt" - spec.Libc = "wasi-libc" - spec.DefaultStackSize = 1024 * 64 // 64kB - spec.LDFlags = append(spec.LDFlags, - "--stack-first", - "--no-demangle", - ) - spec.Emulator = "wasmtime run --dir={tmpDir}::/tmp {}" - spec.ExtraFiles = append(spec.ExtraFiles, - "src/runtime/asm_tinygowasm.S", - "src/internal/task/task_asyncify_wasm.S", - ) - llvmos = "wasi" + case "wasm", "wasip1", "wasip2": + return nil, fmt.Errorf("GOOS=%s but GOARCH is unset. Please set GOARCH to wasm", options.GOOS) default: return nil, fmt.Errorf("unknown GOOS=%s", options.GOOS) } diff --git a/main.go b/main.go index 1f2fc95b14..d4562fc4f6 100644 --- a/main.go +++ b/main.go @@ -1684,8 +1684,24 @@ func main() { usage(command) os.Exit(1) } - if options.Target == "" && filepath.Ext(outpath) == ".wasm" { - options.Target = "wasm" + if options.Target == "" { + switch { + case options.GOARCH == "wasm": + switch options.GOOS { + case "js": + options.Target = "wasm" + case "wasip1": + options.Target = "wasip1" + case "wasip2": + options.Target = "wasip2" + default: + fmt.Fprintln(os.Stderr, "GOARCH=wasm but GOOS is not set correctly. Please set GOOS to wasm, wasip1, or wasip2.") + os.Exit(1) + } + case filepath.Ext(outpath) == ".wasm": + fmt.Fprintln(os.Stderr, "you appear to want to build a wasm file, but have not specified either a target flag, or the GOARCH/GOOS to use.") + os.Exit(1) + } } err := Build(pkgName, outpath, options) From 209754493b4a0b6069b887007db129c8374929e7 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Fri, 21 Feb 2025 10:46:46 +0100 Subject: [PATCH 389/444] make: use GOOS and GOARCH for building wasm simulated boards Signed-off-by: deadprogram --- GNUmakefile | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index a43b14b5c3..f740374f1c 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -569,21 +569,21 @@ smoketest: testchdir @$(MD5SUM) test.hex # test simulated boards on play.tinygo.org ifneq ($(WASM), 0) - $(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1 + GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1 @$(MD5SUM) test.wasm - $(TINYGO) build -size short -o test.wasm -tags=hifive1b examples/blinky1 + GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=hifive1b examples/blinky1 @$(MD5SUM) test.wasm - $(TINYGO) build -size short -o test.wasm -tags=reelboard examples/blinky1 + GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=reelboard examples/blinky1 @$(MD5SUM) test.wasm - $(TINYGO) build -size short -o test.wasm -tags=microbit examples/microbit-blink + GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=microbit examples/microbit-blink @$(MD5SUM) test.wasm - $(TINYGO) build -size short -o test.wasm -tags=circuitplay_express examples/blinky1 + GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=circuitplay_express examples/blinky1 @$(MD5SUM) test.wasm - $(TINYGO) build -size short -o test.wasm -tags=circuitplay_bluefruit examples/blinky1 + GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=circuitplay_bluefruit examples/blinky1 @$(MD5SUM) test.wasm - $(TINYGO) build -size short -o test.wasm -tags=mch2022 examples/machinetest + GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=mch2022 examples/machinetest @$(MD5SUM) test.wasm - $(TINYGO) build -size short -o test.wasm -tags=gopher_badge examples/blinky1 + GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=gopher_badge examples/blinky1 @$(MD5SUM) test.wasm endif # test all targets/boards From 81da7bb4c1f3771122ea0a33002dbe40074fe739 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sun, 16 Feb 2025 19:04:09 +0100 Subject: [PATCH 390/444] targets: add target for pico2-w board Signed-off-by: deadprogram --- targets/pico2-w.json | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 targets/pico2-w.json diff --git a/targets/pico2-w.json b/targets/pico2-w.json new file mode 100644 index 0000000000..0f1349645d --- /dev/null +++ b/targets/pico2-w.json @@ -0,0 +1,4 @@ +{ + "inherits": ["pico2"], + "build-tags": ["pico2-w", "cyw43439"] +} From 42f5e55ed51c45cc51d22ca8dc685d203ce62f43 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Tue, 25 Feb 2025 20:04:23 +0100 Subject: [PATCH 391/444] docs: small corrections for README regarding wasm Signed-off-by: deadprogram --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e03b749bb9..518dcdad18 100644 --- a/README.md +++ b/README.md @@ -48,20 +48,22 @@ Here is a small TinyGo program for use by a WASI host application: ```go package main -//go:wasm-module yourmodulename -//export add +//go:wasmexport add func add(x, y uint32) uint32 { return x + y } +``` + +This compiles the above TinyGo program for use on any WASI Preview 1 runtime: -// main is required for the `wasip1` target, even if it isn't used. -func main() {} +```shell +tinygo build -buildmode=c-shared -o add.wasm -target=wasip1 add.go ``` -This compiles the above TinyGo program for use on any WASI runtime: +You can also use the same syntax as Go 1.24+: ```shell -tinygo build -o main.wasm -target=wasip1 main.go +GOARCH=wasip1 GOOS=wasm tinygo build -buildmode=c-shared -o add.wasm add.go ``` ## Installation From 31c4bc11510f49ea1a7bf01e65bb1a906d20cdd0 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Tue, 25 Feb 2025 20:04:49 +0100 Subject: [PATCH 392/444] license: update for 2025 Signed-off-by: deadprogram --- LICENSE | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 92f68666c5..4d0fde7595 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ -Copyright (c) 2018-2023 The TinyGo Authors. All rights reserved. +Copyright (c) 2018-2025 The TinyGo Authors. All rights reserved. TinyGo includes portions of the Go standard library. -Copyright (c) 2009-2023 The Go Authors. All rights reserved. +Copyright (c) 2009-2024 The Go Authors. All rights reserved. TinyGo includes portions of LLVM, which is under the Apache License v2.0 with LLVM Exceptions. See https://llvm.org/LICENSE.txt for license information. From 92c130c7be259c13bb1fd1a58b6c9f7b717323da Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Fri, 28 Feb 2025 23:05:27 +0100 Subject: [PATCH 393/444] machine: compute rp2 clock dividers from crystal and target frequency (#4747) Follow-up to #4728 which implemented the algorithm for finding the dividers. The calculation is computed at compile time by interp, as verified by building example/blinky1 for -target pico. --- src/machine/machine_rp2_clocks.go | 16 ++++++++++++++-- src/machine/machine_rp2_pll.go | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/machine/machine_rp2_clocks.go b/src/machine/machine_rp2_clocks.go index 9dd2774b9c..520a8d333c 100644 --- a/src/machine/machine_rp2_clocks.go +++ b/src/machine/machine_rp2_clocks.go @@ -137,7 +137,19 @@ func (clk *clock) configure(src, auxsrc, srcFreq, freq uint32) { } -const pllsysFB, pllsysPD1, pllsysPD2 uint32 = 125, 6, 2 // RP2040 running 125MHz with 1500MHz VCO. +var pllsysFB, pllsysPD1, pllsysPD2 uint32 + +// Compute clock dividers. +// +// Note that the entire init function is computed at compile time +// by interp. +func init() { + fb, _, pd1, pd2, err := pllSearch{LockRefDiv: 1}.CalcDivs(xoscFreq*MHz, uint64(CPUFrequency()), MHz) + if err != nil { + panic(err) + } + pllsysFB, pllsysPD1, pllsysPD2 = uint32(fb), uint32(pd1), uint32(pd2) +} // init initializes the clock hardware. // @@ -165,7 +177,7 @@ func (clks *clocksType) init() { // REF FBDIV VCO POSTDIV // pllSys: 12 / 1 = 12MHz * 125 = 1500MHZ / 6 / 2 = 125MHz // pllUSB: 12 / 1 = 12MHz * 40 = 480 MHz / 5 / 2 = 48MHz - pllSys.init(1, uint32(pllsysFB), uint32(pllsysPD1), uint32(pllsysPD2)) + pllSys.init(1, pllsysFB, pllsysPD1, pllsysPD2) pllUSB.init(1, 40, 5, 2) // Configure clocks diff --git a/src/machine/machine_rp2_pll.go b/src/machine/machine_rp2_pll.go index dcc654ab06..d5760842ba 100644 --- a/src/machine/machine_rp2_pll.go +++ b/src/machine/machine_rp2_pll.go @@ -111,7 +111,7 @@ var errVCOOverflow = errors.New("VCO calculation overflow; use lower MHz") // // Example for 12MHz crystal and RP2350: // -// fbdiv, refdiv, pd1, pd2, _ := pllSearch{LockRefDiv:1}.CalcDivs(12*MHz, 133*MHz, MHz) +// fbdiv, refdiv, pd1, pd2, _ := pllSearch{LockRefDiv:1}.CalcDivs(12*MHz, 150*MHz, MHz) type pllSearch struct { LowerVCO bool LockRefDiv uint8 From b95effbdd7996694793b57e624cc56bb4131ffdb Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sat, 1 Mar 2025 14:09:53 +0100 Subject: [PATCH 394/444] machine: bump rp2350 CPUFrequency to 150 MHz (#4766) Leave rp2040 speed bump to 200 MHz for a future change, because it requires bumping the core voltage as well[0]. [0] https://github.com/raspberrypi/pico-sdk/releases/tag/2.1.1 --- src/machine/machine_rp2_2040.go | 1 + src/machine/machine_rp2_2350.go | 1 + src/machine/machine_rp2_clocks.go | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/machine/machine_rp2_2040.go b/src/machine/machine_rp2_2040.go index 0691b8d77f..e7ae38c069 100644 --- a/src/machine/machine_rp2_2040.go +++ b/src/machine/machine_rp2_2040.go @@ -9,6 +9,7 @@ import ( ) const ( + cpuFreq = 125 * MHz _NUMBANK0_GPIOS = 30 _NUMBANK0_IRQS = 4 _NUMIRQ = 32 diff --git a/src/machine/machine_rp2_2350.go b/src/machine/machine_rp2_2350.go index 5ef5098c6e..d20bb2d78d 100644 --- a/src/machine/machine_rp2_2350.go +++ b/src/machine/machine_rp2_2350.go @@ -9,6 +9,7 @@ import ( ) const ( + cpuFreq = 150 * MHz _NUMBANK0_GPIOS = 48 _NUMBANK0_IRQS = 6 rp2350ExtraReg = 1 diff --git a/src/machine/machine_rp2_clocks.go b/src/machine/machine_rp2_clocks.go index 520a8d333c..d6059896ee 100644 --- a/src/machine/machine_rp2_clocks.go +++ b/src/machine/machine_rp2_clocks.go @@ -10,7 +10,7 @@ import ( ) func CPUFrequency() uint32 { - return 125 * MHz + return cpuFreq } // clockIndex identifies a hardware clock From 64d8a043084cb9a56763192000e40ddc5dce733f Mon Sep 17 00:00:00 2001 From: Egawa Takashi Date: Sat, 1 Mar 2025 02:17:06 +0900 Subject: [PATCH 395/444] convert offset as signed int into unsigned int in syscall/js.stringVal in wasm_exec.js --- targets/wasm_exec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/targets/wasm_exec.js b/targets/wasm_exec.js index e61fa52ff7..53ea75fd42 100644 --- a/targets/wasm_exec.js +++ b/targets/wasm_exec.js @@ -321,6 +321,7 @@ // func stringVal(value string) ref "syscall/js.stringVal": (value_ptr, value_len) => { + value_ptr >>>= 0; const s = loadString(value_ptr, value_len); return boxValue(s); }, From 20fc814635fd53454d37bfde620f1f7fe51cdb99 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sun, 2 Mar 2025 12:38:29 +0100 Subject: [PATCH 396/444] machine: replace hard-coded cpu frequencies on rp2xxx (#4767) Missed from the earlier change that bumped the rp2350 frequency. --- src/machine/machine_rp2_clocks.go | 14 +++++++------- src/machine/machine_rp2_spi.go | 6 +++--- src/machine/machine_rp2_uart.go | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/machine/machine_rp2_clocks.go b/src/machine/machine_rp2_clocks.go index d6059896ee..061f3dfe44 100644 --- a/src/machine/machine_rp2_clocks.go +++ b/src/machine/machine_rp2_clocks.go @@ -144,7 +144,7 @@ var pllsysFB, pllsysPD1, pllsysPD2 uint32 // Note that the entire init function is computed at compile time // by interp. func init() { - fb, _, pd1, pd2, err := pllSearch{LockRefDiv: 1}.CalcDivs(xoscFreq*MHz, uint64(CPUFrequency()), MHz) + fb, _, pd1, pd2, err := pllSearch{LockRefDiv: 1}.CalcDivs(xoscFreq*MHz, cpuFreq, MHz) if err != nil { panic(err) } @@ -185,15 +185,15 @@ func (clks *clocksType) init() { cref := clks.clock(clkRef) cref.configure(rp.CLOCKS_CLK_REF_CTRL_SRC_XOSC_CLKSRC, 0, // No aux mux - 12*MHz, - 12*MHz) + xoscFreq, + xoscFreq) // clkSys = pllSys (125MHz) / 1 = 125MHz csys := clks.clock(clkSys) csys.configure(rp.CLOCKS_CLK_SYS_CTRL_SRC_CLKSRC_CLK_SYS_AUX, rp.CLOCKS_CLK_SYS_CTRL_AUXSRC_CLKSRC_PLL_SYS, - 125*MHz, - 125*MHz) + cpuFreq, + cpuFreq) // clkUSB = pllUSB (48MHz) / 1 = 48MHz cusb := clks.clock(clkUSB) @@ -217,8 +217,8 @@ func (clks *clocksType) init() { cperi := clks.clock(clkPeri) cperi.configure(0, rp.CLOCKS_CLK_PERI_CTRL_AUXSRC_CLK_SYS, - 125*MHz, - 125*MHz) + cpuFreq, + cpuFreq) clks.initTicks() } diff --git a/src/machine/machine_rp2_spi.go b/src/machine/machine_rp2_spi.go index faab9839af..a589cb648c 100644 --- a/src/machine/machine_rp2_spi.go +++ b/src/machine/machine_rp2_spi.go @@ -104,13 +104,13 @@ func (spi SPI) Transfer(w byte) (byte, error) { } func (spi SPI) SetBaudRate(br uint32) error { - const freqin uint32 = 125 * MHz const maxBaud uint32 = 66.5 * MHz // max output frequency is 66.5MHz on rp2040. see Note page 527. // Find smallest prescale value which puts output frequency in range of // post-divide. Prescale is an even number from 2 to 254 inclusive. var prescale, postdiv uint32 + freq := CPUFrequency() for prescale = 2; prescale < 255; prescale += 2 { - if freqin < (prescale+2)*256*br { + if freq < (prescale+2)*256*br { break } } @@ -120,7 +120,7 @@ func (spi SPI) SetBaudRate(br uint32) error { // Find largest post-divide which makes output <= baudrate. Post-divide is // an integer in the range 1 to 256 inclusive. for postdiv = 256; postdiv > 1; postdiv-- { - if freqin/(prescale*(postdiv-1)) > br { + if freq/(prescale*(postdiv-1)) > br { break } } diff --git a/src/machine/machine_rp2_uart.go b/src/machine/machine_rp2_uart.go index c984d41424..77aa26e54c 100644 --- a/src/machine/machine_rp2_uart.go +++ b/src/machine/machine_rp2_uart.go @@ -75,7 +75,7 @@ func (uart *UART) Configure(config UARTConfig) error { // SetBaudRate sets the baudrate to be used for the UART. func (uart *UART) SetBaudRate(br uint32) { - div := 8 * 125 * MHz / br + div := 8 * CPUFrequency() / br ibrd := div >> 7 var fbrd uint32 From 9a1cde40a8bce89f3d72a651813f72bf9147e30e Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sun, 2 Mar 2025 15:26:39 -0800 Subject: [PATCH 397/444] src/reflect: implement Value.Equal Implementation copied from Go. --- src/reflect/value.go | 76 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/src/reflect/value.go b/src/reflect/value.go index 7ac71aa184..ac47e05f03 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -271,8 +271,82 @@ func (v Value) Comparable() bool { } } +// Equal reports true if v is equal to u. +// For two invalid values, Equal will report true. +// For an interface value, Equal will compare the value within the interface. +// Otherwise, If the values have different types, Equal will report false. +// Otherwise, for arrays and structs Equal will compare each element in order, +// and report false if it finds non-equal elements. +// During all comparisons, if values of the same type are compared, +// and the type is not comparable, Equal will panic. +// +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. func (v Value) Equal(u Value) bool { - panic("unimplemented: reflect.Value.Equal") + if v.Kind() == Interface { + v = v.Elem() + } + if u.Kind() == Interface { + u = u.Elem() + } + + if !v.IsValid() || !u.IsValid() { + return v.IsValid() == u.IsValid() + } + + if v.Kind() != u.Kind() || v.Type() != u.Type() { + return false + } + + // Handle each Kind directly rather than calling valueInterface + // to avoid allocating. + switch v.Kind() { + default: + panic("reflect.Value.Equal: invalid Kind") + case Bool: + return v.Bool() == u.Bool() + case Int, Int8, Int16, Int32, Int64: + return v.Int() == u.Int() + case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: + return v.Uint() == u.Uint() + case Float32, Float64: + return v.Float() == u.Float() + case Complex64, Complex128: + return v.Complex() == u.Complex() + case String: + return v.String() == u.String() + case Chan, Pointer, UnsafePointer: + return v.Pointer() == u.Pointer() + case Array: + // u and v have the same type so they have the same length + vl := v.Len() + if vl == 0 { + // panic on [0]func() + if !v.Type().Elem().Comparable() { + break + } + return true + } + for i := 0; i < vl; i++ { + if !v.Index(i).Equal(u.Index(i)) { + return false + } + } + return true + case Struct: + // u and v have the same type so they have the same fields + nf := v.NumField() + for i := 0; i < nf; i++ { + if !v.Field(i).Equal(u.Field(i)) { + return false + } + } + return true + case Func, Map, Slice: + break + } + panic("reflect.Value.Equal: values of type " + v.Type().String() + " are not comparable") } func (v Value) Addr() Value { From 47c0debe4be1148533253e10aa9078adee407722 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sun, 2 Mar 2025 21:42:14 +0100 Subject: [PATCH 398/444] docs: update CHANGELOG for 0.36.0 release Signed-off-by: deadprogram --- CHANGELOG.md | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f47bcea91..4415de438e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,72 @@ +0.36.0 +--- +* **general** + - add initial Go 1.24 support + - add support for LLVM 19 + - update license for 2025 + - make small corrections for README regarding wasm + - use GOOS and GOARCH for building wasm simulated boards + - only infer target for wasm when GOOS and GOARCH are set correctly, not just based on file extension + - add test-corpus-wasip2 + - use older image for cross-compiling builds + - update Linux builds to run on ubuntu-latest since 20.04 is being retired + - ensure build output directory is created + - add NoSandbox flag to chrome headless that is run during WASM tests, since this is now required for Ubuntu 23+ and we are using Ubuntu 24+ when running Github Actions + - update wasmtime used for CI to 29.0.1 to fix issue with install during CI tests + - update to use `Get-CimInstance` as `wmic` is being deprecated on WIndows + - remove unnecessary executable permissions + - `goenv`: update to new v0.36.0 development version +* **compiler** + - `builder`: fix parsing of external ld.lld error messages + - `cgo`: mangle identifier names + - `interp`: correctly mark functions as modifying memory + - add buildmode=wasi-legacy to support existing base of users who expected the older behavior for wasi modules to not return an exit code as if they were reactors +* **standard library** + - `crypto/tls`: add Dialer.DialContext() to fix websocket client + - `crypto/tls`: add VersionTLS constants and VersionName(version uint16) method that turns it into a string, copied from big go + - `internal/syscall/unix`: use our own version of this package + - `machine`: replace hard-coded cpu frequencies on rp2xxx + - `machine`: bump rp2350 CPUFrequency to 150 MHz + - `machine`: compute rp2 clock dividers from crystal and target frequency + - `machine`: remove bytes package dependency in flash code + - `machine/usb/descriptor`: avoid bytes package + - `net`: update to latest submodule with httptest subpackage and ResolveIPAddress implementation + - `os`: add File.Chdir support + - `os`: implement stub Chdir for non-OS systems + - `os/file`: add file.Chmod + - `reflect`: implement Value.Equal + - `runtime`: add FIPS helper functions + - `runtime`: manually initialize xorshift state + - `sync`: move Mutex to internal/task + - `syscall`: add wasip1 RandomGet + - `testing`: add Chdir + - `wasip2`: add stubs to get internal/syscall/unix to work +* **fixes** + - correctly handle calls for GetRNG() when being made from nrf devices with SoftDevice enabled + - fix stm32f103 ADC + - `wasm`: correctly handle id lookup for finalizeRef call + - `wasm`: avoid total failure on wasm finalizer call + - `wasm`: convert offset as signed int into unsigned int in syscall/js.stringVal in wasm_exec.js +* **targets** + - rp2350: add pll generalized solution; fix ADC handles; pwm period fix + - rp2350: extending support to include the rp2350b + - rp2350: cleanup: unexport internal USB and clock package variable, consts and types + - nrf: make ADC resolution changeable + - turn on GC for TKey1 device, since it does in fact work + - match Pico2 stack size to Pico +* **boards** + - add support for Pimoroni Pico Plus2 + - add target for pico2-w board + - add comboat_fw tag for elecrow W5 boards with Combo-AT Wifi firmware + - add support for Elecrow Pico rp2350 W5 boards + - add support for Elecrow Pico rp2040 W5 boards + - add support for NRF51 HW-651 + - add support for esp32c3-supermini + - add support for waveshare-rp2040-tiny +* **examples** + - add naive debouncing for pininterrupt example + + 0.35.0 --- * **general** From 8c54e3dd885824638015f319907f75c01570cdad Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sun, 2 Mar 2025 21:46:53 +0100 Subject: [PATCH 399/444] release: update version to 0.36.0 Signed-off-by: deadprogram --- goenv/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/goenv/version.go b/goenv/version.go index 5592866a9c..f2695d275d 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -10,7 +10,7 @@ import ( // Version of TinyGo. // Update this value before release of new version of software. -const version = "0.36.0-dev" +const version = "0.36.0" // Return TinyGo version, either in the form 0.30.0 or as a development version // (like 0.30.0-dev-abcd012). From 7d6d93f7aa5554db353385d9a3c2b3710cddb14f Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Tue, 4 Mar 2025 12:57:59 +0100 Subject: [PATCH 400/444] machine: bump rp2040 to 200MHz (#4768) * machine: add support for core voltage adjustments to rp2040 In preparation for bumping the core frequency of the rp2040, this change implements the required core voltage adjustment logic. * machine: bump rp2040 to 200MHz --- src/machine/machine_rp2_2040.go | 15 ++++++++++++++- src/machine/machine_rp2_2350.go | 4 ++++ src/machine/machine_rp2_clocks.go | 12 ++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/machine/machine_rp2_2040.go b/src/machine/machine_rp2_2040.go index e7ae38c069..9cdb3a072e 100644 --- a/src/machine/machine_rp2_2040.go +++ b/src/machine/machine_rp2_2040.go @@ -9,7 +9,7 @@ import ( ) const ( - cpuFreq = 125 * MHz + cpuFreq = 200 * MHz _NUMBANK0_GPIOS = 30 _NUMBANK0_IRQS = 4 _NUMIRQ = 32 @@ -208,3 +208,16 @@ func (clks *clocksType) initTicks() {} // No ticks on RP2040 func (wd *watchdogImpl) startTick(cycles uint32) { rp.WATCHDOG.TICK.Set(cycles | rp.WATCHDOG_TICK_ENABLE) } + +func adjustCoreVoltage() bool { + if cpuFreq <= 133*MHz { + return false + } + // The rp2040 is certified to run at 200MHz with the + // core voltage set to 1150mV. + const targetVoltage = 1150 + // 0b0101 maps to 800mV and each step is 50mV. + const vreg = 0b0101 + (targetVoltage-800)/50 + rp.VREG_AND_CHIP_RESET.SetVREG_VSEL(vreg) + return true +} diff --git a/src/machine/machine_rp2_2350.go b/src/machine/machine_rp2_2350.go index d20bb2d78d..d259e750d8 100644 --- a/src/machine/machine_rp2_2350.go +++ b/src/machine/machine_rp2_2350.go @@ -222,3 +222,7 @@ func EnterBootloader() { func (wd *watchdogImpl) startTick(cycles uint32) { rp.TICKS.WATCHDOG_CTRL.SetBits(1) } + +func adjustCoreVoltage() bool { + return false +} diff --git a/src/machine/machine_rp2_clocks.go b/src/machine/machine_rp2_clocks.go index 061f3dfe44..dafebbe5d3 100644 --- a/src/machine/machine_rp2_clocks.go +++ b/src/machine/machine_rp2_clocks.go @@ -42,6 +42,10 @@ type clock struct { cix clockIndex } +// The delay in seconds for core voltage adjustments to +// settle. Taken from the Pico SDK. +const _VREG_VOLTAGE_AUTO_ADJUST_DELAY = 1 / 1e3 + // clock returns the clock identified by cix. func (clks *clocksType) clock(cix clockIndex) clock { return clock{ @@ -188,6 +192,14 @@ func (clks *clocksType) init() { xoscFreq, xoscFreq) + if adjustCoreVoltage() { + // Wait for the voltage to settle. + const cycles = _VREG_VOLTAGE_AUTO_ADJUST_DELAY * xoscFreq * MHz + for i := 0; i < cycles; i++ { + arm.Asm("nop") + } + } + // clkSys = pllSys (125MHz) / 1 = 125MHz csys := clks.clock(clkSys) csys.configure(rp.CLOCKS_CLK_SYS_CTRL_SRC_CLKSRC_CLK_SYS_AUX, From e49663809bc0ccf4516029c50de9ac2f56b2aa21 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 7 Mar 2025 10:18:18 +0100 Subject: [PATCH 401/444] machine: fix RP2040 Pico board on the playground Right now it doesn't compile, with errors like the following: # machine /app/tinygo/src/machine/board_pico.go:7:13: undefined: GPIO0 /app/tinygo/src/machine/board_pico.go:8:13: undefined: GPIO1 /app/tinygo/src/machine/board_pico.go:9:13: undefined: GPIO2 /app/tinygo/src/machine/board_pico.go:10:13: undefined: GPIO3 [...etc...] This patch should fix that. --- GNUmakefile | 2 ++ src/machine/machine_rp2_pins.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/GNUmakefile b/GNUmakefile index f740374f1c..45bca5d8a9 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -585,6 +585,8 @@ ifneq ($(WASM), 0) @$(MD5SUM) test.wasm GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=gopher_badge examples/blinky1 @$(MD5SUM) test.wasm + GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=pico examples/blinky1 + @$(MD5SUM) test.wasm endif # test all targets/boards $(TINYGO) build -size short -o test.hex -target=pca10040-s132v6 examples/blinky1 diff --git a/src/machine/machine_rp2_pins.go b/src/machine/machine_rp2_pins.go index 43b31f938d..36e9bd629c 100644 --- a/src/machine/machine_rp2_pins.go +++ b/src/machine/machine_rp2_pins.go @@ -1,4 +1,4 @@ -//go:build rp2040 || rp2350 || gopher_badge +//go:build rp2040 || rp2350 || gopher_badge || pico package machine From 6c9074772ce135c5c2bd2124be198fef913ba201 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 7 Mar 2025 10:37:40 +0100 Subject: [PATCH 402/444] compiler: crypto/internal/sysrand is allowed to use unsafe signatures Apparently this package imports `runtime.getRandomData` from the gojs module. This is not yet implemented, but simply allowing this package to do such imports gets crypto/sha256 to compile. --- compiler/symbol.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/symbol.go b/compiler/symbol.go index 1de3c6f39d..1226683d57 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -446,7 +446,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) checkWasmImportExport(f *ssa.Function, pragma string) { - if c.pkg.Path() == "runtime" || c.pkg.Path() == "syscall/js" || c.pkg.Path() == "syscall" { + if c.pkg.Path() == "runtime" || c.pkg.Path() == "syscall/js" || c.pkg.Path() == "syscall" || c.pkg.Path() == "crypto/internal/sysrand" { // The runtime is a special case. Allow all kinds of parameters // (importantly, including pointers). return From 5a09084c732a4b07cd961508a5564e31ed9691f3 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 7 Mar 2025 10:43:28 +0100 Subject: [PATCH 403/444] os: add stub Symlink for wasm This doesn't do anything, but it makes tests work. --- src/os/file_other.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/os/file_other.go b/src/os/file_other.go index 8e7d33e00e..1fdf4b1ef4 100644 --- a/src/os/file_other.go +++ b/src/os/file_other.go @@ -134,6 +134,10 @@ func Pipe() (r *File, w *File, err error) { return nil, nil, ErrNotImplemented } +func Symlink(oldname, newname string) error { + return ErrNotImplemented +} + func Readlink(name string) (string, error) { return "", ErrNotImplemented } From dc876c6ed45b4c52dedca6b0c5a4bc3448f1bd64 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 7 Mar 2025 10:43:55 +0100 Subject: [PATCH 404/444] ci: add single test for wasm It looks like we didn't have any tests for wasm. Having one tests is not much, but it proves that the infrastructure works and it actually verifies a fix to https://github.com/tinygo-org/tinygo/issues/4777. We should add more packages to this list in the future. --- .github/workflows/linux.yml | 1 + GNUmakefile | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index cbb0a7c554..a0a9086350 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -165,6 +165,7 @@ jobs: ln -s ~/lib/tinygo/bin/tinygo ~/go/bin/tinygo - run: make tinygo-test-wasip1-fast - run: make tinygo-test-wasip2-fast + - run: make tinygo-test-wasm - run: make smoketest assert-test-linux: # Run all tests that can run on Linux, with LLVM assertions enabled to catch diff --git a/GNUmakefile b/GNUmakefile index 45bca5d8a9..13481aab74 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -407,6 +407,9 @@ TEST_PACKAGES_WINDOWS := \ text/template/parse \ $(nil) +TEST_PACKAGES_WASM := \ + crypto/sha256 + # Report platforms on which each standard library package is known to pass tests jointmp := $(shell echo /tmp/join.$$$$) report-stdlib-tests-pass: @@ -450,6 +453,8 @@ 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-wasi: $(TINYGO) test -target wasip1 $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi tinygo-test-wasip1: From 226744a53897a143a51cc123afe6a45207cef10c Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sun, 9 Mar 2025 14:26:38 +0100 Subject: [PATCH 405/444] machine: correct register address for Pin.SetInterrupt for rp2350 (#4782) Fixes #4689 --- src/machine/machine_rp2_gpio.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/machine/machine_rp2_gpio.go b/src/machine/machine_rp2_gpio.go index 6854f8aad4..25d76261fe 100644 --- a/src/machine/machine_rp2_gpio.go +++ b/src/machine/machine_rp2_gpio.go @@ -33,6 +33,7 @@ type irqSummary struct { type ioBank0Type struct { io [_NUMBANK0_GPIOS]ioType + _ [rp2350ExtraReg][128]byte irqsum [rp2350ExtraReg]irqSummary intR [_NUMBANK0_IRQS]volatile.Register32 proc0IRQctrl irqCtrl From ebf70ab18ebe9e6bbcd2ce76c2cc2c16d86ba633 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 7 Mar 2025 11:40:37 +0100 Subject: [PATCH 406/444] ci: add more tests for wasm and baremetal Run a range of tests in CI, to make sure browser wasm and baremetal don't regress too badly. I have intentionally filtered out tests, so that newly added tests to TEST_PACKAGES_FAST will be added here as well (and can be excluded if needed). --- .github/workflows/linux.yml | 2 +- GNUmakefile | 50 +++++++++++++++++++++++++++++++------ targets/riscv-qemu.ld | 10 ++++---- 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index a0a9086350..0a8fabf1f2 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -278,7 +278,7 @@ jobs: run: make tinygo-test - run: make smoketest - run: make wasmtest - - run: make tinygo-baremetal + - run: make tinygo-test-baremetal build-linux-cross: # Build ARM Linux binaries, ready for release. # This intentionally uses an older Linux image, so that we compile against diff --git a/GNUmakefile b/GNUmakefile index 13481aab74..6e626a488d 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -407,8 +407,45 @@ TEST_PACKAGES_WINDOWS := \ text/template/parse \ $(nil) -TEST_PACKAGES_WASM := \ - crypto/sha256 + +# These packages cannot be tested on wasm, mostly because these tests assume a +# working filesystem. This could perhaps be fixed, by supporting filesystem +# access when running inside Node.js. +TEST_PACKAGES_WASM = $(filter-out $(TEST_PACKAGES_NONWASM), $(TEST_PACKAGES_FAST)) +TEST_PACKAGES_NONWASM = \ + compress/lzw \ + compress/zlib \ + crypto/ecdsa \ + debug/macho \ + embed/internal/embedtest \ + go/format \ + os \ + testing \ + $(nil) + +# These packages cannot be tested on baremetal. +# +# Some reasons why the tests don't pass on baremetal: +# +# * No filesystem is available, so packages like compress/zlib can't be tested +# (just like wasm). +# * There is no RNG implemented (TODO, I think this is fixable). +# * picolibc math functions apparently are less precise, the math package +# fails on baremetal. +# * Some packages fail or hang for an unknown reason, this should be +# investigated and fixed. +TEST_PACKAGES_BAREMETAL = $(filter-out $(TEST_PACKAGES_NONBAREMETAL), $(TEST_PACKAGES_FAST)) +TEST_PACKAGES_NONBAREMETAL = \ + $(TEST_PACKAGES_NONWASM) \ + crypto/elliptic \ + crypto/md5 \ + crypto/sha1 \ + math \ + reflect \ + encoding/asn1 \ + encoding/base32 \ + go/ast \ + $(nil) # Report platforms on which each standard library package is known to pass tests jointmp := $(shell echo /tmp/join.$$$$) @@ -489,6 +526,10 @@ tinygo-bench-wasip2: tinygo-bench-wasip2-fast: $(TINYGO) test -target wasip2 -bench . $(TEST_PACKAGES_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) + # Test external packages in a large corpus. test-corpus: CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml @@ -499,11 +540,6 @@ test-corpus-wasi: wasi-libc test-corpus-wasip2: wasi-libc CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasip2 -tinygo-baremetal: - # Regression tests that run on a baremetal target and don't fit in either main_test.go or smoketest. - # regression test for #2666: e.g. encoding/hex must pass on baremetal - $(TINYGO) test -target cortex-m-qemu encoding/hex - .PHONY: testchdir testchdir: # test 'build' command with{,out} -C argument diff --git a/targets/riscv-qemu.ld b/targets/riscv-qemu.ld index ab344571e2..822c00d10b 100644 --- a/targets/riscv-qemu.ld +++ b/targets/riscv-qemu.ld @@ -1,16 +1,16 @@ /* Memory map: * https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c - * RAM and flash are set to 1MB each. That should be enough for the foreseeable - * future. QEMU does not seem to limit the flash/RAM size and in fact doesn't - * seem to differentiate between it. + * Looks like we can use any address starting from 0x80000000 (so 2GB of space). + * However, using a large space slows down tests. */ MEMORY { - FLASH_TEXT (rw) : ORIGIN = 0x80000000, LENGTH = 0x100000 - RAM (xrw) : ORIGIN = 0x80100000, LENGTH = 0x100000 + RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 100M } +REGION_ALIAS("FLASH_TEXT", RAM) + _stack_size = 2K; INCLUDE "targets/riscv.ld" From 7f970a45c285863bccb53acbd266496bee63de5e Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sun, 2 Mar 2025 14:27:18 +0100 Subject: [PATCH 407/444] machine: don't block the rp2xxx UART interrupt handler Don't block forever if there's nothing to receive. On the other hand, process the entire FIFO, not just a single byte. This fixes an issue where the rp2350 would hang after programming through openocd, where the UART0 interrupt would be spuriously pending. --- src/machine/machine_rp2_uart.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/machine/machine_rp2_uart.go b/src/machine/machine_rp2_uart.go index 77aa26e54c..03ebb9d874 100644 --- a/src/machine/machine_rp2_uart.go +++ b/src/machine/machine_rp2_uart.go @@ -67,7 +67,7 @@ func (uart *UART) Configure(config UARTConfig) error { uart.Interrupt.SetPriority(0x80) uart.Interrupt.Enable() - // setup interrupt on receive + // Setup interrupt on receive. uart.Bus.UARTIMSC.Set(rp.UART0_UARTIMSC_RXIM) return nil @@ -153,7 +153,7 @@ func initUART(uart *UART) { // handleInterrupt should be called from the appropriate interrupt handler for // this UART instance. func (uart *UART) handleInterrupt(interrupt.Interrupt) { - for uart.Bus.UARTFR.HasBits(rp.UART0_UARTFR_RXFE) { + for !uart.Bus.UARTFR.HasBits(rp.UART0_UARTFR_RXFE) { + uart.Receive(byte((uart.Bus.UARTDR.Get() & 0xFF))) } - uart.Receive(byte((uart.Bus.UARTDR.Get() & 0xFF))) } From cab3834bc9609e2ccfe57045983c4bdcd7eddb5d Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 10 Mar 2025 11:34:29 +0100 Subject: [PATCH 408/444] riscv-qemu: add VirtIO RNG device This implements machine.GetRNG() using VirtIO. This gets the tests to pass for crypto/md5 and crypto/sha1 that use crypto/rand in their tests. --- GNUmakefile | 3 - src/crypto/rand/rand_baremetal.go | 2 +- src/machine/virt.go | 189 ++++++++++++++++++++++++++++++ src/runtime/rand_hwrng.go | 2 +- src/runtime/rand_norng.go | 2 +- targets/riscv-qemu.json | 2 +- 6 files changed, 193 insertions(+), 7 deletions(-) create mode 100644 src/machine/virt.go diff --git a/GNUmakefile b/GNUmakefile index 6e626a488d..978d7d9817 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -429,7 +429,6 @@ TEST_PACKAGES_NONWASM = \ # # * No filesystem is available, so packages like compress/zlib can't be tested # (just like wasm). -# * There is no RNG implemented (TODO, I think this is fixable). # * picolibc math functions apparently are less precise, the math package # fails on baremetal. # * Some packages fail or hang for an unknown reason, this should be @@ -438,8 +437,6 @@ TEST_PACKAGES_BAREMETAL = $(filter-out $(TEST_PACKAGES_NONBAREMETAL), $(TEST_PAC TEST_PACKAGES_NONBAREMETAL = \ $(TEST_PACKAGES_NONWASM) \ crypto/elliptic \ - crypto/md5 \ - crypto/sha1 \ math \ reflect \ encoding/asn1 \ diff --git a/src/crypto/rand/rand_baremetal.go b/src/crypto/rand/rand_baremetal.go index 30b2c3b230..5711f23eb0 100644 --- a/src/crypto/rand/rand_baremetal.go +++ b/src/crypto/rand/rand_baremetal.go @@ -1,4 +1,4 @@ -//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey +//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt) // If you update the above build constraint, you'll probably also need to update // src/runtime/rand_hwrng.go. diff --git a/src/machine/virt.go b/src/machine/virt.go new file mode 100644 index 0000000000..2b28ae61e6 --- /dev/null +++ b/src/machine/virt.go @@ -0,0 +1,189 @@ +//go:build tinygo.riscv32 && virt + +// Machine implementation for VirtIO targets. +// At the moment only QEMU RISC-V is supported, but support for ARM for example +// should not be difficult to add with a change to virtioFindDevice. + +package machine + +import ( + "errors" + "runtime/volatile" + "sync" + "unsafe" +) + +const deviceName = "riscv-qemu" + +func (p Pin) Set(high bool) { + // no pins defined +} + +var rngLock sync.Mutex +var rngDevice *virtioDevice1 +var rngBuf volatile.Register32 + +var errNoRNG = errors.New("machine: no entropy source found") +var errNoRNGData = errors.New("machine: entropy source didn't return enough data") + +// GetRNG returns random numbers from a VirtIO entropy source. +// When running in QEMU, it requires adding the RNG device: +// +// -device virtio-rng-device +func GetRNG() (uint32, error) { + rngLock.Lock() + + // Initialize the device on first use. + if rngDevice == nil { + // Search for an available RNG. + rngDevice = virtioFindDevice(virtioDeviceEntropySource) + if rngDevice == nil { + rngLock.Unlock() + return 0, errNoRNG + } + + // Initialize the device. + rngDevice.status.Set(0) // reset device + rngDevice.status.Set(virtioDeviceStatusAcknowledge) + rngDevice.status.Set(virtioDeviceStatusAcknowledge | virtioDeviceStatusDriver) + rngDevice.hostFeaturesSel.Set(0) + rngDevice.status.Set(virtioDeviceStatusAcknowledge | virtioDeviceStatusDriver | virtioDeviceStatusDriverOk) + rngDevice.guestPageSize.Set(4096) + + // Configure queue, according to section 4.2.4 "Legacy interface". + // Note: we're skipping checks for queuePFM and queueNumMax. + rngDevice.queueSel.Set(0) // use queue 0 (the only queue) + rngDevice.queueNum.Set(1) // use a single buffer in the queue + rngDevice.queueAlign.Set(4096) // default alignment appears to be 4096 + rngDevice.queuePFN.Set(uint32(uintptr(unsafe.Pointer(&rngQueue))) / 4096) + + // Configure the only buffer in the queue (but don't increment + // rngQueue.available yet). + rngQueue.buffers[0].address = uint64(uintptr(unsafe.Pointer(&rngBuf))) + rngQueue.buffers[0].length = uint32(unsafe.Sizeof(rngBuf)) + rngQueue.buffers[0].flags = 2 // 2 means write-only buffer + } + + // Increment the available ring buffer. This doesn't actually change the + // buffer index (it's a ring with a single entry), but the number needs to + // be incremented otherwise the device won't recognize a new buffer. + index := rngQueue.available.index + rngQueue.available.index = index + 1 + rngDevice.queueNotify.Set(0) // notify the device of the 'new' (reused) buffer + for rngQueue.used.index.Get() != index+1 { + // Busy wait until the RNG buffer is filled. + // A better way would be to wait for an interrupt, but since this driver + // implementation is mostly used for testing it's good enough for now. + } + + // Check that we indeed got 4 bytes back. + if rngQueue.used.ring[0].length != 4 { + rngLock.Unlock() + return 0, errNoRNGData + } + + // Read the resulting random numbers. + result := rngBuf.Get() + + rngLock.Unlock() + + return result, nil +} + +// Implement a driver for the VirtIO entropy device. +// https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html +// http://wiki.osdev.org/Virtio +// http://www.dumais.io/index.php?article=aca38a9a2b065b24dfa1dee728062a12 + +const ( + virtioDeviceStatusAcknowledge = 1 + virtioDeviceStatusDriver = 2 + virtioDeviceStatusDriverOk = 4 + virtioDeviceStatusFeaturesOk = 8 + virtioDeviceStatusFailed = 128 +) + +const ( + virtioDeviceReserved = iota + virtioDeviceNetworkCard + virtioDeviceBlockDevice + virtioDeviceConsole + virtioDeviceEntropySource + // there are more device types +) + +// VirtIO device version 1 +type virtioDevice1 struct { + magic volatile.Register32 // always 0x74726976 + version volatile.Register32 + deviceID volatile.Register32 + vendorID volatile.Register32 + hostFeatures volatile.Register32 + hostFeaturesSel volatile.Register32 + _ [2]uint32 + guestFeatures volatile.Register32 + guestFeaturesSel volatile.Register32 + guestPageSize volatile.Register32 + _ uint32 + queueSel volatile.Register32 + queueNumMax volatile.Register32 + queueNum volatile.Register32 + queueAlign volatile.Register32 + queuePFN volatile.Register32 + _ [3]uint32 + queueNotify volatile.Register32 + _ [3]uint32 + interruptStatus volatile.Register32 + interruptAck volatile.Register32 + _ [2]uint32 + status volatile.Register32 +} + +// VirtIO queue, with a single buffer. +type virtioQueue struct { + buffers [1]struct { + address uint64 + length uint32 + flags uint16 + next uint16 + } // 16 bytes + + available struct { + flags uint16 + index uint16 + ring [1]uint16 + eventIndex uint16 + } // 8 bytes + + _ [4096 - 16*1 - 8*1]byte // padding (to align on a 4096 byte boundary) + + used struct { + flags uint16 + index volatile.Register16 + ring [1]struct { + index uint32 + length uint32 + } + availEvent uint16 + } +} + +func virtioFindDevice(deviceID uint32) *virtioDevice1 { + // On RISC-V, QEMU defines 8 VirtIO devices starting at 0x10001000 and + // repeating every 0x1000 bytes. + // The memory map can be seen in the QEMU source code: + // https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c + for i := 0; i < 8; i++ { + dev := (*virtioDevice1)(unsafe.Pointer(uintptr(0x10001000 + i*0x1000))) + if dev.magic.Get() != 0x74726976 || dev.version.Get() != 1 || dev.deviceID.Get() != deviceID { + continue + } + return dev + } + return nil +} + +// A VirtIO queue needs to be page-aligned. +// +//go:align 4096 +var rngQueue virtioQueue diff --git a/src/runtime/rand_hwrng.go b/src/runtime/rand_hwrng.go index 2c690b4490..9f1a152d7f 100644 --- a/src/runtime/rand_hwrng.go +++ b/src/runtime/rand_hwrng.go @@ -1,4 +1,4 @@ -//go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey) +//go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt)) // If you update the above build constraint, you'll probably also need to update // src/crypto/rand/rand_baremetal.go. diff --git a/src/runtime/rand_norng.go b/src/runtime/rand_norng.go index a86cdc5429..aa79c500dc 100644 --- a/src/runtime/rand_norng.go +++ b/src/runtime/rand_norng.go @@ -1,4 +1,4 @@ -//go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey) +//go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt)) package runtime diff --git a/targets/riscv-qemu.json b/targets/riscv-qemu.json index cb26829686..84890308ba 100644 --- a/targets/riscv-qemu.json +++ b/targets/riscv-qemu.json @@ -4,5 +4,5 @@ "build-tags": ["virt", "qemu"], "default-stack-size": 4096, "linkerscript": "targets/riscv-qemu.ld", - "emulator": "qemu-system-riscv32 -machine virt -nographic -bios none -kernel {}" + "emulator": "qemu-system-riscv32 -machine virt -nographic -bios none -device virtio-rng-device -kernel {}" } From 2bee29959b9f35ad0cf3ff28ddcc08968eecee34 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Tue, 25 Feb 2025 11:09:14 +0100 Subject: [PATCH 409/444] refactor: use *SPI everywhere to make consistant for implementations. Fixes #4663 "in reverse" by making SPI a pointer everywhere, as discussed in the comments. Signed-off-by: deadprogram --- src/machine/board_feather-stm32f405.go | 6 ++-- src/machine/board_lgt92.go | 4 +-- src/machine/board_maixbit_baremetal.go | 4 +-- src/machine/board_mksnanov3.go | 4 +-- src/machine/board_nucleol031k6.go | 4 +-- src/machine/board_stm32f469disco.go | 4 +-- src/machine/board_stm32f4disco.go | 4 +-- src/machine/machine_atmega.go | 4 +-- src/machine/machine_atmega1280.go | 2 +- src/machine/machine_atmega1284p.go | 2 +- src/machine/machine_atmega2560.go | 2 +- src/machine/machine_atmega328p.go | 2 +- src/machine/machine_atmega328pb.go | 4 +-- src/machine/machine_atsamd21.go | 12 +++---- src/machine/machine_atsamd51.go | 12 +++---- src/machine/machine_esp32.go | 6 ++-- src/machine/machine_esp32c3_spi.go | 6 ++-- src/machine/machine_fe310.go | 4 +-- src/machine/machine_generic.go | 6 ++-- src/machine/machine_generic_peripherals.go | 4 +-- src/machine/machine_k210.go | 4 +-- src/machine/machine_nrf51.go | 10 +++--- src/machine/machine_nrf52xxx.go | 12 +++---- src/machine/machine_rp2_spi.go | 40 ++++++++++------------ src/machine/machine_stm32_spi.go | 4 +-- src/machine/machine_stm32f103.go | 8 ++--- src/machine/machine_stm32f4.go | 6 ++-- src/machine/machine_stm32l0.go | 6 ++-- src/machine/machine_stm32l4.go | 6 ++-- src/machine/machine_stm32wlx.go | 6 ++-- src/machine/spi_tx.go | 2 +- 31 files changed, 99 insertions(+), 101 deletions(-) diff --git a/src/machine/board_feather-stm32f405.go b/src/machine/board_feather-stm32f405.go index 5b980867f5..4a184bad51 100644 --- a/src/machine/board_feather-stm32f405.go +++ b/src/machine/board_feather-stm32f405.go @@ -186,15 +186,15 @@ const ( ) var ( - SPI1 = SPI{ + SPI1 = &SPI{ Bus: stm32.SPI2, AltFuncSelector: AF5_SPI1_SPI2, } - SPI2 = SPI{ + SPI2 = &SPI{ Bus: stm32.SPI3, AltFuncSelector: AF6_SPI3, } - SPI3 = SPI{ + SPI3 = &SPI{ Bus: stm32.SPI1, AltFuncSelector: AF5_SPI1_SPI2, } diff --git a/src/machine/board_lgt92.go b/src/machine/board_lgt92.go index ff4d03cd82..71b05083cc 100644 --- a/src/machine/board_lgt92.go +++ b/src/machine/board_lgt92.go @@ -84,10 +84,10 @@ var ( I2C0 = I2C1 // SPI - SPI0 = SPI{ + SPI0 = &SPI{ Bus: stm32.SPI1, } - SPI1 = &SPI0 + SPI1 = SPI0 ) func init() { diff --git a/src/machine/board_maixbit_baremetal.go b/src/machine/board_maixbit_baremetal.go index 7318cfa9e3..f5a7e8d4cb 100644 --- a/src/machine/board_maixbit_baremetal.go +++ b/src/machine/board_maixbit_baremetal.go @@ -6,10 +6,10 @@ import "device/kendryte" // SPI on the MAix Bit. var ( - SPI0 = SPI{ + SPI0 = &SPI{ Bus: kendryte.SPI0, } - SPI1 = SPI{ + SPI1 = &SPI{ Bus: kendryte.SPI1, } ) diff --git a/src/machine/board_mksnanov3.go b/src/machine/board_mksnanov3.go index d096cdb9d1..35fef682d4 100644 --- a/src/machine/board_mksnanov3.go +++ b/src/machine/board_mksnanov3.go @@ -89,11 +89,11 @@ const ( // Since the first interface is named SPI1, both SPI0 and SPI1 refer to SPI1. var ( - SPI0 = SPI{ + SPI0 = &SPI{ Bus: stm32.SPI1, AltFuncSelector: AF5_SPI1_SPI2, } - SPI1 = &SPI0 + SPI1 = SPI0 ) const ( diff --git a/src/machine/board_nucleol031k6.go b/src/machine/board_nucleol031k6.go index 1a57289b9d..aea92d8be0 100644 --- a/src/machine/board_nucleol031k6.go +++ b/src/machine/board_nucleol031k6.go @@ -86,11 +86,11 @@ var ( I2C0 = I2C1 // SPI - SPI0 = SPI{ + SPI0 = &SPI{ Bus: stm32.SPI1, AltFuncSelector: 0, } - SPI1 = &SPI0 + SPI1 = SPI0 ) func init() { diff --git a/src/machine/board_stm32f469disco.go b/src/machine/board_stm32f469disco.go index a4eef1420a..8fb5cde2ea 100644 --- a/src/machine/board_stm32f469disco.go +++ b/src/machine/board_stm32f469disco.go @@ -59,11 +59,11 @@ const ( // Since the first interface is named SPI1, both SPI0 and SPI1 refer to SPI1. // TODO: implement SPI2 and SPI3. var ( - SPI0 = SPI{ + SPI0 = &SPI{ Bus: stm32.SPI1, AltFuncSelector: AF5_SPI1_SPI2, } - SPI1 = &SPI0 + SPI1 = SPI0 ) const ( diff --git a/src/machine/board_stm32f4disco.go b/src/machine/board_stm32f4disco.go index 291109c5de..d048fcacf4 100644 --- a/src/machine/board_stm32f4disco.go +++ b/src/machine/board_stm32f4disco.go @@ -86,11 +86,11 @@ const ( // Since the first interface is named SPI1, both SPI0 and SPI1 refer to SPI1. // TODO: implement SPI2 and SPI3. var ( - SPI0 = SPI{ + SPI0 = &SPI{ Bus: stm32.SPI1, AltFuncSelector: AF5_SPI1_SPI2, } - SPI1 = &SPI0 + SPI1 = SPI0 ) const ( diff --git a/src/machine/machine_atmega.go b/src/machine/machine_atmega.go index 46dee5ef1e..7a59e5e092 100644 --- a/src/machine/machine_atmega.go +++ b/src/machine/machine_atmega.go @@ -257,7 +257,7 @@ type SPI struct { } // Configure is intended to setup the SPI interface. -func (s SPI) Configure(config SPIConfig) error { +func (s *SPI) Configure(config SPIConfig) error { // This is only here to help catch a bug with the configuration // where a machine missed a value. @@ -330,7 +330,7 @@ func (s SPI) Configure(config SPIConfig) error { } // Transfer writes the byte into the register and returns the read content -func (s SPI) Transfer(b byte) (byte, error) { +func (s *SPI) Transfer(b byte) (byte, error) { s.spdr.Set(uint8(b)) for !s.spsr.HasBits(s.spsrSPIF) { diff --git a/src/machine/machine_atmega1280.go b/src/machine/machine_atmega1280.go index 1b8cb848de..ad33dcf8c0 100644 --- a/src/machine/machine_atmega1280.go +++ b/src/machine/machine_atmega1280.go @@ -927,7 +927,7 @@ func (pwm PWM) Set(channel uint8, value uint32) { } // SPI configuration -var SPI0 = SPI{ +var SPI0 = &SPI{ spcr: avr.SPCR, spdr: avr.SPDR, spsr: avr.SPSR, diff --git a/src/machine/machine_atmega1284p.go b/src/machine/machine_atmega1284p.go index 511f9e8730..db8fd65a2f 100644 --- a/src/machine/machine_atmega1284p.go +++ b/src/machine/machine_atmega1284p.go @@ -71,7 +71,7 @@ func (p Pin) getPortMask() (*volatile.Register8, uint8) { } // SPI configuration -var SPI0 = SPI{ +var SPI0 = &SPI{ spcr: avr.SPCR, spsr: avr.SPSR, spdr: avr.SPDR, diff --git a/src/machine/machine_atmega2560.go b/src/machine/machine_atmega2560.go index 339c35ae77..ede862a931 100644 --- a/src/machine/machine_atmega2560.go +++ b/src/machine/machine_atmega2560.go @@ -131,7 +131,7 @@ func (p Pin) getPortMask() (*volatile.Register8, uint8) { } // SPI configuration -var SPI0 = SPI{ +var SPI0 = &SPI{ spcr: avr.SPCR, spdr: avr.SPDR, spsr: avr.SPSR, diff --git a/src/machine/machine_atmega328p.go b/src/machine/machine_atmega328p.go index 1995403aab..5bacfb8f20 100644 --- a/src/machine/machine_atmega328p.go +++ b/src/machine/machine_atmega328p.go @@ -25,7 +25,7 @@ var I2C0 = &I2C{ } // SPI configuration -var SPI0 = SPI{ +var SPI0 = &SPI{ spcr: avr.SPCR, spdr: avr.SPDR, spsr: avr.SPSR, diff --git a/src/machine/machine_atmega328pb.go b/src/machine/machine_atmega328pb.go index 091fb6337f..935c581d54 100644 --- a/src/machine/machine_atmega328pb.go +++ b/src/machine/machine_atmega328pb.go @@ -60,7 +60,7 @@ var I2C1 = &I2C{ } // SPI configuration -var SPI0 = SPI{ +var SPI0 = &SPI{ spcr: avr.SPCR0, spdr: avr.SPDR0, spsr: avr.SPSR0, @@ -82,7 +82,7 @@ var SPI0 = SPI{ cs: PB2, } -var SPI1 = SPI{ +var SPI1 = &SPI{ spcr: avr.SPCR1, spdr: avr.SPDR1, spsr: avr.SPSR1, diff --git a/src/machine/machine_atsamd21.go b/src/machine/machine_atsamd21.go index 0a5c320ca0..e0c5f2cc29 100644 --- a/src/machine/machine_atsamd21.go +++ b/src/machine/machine_atsamd21.go @@ -1224,7 +1224,7 @@ type SPIConfig struct { } // Configure is intended to setup the SPI interface. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // Use default pins if not set. if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { config.SCK = SPI0_SCK_PIN @@ -1346,7 +1346,7 @@ func (spi SPI) Configure(config SPIConfig) error { } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { // write data spi.Bus.DATA.Set(uint32(w)) @@ -1375,7 +1375,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { // This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": // // spi.Tx(nil, rx) -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { switch { case w == nil: // read only, so write zero and read a result. @@ -1396,7 +1396,7 @@ func (spi SPI) Tx(w, r []byte) error { return nil } -func (spi SPI) tx(tx []byte) { +func (spi *SPI) tx(tx []byte) { for i := 0; i < len(tx); i++ { for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) { } @@ -1411,7 +1411,7 @@ func (spi SPI) tx(tx []byte) { } } -func (spi SPI) rx(rx []byte) { +func (spi *SPI) rx(rx []byte) { spi.Bus.DATA.Set(0) for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) { } @@ -1427,7 +1427,7 @@ func (spi SPI) rx(rx []byte) { rx[len(rx)-1] = byte(spi.Bus.DATA.Get()) } -func (spi SPI) txrx(tx, rx []byte) { +func (spi *SPI) txrx(tx, rx []byte) { spi.Bus.DATA.Set(uint32(tx[0])) for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) { } diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go index 5cd1d314a2..d169eab995 100644 --- a/src/machine/machine_atsamd51.go +++ b/src/machine/machine_atsamd51.go @@ -1431,7 +1431,7 @@ type SPIConfig struct { } // Configure is intended to setup the SPI interface. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // Use default pins if not set. if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { config.SCK = SPI0_SCK_PIN @@ -1574,7 +1574,7 @@ func (spi SPI) Configure(config SPIConfig) error { } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { // write data spi.Bus.DATA.Set(uint32(w)) @@ -1603,7 +1603,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { // This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": // // spi.Tx(nil, rx) -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { switch { case w == nil: // read only, so write zero and read a result. @@ -1624,7 +1624,7 @@ func (spi SPI) Tx(w, r []byte) error { return nil } -func (spi SPI) tx(tx []byte) { +func (spi *SPI) tx(tx []byte) { for i := 0; i < len(tx); i++ { for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) { } @@ -1639,7 +1639,7 @@ func (spi SPI) tx(tx []byte) { } } -func (spi SPI) rx(rx []byte) { +func (spi *SPI) rx(rx []byte) { spi.Bus.DATA.Set(0) for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) { } @@ -1655,7 +1655,7 @@ func (spi SPI) rx(rx []byte) { rx[len(rx)-1] = byte(spi.Bus.DATA.Get()) } -func (spi SPI) txrx(tx, rx []byte) { +func (spi *SPI) txrx(tx, rx []byte) { spi.Bus.DATA.Set(uint32(tx[0])) for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) { } diff --git a/src/machine/machine_esp32.go b/src/machine/machine_esp32.go index d6f6599288..50ff74d672 100644 --- a/src/machine/machine_esp32.go +++ b/src/machine/machine_esp32.go @@ -354,7 +354,7 @@ type SPIConfig struct { } // Configure and make the SPI peripheral ready to use. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { if config.Frequency == 0 { config.Frequency = 4e6 // default to 4MHz } @@ -445,7 +445,7 @@ func (spi SPI) Configure(config SPIConfig) error { // Transfer writes/reads a single byte using the SPI interface. If you need to // transfer larger amounts of data, Tx will be faster. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { spi.Bus.MISO_DLEN.Set(7 << esp.SPI_MISO_DLEN_USR_MISO_DBITLEN_Pos) spi.Bus.MOSI_DLEN.Set(7 << esp.SPI_MOSI_DLEN_USR_MOSI_DBITLEN_Pos) @@ -464,7 +464,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { // interface, there must always be the same number of bytes written as bytes read. // This is accomplished by sending zero bits if r is bigger than w or discarding // the incoming data if w is bigger than r. -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { toTransfer := len(w) if len(r) > toTransfer { toTransfer = len(r) diff --git a/src/machine/machine_esp32c3_spi.go b/src/machine/machine_esp32c3_spi.go index 2fb8abc200..ecb1923dee 100644 --- a/src/machine/machine_esp32c3_spi.go +++ b/src/machine/machine_esp32c3_spi.go @@ -114,7 +114,7 @@ func freqToClockDiv(hz uint32) uint32 { } // Configure and make the SPI peripheral ready to use. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // right now this is only setup to work for the esp32c3 spi2 bus if spi.Bus != esp.SPI2 { return ErrInvalidSPIBus @@ -216,7 +216,7 @@ func (spi SPI) Configure(config SPIConfig) error { // Transfer writes/reads a single byte using the SPI interface. If you need to // transfer larger amounts of data, Tx will be faster. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { spi.Bus.SetMS_DLEN_MS_DATA_BITLEN(7) spi.Bus.SetW0(uint32(w)) @@ -238,7 +238,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { // interface, there must always be the same number of bytes written as bytes read. // This is accomplished by sending zero bits if r is bigger than w or discarding // the incoming data if w is bigger than r. -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { toTransfer := len(w) if len(r) > toTransfer { toTransfer = len(r) diff --git a/src/machine/machine_fe310.go b/src/machine/machine_fe310.go index 4a15ad76e1..2f716d6168 100644 --- a/src/machine/machine_fe310.go +++ b/src/machine/machine_fe310.go @@ -140,7 +140,7 @@ type SPIConfig struct { } // Configure is intended to setup the SPI interface. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // Use default pins if not set. if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { config.SCK = SPI0_SCK_PIN @@ -197,7 +197,7 @@ func (spi SPI) Configure(config SPIConfig) error { } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { // wait for tx ready for spi.Bus.TXDATA.HasBits(sifive.QSPI_TXDATA_FULL) { } diff --git a/src/machine/machine_generic.go b/src/machine/machine_generic.go index 4f040fdbbd..1c8512244b 100644 --- a/src/machine/machine_generic.go +++ b/src/machine/machine_generic.go @@ -60,13 +60,13 @@ type SPIConfig struct { Mode uint8 } -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { spiConfigure(spi.Bus, config.SCK, config.SDO, config.SDI) return nil } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { return spiTransfer(spi.Bus, w), nil } @@ -87,7 +87,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { // This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": // // spi.Tx(nil, rx) -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { var wptr, rptr *byte var wlen, rlen int if len(w) != 0 { diff --git a/src/machine/machine_generic_peripherals.go b/src/machine/machine_generic_peripherals.go index 28539f0e7f..6c95c206cd 100644 --- a/src/machine/machine_generic_peripherals.go +++ b/src/machine/machine_generic_peripherals.go @@ -8,7 +8,7 @@ package machine var ( UART0 = hardwareUART0 UART1 = hardwareUART1 - SPI0 = SPI{0} - SPI1 = SPI{1} + SPI0 = &SPI{0} + SPI1 = &SPI{1} I2C0 = &I2C{0} ) diff --git a/src/machine/machine_k210.go b/src/machine/machine_k210.go index 28d5098e75..d83576a617 100644 --- a/src/machine/machine_k210.go +++ b/src/machine/machine_k210.go @@ -419,7 +419,7 @@ type SPIConfig struct { // Configure is intended to setup the SPI interface. // Only SPI controller 0 and 1 can be used because SPI2 is a special // peripheral-mode controller and SPI3 is used for flashing. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // Use default pins if not set. if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { config.SCK = SPI0_SCK_PIN @@ -476,7 +476,7 @@ func (spi SPI) Configure(config SPIConfig) error { } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { spi.Bus.SSIENR.Set(0) // Set transfer-receive mode. diff --git a/src/machine/machine_nrf51.go b/src/machine/machine_nrf51.go index 8d27e6fc55..d627d63c21 100644 --- a/src/machine/machine_nrf51.go +++ b/src/machine/machine_nrf51.go @@ -34,8 +34,8 @@ type SPI struct { // There are 2 SPI interfaces on the NRF51. var ( - SPI0 = SPI{Bus: nrf.SPI0} - SPI1 = SPI{Bus: nrf.SPI1} + SPI0 = &SPI{Bus: nrf.SPI0} + SPI1 = &SPI{Bus: nrf.SPI1} ) // SPIConfig is used to store config info for SPI. @@ -49,7 +49,7 @@ type SPIConfig struct { } // Configure is intended to setup the SPI interface. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // Disable bus to configure it spi.Bus.ENABLE.Set(nrf.SPI_ENABLE_ENABLE_Disabled) @@ -122,7 +122,7 @@ func (spi SPI) Configure(config SPIConfig) error { } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { spi.Bus.TXD.Set(uint32(w)) for spi.Bus.EVENTS_READY.Get() == 0 { } @@ -150,7 +150,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { // This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": // // spi.Tx(nil, rx) -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { var err error switch { diff --git a/src/machine/machine_nrf52xxx.go b/src/machine/machine_nrf52xxx.go index 927458f543..a582a7aa56 100644 --- a/src/machine/machine_nrf52xxx.go +++ b/src/machine/machine_nrf52xxx.go @@ -199,9 +199,9 @@ type SPI struct { // There are 3 SPI interfaces on the NRF528xx. var ( - SPI0 = SPI{Bus: nrf.SPIM0, buf: new([1]byte)} - SPI1 = SPI{Bus: nrf.SPIM1, buf: new([1]byte)} - SPI2 = SPI{Bus: nrf.SPIM2, buf: new([1]byte)} + SPI0 = &SPI{Bus: nrf.SPIM0, buf: new([1]byte)} + SPI1 = &SPI{Bus: nrf.SPIM1, buf: new([1]byte)} + SPI2 = &SPI{Bus: nrf.SPIM2, buf: new([1]byte)} ) // SPIConfig is used to store config info for SPI. @@ -215,7 +215,7 @@ type SPIConfig struct { } // Configure is intended to set up the SPI interface. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // Disable bus to configure it spi.Bus.ENABLE.Set(nrf.SPIM_ENABLE_ENABLE_Disabled) @@ -288,7 +288,7 @@ func (spi SPI) Configure(config SPIConfig) error { } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { buf := spi.buf[:] buf[0] = w err := spi.Tx(buf[:], buf[:]) @@ -300,7 +300,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { // as bytes read. Therefore, if the number of bytes don't match it will be // padded until they fit: if len(w) > len(r) the extra bytes received will be // dropped and if len(w) < len(r) extra 0 bytes will be sent. -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { // Unfortunately the hardware (on the nrf52832) only supports up to 255 // bytes in the buffers, so if either w or r is longer than that the // transfer needs to be broken up in pieces. diff --git a/src/machine/machine_rp2_spi.go b/src/machine/machine_rp2_spi.go index a589cb648c..88301c98b2 100644 --- a/src/machine/machine_rp2_spi.go +++ b/src/machine/machine_rp2_spi.go @@ -10,12 +10,10 @@ import ( // SPI on the RP2040 var ( - SPI0 = &_SPI0 - _SPI0 = SPI{ + SPI0 = &SPI{ Bus: rp.SPI0, } - SPI1 = &_SPI1 - _SPI1 = SPI{ + SPI1 = &SPI{ Bus: rp.SPI1, } ) @@ -73,7 +71,7 @@ type SPI struct { // // This form sends 0xff and puts the result into rx buffer. Useful for reading from SD cards // which require 0xff input on SI. -func (spi SPI) Tx(w, r []byte) (err error) { +func (spi *SPI) Tx(w, r []byte) (err error) { switch { case w == nil: // read only, so write zero and read a result. @@ -92,7 +90,7 @@ func (spi SPI) Tx(w, r []byte) (err error) { } // Write a single byte and read a single byte from TX/RX FIFO. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { for !spi.isWritable() { } @@ -103,7 +101,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { return uint8(spi.Bus.SSPDR.Get()), nil } -func (spi SPI) SetBaudRate(br uint32) error { +func (spi *SPI) SetBaudRate(br uint32) error { const maxBaud uint32 = 66.5 * MHz // max output frequency is 66.5MHz on rp2040. see Note page 527. // Find smallest prescale value which puts output frequency in range of // post-divide. Prescale is an even number from 2 to 254 inclusive. @@ -129,8 +127,8 @@ func (spi SPI) SetBaudRate(br uint32) error { return nil } -func (spi SPI) GetBaudRate() uint32 { - const freqin uint32 = 125 * MHz +func (spi *SPI) GetBaudRate() uint32 { + freqin := CPUFrequency() prescale := spi.Bus.SSPCPSR.Get() postdiv := ((spi.Bus.SSPCR0.Get() & rp.SPI0_SSPCR0_SCR_Msk) >> rp.SPI0_SSPCR0_SCR_Pos) + 1 return freqin / (prescale * postdiv) @@ -152,7 +150,7 @@ func (spi SPI) GetBaudRate() uint32 { // SCK: 10, 14 // // No pin configuration is needed of SCK, SDO and SDI needed after calling Configure. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { const defaultBaud uint32 = 4 * MHz if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { // set default pins if config zero valued or invalid clock pin supplied. @@ -199,7 +197,7 @@ func (spi SPI) Configure(config SPIConfig) error { return spi.initSPI(config) } -func (spi SPI) initSPI(config SPIConfig) (err error) { +func (spi *SPI) initSPI(config SPIConfig) (err error) { spi.reset() // LSB-first not supported on PL022: if config.LSBFirst { @@ -217,7 +215,7 @@ func (spi SPI) initSPI(config SPIConfig) (err error) { } //go:inline -func (spi SPI) setFormat(mode uint8) { +func (spi *SPI) setFormat(mode uint8) { cpha := uint32(mode) & 1 cpol := uint32(mode>>1) & 1 spi.Bus.SSPCR0.ReplaceBits( @@ -230,7 +228,7 @@ func (spi SPI) setFormat(mode uint8) { // reset resets SPI and waits until reset is done. // //go:inline -func (spi SPI) reset() { +func (spi *SPI) reset() { resetVal := spi.deinit() rp.RESETS.RESET.ClearBits(resetVal) // Wait until reset is done. @@ -239,7 +237,7 @@ func (spi SPI) reset() { } //go:inline -func (spi SPI) deinit() (resetVal uint32) { +func (spi *SPI) deinit() (resetVal uint32) { switch spi.Bus { case rp.SPI0: resetVal = rp.RESETS_RESET_SPI0 @@ -254,19 +252,19 @@ func (spi SPI) deinit() (resetVal uint32) { // isWritable returns false if no space is available to write. True if a write is possible // //go:inline -func (spi SPI) isWritable() bool { +func (spi *SPI) isWritable() bool { return spi.Bus.SSPSR.HasBits(rp.SPI0_SSPSR_TNF) } // isReadable returns true if a read is possible i.e. data is present // //go:inline -func (spi SPI) isReadable() bool { +func (spi *SPI) isReadable() bool { return spi.Bus.SSPSR.HasBits(rp.SPI0_SSPSR_RNE) } // PrintRegs prints SPI's peripheral common registries current values -func (spi SPI) PrintRegs() { +func (spi *SPI) PrintRegs() { cr0 := spi.Bus.SSPCR0.Get() cr1 := spi.Bus.SSPCR1.Get() dmacr := spi.Bus.SSPDMACR.Get() @@ -282,12 +280,12 @@ func (spi SPI) PrintRegs() { } //go:inline -func (spi SPI) isBusy() bool { +func (spi *SPI) isBusy() bool { return spi.Bus.SSPSR.HasBits(rp.SPI0_SSPSR_BSY) } // tx writes buffer to SPI ignoring Rx. -func (spi SPI) tx(tx []byte) error { +func (spi *SPI) tx(tx []byte) error { if len(tx) == 0 { // We don't have to do anything. // This avoids a panic in &tx[0] when len(tx) == 0. @@ -352,7 +350,7 @@ func (spi SPI) tx(tx []byte) error { // txrepeat is output repeatedly on SO as data is read in from SI. // Generally this can be 0, but some devices require a specific value here, // e.g. SD cards expect 0xff -func (spi SPI) rx(rx []byte, txrepeat byte) error { +func (spi *SPI) rx(rx []byte, txrepeat byte) error { plen := len(rx) const fifoDepth = 8 // see txrx var rxleft, txleft = plen, plen @@ -375,7 +373,7 @@ func (spi SPI) rx(rx []byte, txrepeat byte) error { // Write len bytes from src to SPI. Simultaneously read len bytes from SPI to dst. // Note this function is guaranteed to exit in a known amount of time (bits sent * time per bit) -func (spi SPI) txrx(tx, rx []byte) error { +func (spi *SPI) txrx(tx, rx []byte) error { plen := len(tx) if plen != len(rx) { return ErrTxInvalidSliceSize diff --git a/src/machine/machine_stm32_spi.go b/src/machine/machine_stm32_spi.go index 72d4316616..3c6e0b6a88 100644 --- a/src/machine/machine_stm32_spi.go +++ b/src/machine/machine_stm32_spi.go @@ -21,7 +21,7 @@ type SPIConfig struct { } // Configure is intended to setup the STM32 SPI1 interface. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // -- CONFIGURING THE SPI IN MASTER MODE -- // @@ -98,7 +98,7 @@ func (spi SPI) Configure(config SPIConfig) error { } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { // 1. Enable the SPI by setting the SPE bit to 1. // 2. Write the first data item to be transmitted into the SPI_DR register diff --git a/src/machine/machine_stm32f103.go b/src/machine/machine_stm32f103.go index b8d494ad7b..9e7bb347c9 100644 --- a/src/machine/machine_stm32f103.go +++ b/src/machine/machine_stm32f103.go @@ -333,16 +333,16 @@ type SPI struct { // Since the first interface is named SPI1, both SPI0 and SPI1 refer to SPI1. // TODO: implement SPI2 and SPI3. var ( - SPI1 = SPI{Bus: stm32.SPI1} + SPI1 = &SPI{Bus: stm32.SPI1} SPI0 = SPI1 ) -func (spi SPI) config8Bits() { +func (spi *SPI) config8Bits() { // no-op on this series } // Set baud rate for SPI -func (spi SPI) getBaudRate(config SPIConfig) uint32 { +func (spi *SPI) getBaudRate(config SPIConfig) uint32 { var conf uint32 // set frequency dependent on PCLK2 prescaler (div 1) @@ -368,7 +368,7 @@ func (spi SPI) getBaudRate(config SPIConfig) uint32 { } // Configure SPI pins for input output and clock -func (spi SPI) configurePins(config SPIConfig) { +func (spi *SPI) configurePins(config SPIConfig) { config.SCK.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltPushPull}) config.SDO.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltPushPull}) config.SDI.Configure(PinConfig{Mode: PinInputModeFloating}) diff --git a/src/machine/machine_stm32f4.go b/src/machine/machine_stm32f4.go index 41e1b2204f..31f1d2c635 100644 --- a/src/machine/machine_stm32f4.go +++ b/src/machine/machine_stm32f4.go @@ -672,17 +672,17 @@ type SPI struct { AltFuncSelector uint8 } -func (spi SPI) config8Bits() { +func (spi *SPI) config8Bits() { // no-op on this series } -func (spi SPI) configurePins(config SPIConfig) { +func (spi *SPI) configurePins(config SPIConfig) { config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector) config.SDO.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDO}, spi.AltFuncSelector) config.SDI.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDI}, spi.AltFuncSelector) } -func (spi SPI) getBaudRate(config SPIConfig) uint32 { +func (spi *SPI) getBaudRate(config SPIConfig) uint32 { var clock uint32 switch spi.Bus { case stm32.SPI1: diff --git a/src/machine/machine_stm32l0.go b/src/machine/machine_stm32l0.go index b7dc1581c3..1ecd958b81 100644 --- a/src/machine/machine_stm32l0.go +++ b/src/machine/machine_stm32l0.go @@ -234,12 +234,12 @@ type SPI struct { AltFuncSelector uint8 } -func (spi SPI) config8Bits() { +func (spi *SPI) config8Bits() { // no-op on this series } // Set baud rate for SPI -func (spi SPI) getBaudRate(config SPIConfig) uint32 { +func (spi *SPI) getBaudRate(config SPIConfig) uint32 { var conf uint32 localFrequency := config.Frequency @@ -289,7 +289,7 @@ func (spi SPI) getBaudRate(config SPIConfig) uint32 { } // Configure SPI pins for input output and clock -func (spi SPI) configurePins(config SPIConfig) { +func (spi *SPI) configurePins(config SPIConfig) { config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector) config.SDO.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDO}, spi.AltFuncSelector) config.SDI.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDI}, spi.AltFuncSelector) diff --git a/src/machine/machine_stm32l4.go b/src/machine/machine_stm32l4.go index c22cf616b8..b5babc0b2a 100644 --- a/src/machine/machine_stm32l4.go +++ b/src/machine/machine_stm32l4.go @@ -309,14 +309,14 @@ type SPI struct { AltFuncSelector uint8 } -func (spi SPI) config8Bits() { +func (spi *SPI) config8Bits() { // Set rx threshold to 8-bits, so RXNE flag is set for 1 byte // (common STM32 SPI implementation does 8-bit transfers only) spi.Bus.CR2.SetBits(stm32.SPI_CR2_FRXTH) } // Set baud rate for SPI -func (spi SPI) getBaudRate(config SPIConfig) uint32 { +func (spi *SPI) getBaudRate(config SPIConfig) uint32 { var conf uint32 // Default @@ -359,7 +359,7 @@ func (spi SPI) getBaudRate(config SPIConfig) uint32 { } // Configure SPI pins for input output and clock -func (spi SPI) configurePins(config SPIConfig) { +func (spi *SPI) configurePins(config SPIConfig) { config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector) config.SDO.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDO}, spi.AltFuncSelector) config.SDI.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDI}, spi.AltFuncSelector) diff --git a/src/machine/machine_stm32wlx.go b/src/machine/machine_stm32wlx.go index 84e302a101..80ca791e6a 100644 --- a/src/machine/machine_stm32wlx.go +++ b/src/machine/machine_stm32wlx.go @@ -235,19 +235,19 @@ type SPI struct { AltFuncSelector uint8 } -func (spi SPI) config8Bits() { +func (spi *SPI) config8Bits() { // Set rx threshold to 8-bits, so RXNE flag is set for 1 byte // (common STM32 SPI implementation does 8-bit transfers only) spi.Bus.CR2.SetBits(stm32.SPI_CR2_FRXTH) } -func (spi SPI) configurePins(config SPIConfig) { +func (spi *SPI) configurePins(config SPIConfig) { config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector) config.SDO.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDO}, spi.AltFuncSelector) config.SDI.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDI}, spi.AltFuncSelector) } -func (spi SPI) getBaudRate(config SPIConfig) uint32 { +func (spi *SPI) getBaudRate(config SPIConfig) uint32 { var clock uint32 // We keep this switch and separate management of SPI Clocks diff --git a/src/machine/spi_tx.go b/src/machine/spi_tx.go index 67076b2bdf..97385bb596 100644 --- a/src/machine/spi_tx.go +++ b/src/machine/spi_tx.go @@ -22,7 +22,7 @@ package machine // This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": // // spi.Tx(nil, rx) -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { var err error switch { From bcdc5e009756730962bdb5d1ef08e569009d82cd Mon Sep 17 00:00:00 2001 From: deadprogram Date: Tue, 11 Mar 2025 14:11:24 +0100 Subject: [PATCH 410/444] chore: update version to 0.37.0-dev Signed-off-by: deadprogram --- goenv/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/goenv/version.go b/goenv/version.go index f2695d275d..1f0f4c75ea 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -10,7 +10,7 @@ import ( // Version of TinyGo. // Update this value before release of new version of software. -const version = "0.36.0" +const version = "0.37.0-dev" // Return TinyGo version, either in the form 0.30.0 or as a development version // (like 0.30.0-dev-abcd012). From f76e8d2812ef41829c4af1156444c7b082e3df1e Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Tue, 11 Mar 2025 08:50:31 -0700 Subject: [PATCH 411/444] fix: ensure use of pointers for SPI interface on atsam21/atsam51 and other machines/boards that were missing implementation. (#4798) Signed-off-by: deadprogram --- src/machine/board_gnse.go | 2 +- src/machine/board_hifive1b_baremetal.go | 2 +- src/machine/board_lorae5.go | 2 +- src/machine/board_nucleowl55jc.go | 2 +- src/machine/board_teensy40.go | 11 ++++------- src/machine/machine_atsamd21e18.go | 8 ++++---- src/machine/machine_atsamd21g18.go | 12 ++++++------ src/machine/machine_atsamd51g19.go | 12 ++++++------ src/machine/machine_atsamd51j19.go | 12 ++++++------ src/machine/machine_atsamd51j20.go | 12 ++++++------ src/machine/machine_atsamd51p19.go | 16 ++++++++-------- src/machine/machine_atsamd51p20.go | 16 ++++++++-------- src/machine/machine_atsame51j19.go | 12 ++++++------ src/machine/machine_atsame54p20.go | 16 ++++++++-------- src/machine/machine_esp32.go | 4 ++-- src/machine/machine_esp32c3_spi.go | 2 +- src/machine/machine_generic.go | 16 ++++++++-------- 17 files changed, 77 insertions(+), 80 deletions(-) diff --git a/src/machine/board_gnse.go b/src/machine/board_gnse.go index 02a8038b14..8e78b43e5a 100644 --- a/src/machine/board_gnse.go +++ b/src/machine/board_gnse.go @@ -77,7 +77,7 @@ var ( I2C0 = I2C1 // SPI - SPI3 = SPI{ + SPI3 = &SPI{ Bus: stm32.SPI3, } ) diff --git a/src/machine/board_hifive1b_baremetal.go b/src/machine/board_hifive1b_baremetal.go index cea0443e69..f621d3bc56 100644 --- a/src/machine/board_hifive1b_baremetal.go +++ b/src/machine/board_hifive1b_baremetal.go @@ -6,7 +6,7 @@ import "device/sifive" // SPI on the HiFive1. var ( - SPI1 = SPI{ + SPI1 = &SPI{ Bus: sifive.QSPI1, } ) diff --git a/src/machine/board_lorae5.go b/src/machine/board_lorae5.go index f2c26997c4..18b5d8e363 100644 --- a/src/machine/board_lorae5.go +++ b/src/machine/board_lorae5.go @@ -85,7 +85,7 @@ var ( I2C0 = I2C2 // SPI - SPI3 = SPI{ + SPI3 = &SPI{ Bus: stm32.SPI3, } ) diff --git a/src/machine/board_nucleowl55jc.go b/src/machine/board_nucleowl55jc.go index fd4883cc02..a8e88dd8bb 100644 --- a/src/machine/board_nucleowl55jc.go +++ b/src/machine/board_nucleowl55jc.go @@ -84,7 +84,7 @@ var ( I2C0 = I2C1 // SPI - SPI3 = SPI{ + SPI3 = &SPI{ Bus: stm32.SPI3, } ) diff --git a/src/machine/board_teensy40.go b/src/machine/board_teensy40.go index 92fe81bce9..22529a8d75 100644 --- a/src/machine/board_teensy40.go +++ b/src/machine/board_teensy40.go @@ -263,9 +263,8 @@ const ( ) var ( - SPI0 = SPI1 // SPI0 is an alias of SPI1 (LPSPI4) - SPI1 = &_SPI1 - _SPI1 = SPI{ + SPI0 = SPI1 // SPI0 is an alias of SPI1 (LPSPI4) + SPI1 = &SPI{ Bus: nxp.LPSPI4, muxSDI: muxSelect{ // D12 (PB1 [B0_01]) mux: nxp.IOMUXC_LPSPI4_SDI_SELECT_INPUT_DAISY_GPIO_B0_01_ALT3, @@ -284,8 +283,7 @@ var ( sel: &nxp.IOMUXC.LPSPI4_PCS0_SELECT_INPUT, }, } - SPI2 = &_SPI2 - _SPI2 = SPI{ + SPI2 = &SPI{ Bus: nxp.LPSPI3, muxSDI: muxSelect{ // D1 (PA2 [AD_B0_02]) mux: nxp.IOMUXC_LPSPI3_SDI_SELECT_INPUT_DAISY_GPIO_AD_B0_02_ALT7, @@ -304,8 +302,7 @@ var ( sel: &nxp.IOMUXC.LPSPI3_PCS0_SELECT_INPUT, }, } - SPI3 = &_SPI3 - _SPI3 = SPI{ + SPI3 = &SPI{ Bus: nxp.LPSPI1, muxSDI: muxSelect{ // D34 (PC15 [SD_B0_03]) mux: nxp.IOMUXC_LPSPI1_SDI_SELECT_INPUT_DAISY_GPIO_SD_B0_03_ALT4, diff --git a/src/machine/machine_atsamd21e18.go b/src/machine/machine_atsamd21e18.go index 85d7ff2552..85d6853bc7 100644 --- a/src/machine/machine_atsamd21e18.go +++ b/src/machine/machine_atsamd21e18.go @@ -22,10 +22,10 @@ var ( sercomI2CM2 = &I2C{Bus: sam.SERCOM2_I2CM, SERCOM: 2} sercomI2CM3 = &I2C{Bus: sam.SERCOM3_I2CM, SERCOM: 3} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPI, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPI, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPI, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPI, SERCOM: 3} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPI, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPI, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPI, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPI, SERCOM: 3} ) func init() { diff --git a/src/machine/machine_atsamd21g18.go b/src/machine/machine_atsamd21g18.go index 9b78ad3367..9e845cf3bc 100644 --- a/src/machine/machine_atsamd21g18.go +++ b/src/machine/machine_atsamd21g18.go @@ -26,12 +26,12 @@ var ( sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPI, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPI, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPI, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPI, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPI, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPI, SERCOM: 5} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPI, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPI, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPI, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPI, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPI, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPI, SERCOM: 5} ) func init() { diff --git a/src/machine/machine_atsamd51g19.go b/src/machine/machine_atsamd51g19.go index ade031bb04..f223f6ebc8 100644 --- a/src/machine/machine_atsamd51g19.go +++ b/src/machine/machine_atsamd51g19.go @@ -18,12 +18,12 @@ var ( sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} ) // setSERCOMClockGenerator sets the GCLK for sercom diff --git a/src/machine/machine_atsamd51j19.go b/src/machine/machine_atsamd51j19.go index b41c25c145..640e1ef263 100644 --- a/src/machine/machine_atsamd51j19.go +++ b/src/machine/machine_atsamd51j19.go @@ -18,12 +18,12 @@ var ( sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} ) // setSERCOMClockGenerator sets the GCLK for sercom diff --git a/src/machine/machine_atsamd51j20.go b/src/machine/machine_atsamd51j20.go index 2c5391afe7..d582278760 100644 --- a/src/machine/machine_atsamd51j20.go +++ b/src/machine/machine_atsamd51j20.go @@ -18,12 +18,12 @@ var ( sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} ) // setSERCOMClockGenerator sets the GCLK for sercom diff --git a/src/machine/machine_atsamd51p19.go b/src/machine/machine_atsamd51p19.go index 70050c2d6f..bcd66a93a7 100644 --- a/src/machine/machine_atsamd51p19.go +++ b/src/machine/machine_atsamd51p19.go @@ -20,14 +20,14 @@ var ( sercomI2CM6 = &I2C{Bus: sam.SERCOM6_I2CM, SERCOM: 6} sercomI2CM7 = &I2C{Bus: sam.SERCOM7_I2CM, SERCOM: 7} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} - sercomSPIM6 = SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} - sercomSPIM7 = SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM6 = &SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} + sercomSPIM7 = &SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} ) // setSERCOMClockGenerator sets the GCLK for sercom diff --git a/src/machine/machine_atsamd51p20.go b/src/machine/machine_atsamd51p20.go index 9f52ba257f..40e435fa1b 100644 --- a/src/machine/machine_atsamd51p20.go +++ b/src/machine/machine_atsamd51p20.go @@ -20,14 +20,14 @@ var ( sercomI2CM6 = &I2C{Bus: sam.SERCOM6_I2CM, SERCOM: 6} sercomI2CM7 = &I2C{Bus: sam.SERCOM7_I2CM, SERCOM: 7} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} - sercomSPIM6 = SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} - sercomSPIM7 = SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM6 = &SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} + sercomSPIM7 = &SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} ) // setSERCOMClockGenerator sets the GCLK for sercom diff --git a/src/machine/machine_atsame51j19.go b/src/machine/machine_atsame51j19.go index 8f8294a266..29ea411784 100644 --- a/src/machine/machine_atsame51j19.go +++ b/src/machine/machine_atsame51j19.go @@ -18,12 +18,12 @@ var ( sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} ) // setSERCOMClockGenerator sets the GCLK for sercom diff --git a/src/machine/machine_atsame54p20.go b/src/machine/machine_atsame54p20.go index 922ee31474..d7cc31f62d 100644 --- a/src/machine/machine_atsame54p20.go +++ b/src/machine/machine_atsame54p20.go @@ -20,14 +20,14 @@ var ( sercomI2CM6 = &I2C{Bus: sam.SERCOM6_I2CM, SERCOM: 6} sercomI2CM7 = &I2C{Bus: sam.SERCOM7_I2CM, SERCOM: 7} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} - sercomSPIM6 = SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} - sercomSPIM7 = SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM6 = &SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} + sercomSPIM7 = &SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} ) // setSERCOMClockGenerator sets the GCLK for sercom diff --git a/src/machine/machine_esp32.go b/src/machine/machine_esp32.go index 50ff74d672..237a1292c2 100644 --- a/src/machine/machine_esp32.go +++ b/src/machine/machine_esp32.go @@ -334,8 +334,8 @@ type SPI struct { var ( // SPI0 and SPI1 are reserved for use by the caching system etc. - SPI2 = SPI{esp.SPI2} - SPI3 = SPI{esp.SPI3} + SPI2 = &SPI{esp.SPI2} + SPI3 = &SPI{esp.SPI3} ) // SPIConfig configures a SPI peripheral on the ESP32. Make sure to set at least diff --git a/src/machine/machine_esp32c3_spi.go b/src/machine/machine_esp32c3_spi.go index ecb1923dee..aec3ca77a8 100644 --- a/src/machine/machine_esp32c3_spi.go +++ b/src/machine/machine_esp32c3_spi.go @@ -52,7 +52,7 @@ type SPI struct { var ( // SPI0 and SPI1 are reserved for use by the caching system etc. - SPI2 = SPI{esp.SPI2} + SPI2 = &SPI{esp.SPI2} ) // SPIConfig is used to store config info for SPI. diff --git a/src/machine/machine_generic.go b/src/machine/machine_generic.go index 1c8512244b..8bfa097f03 100644 --- a/src/machine/machine_generic.go +++ b/src/machine/machine_generic.go @@ -248,14 +248,14 @@ var ( sercomI2CM6 = &I2C{6} sercomI2CM7 = &I2C{7} - sercomSPIM0 = SPI{0} - sercomSPIM1 = SPI{1} - sercomSPIM2 = SPI{2} - sercomSPIM3 = SPI{3} - sercomSPIM4 = SPI{4} - sercomSPIM5 = SPI{5} - sercomSPIM6 = SPI{6} - sercomSPIM7 = SPI{7} + sercomSPIM0 = &SPI{0} + sercomSPIM1 = &SPI{1} + sercomSPIM2 = &SPI{2} + sercomSPIM3 = &SPI{3} + sercomSPIM4 = &SPI{4} + sercomSPIM5 = &SPI{5} + sercomSPIM6 = &SPI{6} + sercomSPIM7 = &SPI{7} ) // GetRNG returns 32 bits of random data from the WASI random source. From 04c7057ea61debe3f32ae0a6f623845a81867654 Mon Sep 17 00:00:00 2001 From: soypat Date: Tue, 4 Mar 2025 09:10:48 -0300 Subject: [PATCH 412/444] add goroutine benchmark to examples --- src/examples/bench-goro/bench.go | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/examples/bench-goro/bench.go diff --git a/src/examples/bench-goro/bench.go b/src/examples/bench-goro/bench.go new file mode 100644 index 0000000000..db08a03c19 --- /dev/null +++ b/src/examples/bench-goro/bench.go @@ -0,0 +1,40 @@ +package main + +import ( + "machine" + "runtime" + "sync" + "time" +) + +const N = 500000 +const Ngoro = 4 + +func main() { + start := time.Now() + var wg sync.WaitGroup + wg.Add(Ngoro) + for i := 0; i < Ngoro; i++ { + go adder(&wg, N) + } + wg.Wait() + elapsed := time.Since(start) + goroutineCtxSwitchOverhead := (elapsed / (Ngoro * N)).String() + + elapsedstr := elapsed.String() + machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput}) + for { + println("bench:", elapsedstr, "goroutine ctx switch:", goroutineCtxSwitchOverhead) + machine.LED.High() + time.Sleep(elapsed) + machine.LED.Low() + time.Sleep(elapsed) + } +} + +func adder(wg *sync.WaitGroup, num int) { + for i := 0; i < num; i++ { + runtime.Gosched() + } + wg.Done() +} From ff2a79de7c6d9d1f86910f95dffc6d560ebb9257 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 10 Mar 2025 11:51:38 +0100 Subject: [PATCH 413/444] riscv-qemu: increase stack size Bumping the stack size to 8kb (previous was 4kb) gets most remaining tests to pass on baremetal. --- GNUmakefile | 7 ------- targets/riscv-qemu.json | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 978d7d9817..bcbc21b263 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -431,17 +431,10 @@ TEST_PACKAGES_NONWASM = \ # (just like wasm). # * picolibc math functions apparently are less precise, the math package # fails on baremetal. -# * Some packages fail or hang for an unknown reason, this should be -# investigated and fixed. TEST_PACKAGES_BAREMETAL = $(filter-out $(TEST_PACKAGES_NONBAREMETAL), $(TEST_PACKAGES_FAST)) TEST_PACKAGES_NONBAREMETAL = \ $(TEST_PACKAGES_NONWASM) \ - crypto/elliptic \ math \ - reflect \ - encoding/asn1 \ - encoding/base32 \ - go/ast \ $(nil) # Report platforms on which each standard library package is known to pass tests diff --git a/targets/riscv-qemu.json b/targets/riscv-qemu.json index 84890308ba..90f1c312fc 100644 --- a/targets/riscv-qemu.json +++ b/targets/riscv-qemu.json @@ -2,7 +2,7 @@ "inherits": ["riscv32"], "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", "build-tags": ["virt", "qemu"], - "default-stack-size": 4096, + "default-stack-size": 8192, "linkerscript": "targets/riscv-qemu.ld", "emulator": "qemu-system-riscv32 -machine virt -nographic -bios none -device virtio-rng-device -kernel {}" } From 4f7c64cb24853027e28fe0777c3bd0974297389a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=83=BC=E3=82=8B=E3=81=A9=E3=82=93?= <82225965+rdon-key@users.noreply.github.com> Date: Fri, 14 Mar 2025 00:39:43 +0900 Subject: [PATCH 414/444] =?UTF-8?q?fix(rp2040):=20replace=20loop=20counter?= =?UTF-8?q?=20with=20hw=20timer=20for=20USB=20SetAddressReq=E2=80=A6=20(#4?= =?UTF-8?q?796)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(rp2040/rp2350): replace loop counter with hw timer for USB SetAddressRequest timeout. * fix code format. --------- Co-authored-by: rdon --- src/machine/machine_rp2040_usb.go | 17 +++++++++-------- src/machine/machine_rp2350_usb.go | 17 +++++++++-------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/machine/machine_rp2040_usb.go b/src/machine/machine_rp2040_usb.go index ac7df98204..d69cd6ad5f 100644 --- a/src/machine/machine_rp2040_usb.go +++ b/src/machine/machine_rp2040_usb.go @@ -169,21 +169,22 @@ func initEndpoint(ep, config uint32) { } func handleUSBSetAddress(setup usb.Setup) bool { - sendUSBPacket(0, []byte{}, 0) + // Using 570μs timeout which is exactly the same as SAMD21. + const ackTimeout = 570 - // last, set the device address to that requested by host - // wait for transfer to complete - timeout := 3000 rp.USBCTRL_REGS.SIE_STATUS.Set(rp.USBCTRL_REGS_SIE_STATUS_ACK_REC) + sendUSBPacket(0, []byte{}, 0) + + // Wait for transfer to complete with a timeout. + t := timer.timeElapsed() for (rp.USBCTRL_REGS.SIE_STATUS.Get() & rp.USBCTRL_REGS_SIE_STATUS_ACK_REC) == 0 { - timeout-- - if timeout == 0 { - return true + if dt := timer.timeElapsed() - t; dt >= ackTimeout { + return false } } + // Set the device address to that requested by host. rp.USBCTRL_REGS.ADDR_ENDP.Set(uint32(setup.WValueL) & rp.USBCTRL_REGS_ADDR_ENDP_ADDRESS_Msk) - return true } diff --git a/src/machine/machine_rp2350_usb.go b/src/machine/machine_rp2350_usb.go index 48fbbcbd05..b42ce09cca 100644 --- a/src/machine/machine_rp2350_usb.go +++ b/src/machine/machine_rp2350_usb.go @@ -172,21 +172,22 @@ func initEndpoint(ep, config uint32) { } func handleUSBSetAddress(setup usb.Setup) bool { - sendUSBPacket(0, []byte{}, 0) + // Using 570μs timeout which is exactly the same as SAMD21. - // last, set the device address to that requested by host - // wait for transfer to complete - timeout := 3000 + const ackTimeout = 570 rp.USB.SIE_STATUS.Set(rp.USB_SIE_STATUS_ACK_REC) + sendUSBPacket(0, []byte{}, 0) + + // Wait for transfer to complete with a timeout. + t := timer.timeElapsed() for (rp.USB.SIE_STATUS.Get() & rp.USB_SIE_STATUS_ACK_REC) == 0 { - timeout-- - if timeout == 0 { - return true + if dt := timer.timeElapsed() - t; dt >= ackTimeout { + return false } } + // Set the device address to that requested by host. rp.USB.ADDR_ENDP.Set(uint32(setup.WValueL) & rp.USB_ADDR_ENDP_ADDRESS_Msk) - return true } From 4768c7d4313a6b8cf4c2f3eae91e9b6f8af3a439 Mon Sep 17 00:00:00 2001 From: Michael Smith <2015782+mikesmitty@users.noreply.github.com> Date: Sun, 16 Mar 2025 04:45:53 -0400 Subject: [PATCH 415/444] machine/rp2350: add flash support for rp2350 (#4803) * machine/rp2350: add flash support for rp2350 * combine duplicate files * clean things up and group by source file * add stubbed out xip cache clean func if needed in the future * update flash_enable_xip_via_boot2 * remove unused macros and fix inconsistent formatting * make flash size configurable like rp2040 * add missing flash size configs * retain big Go CGo compatibility per #4103 * clarify CS0_SIZE source and remove single-use typedef --- src/machine/flash.go | 2 +- src/machine/machine_rp2350_rom.go | 411 +++++++++++++++++- src/machine/machine_rp2_2350.go | 4 - ...e_rp2040_flash.go => machine_rp2_flash.go} | 2 +- targets/pga2350.json | 5 +- targets/pico2.json | 5 +- targets/rp2350.json | 3 + targets/rp2350.ld | 4 +- targets/tiny2350.json | 3 + 9 files changed, 427 insertions(+), 12 deletions(-) rename src/machine/{machine_rp2040_flash.go => machine_rp2_flash.go} (99%) diff --git a/src/machine/flash.go b/src/machine/flash.go index da832afdb8..c89c091b91 100644 --- a/src/machine/flash.go +++ b/src/machine/flash.go @@ -1,4 +1,4 @@ -//go:build nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32l4 || stm32wlx || atsamd21 || atsamd51 || atsame5x || rp2040 +//go:build nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32l4 || stm32wlx || atsamd21 || atsamd51 || atsame5x || rp2040 || rp2350 package machine diff --git a/src/machine/machine_rp2350_rom.go b/src/machine/machine_rp2350_rom.go index 4f3d4762a8..665464ae69 100644 --- a/src/machine/machine_rp2350_rom.go +++ b/src/machine/machine_rp2350_rom.go @@ -2,7 +2,10 @@ package machine -import () +import ( + "runtime/interrupt" + "unsafe" +) /* typedef unsigned char uint8_t; @@ -10,11 +13,82 @@ typedef unsigned short uint16_t; typedef unsigned long uint32_t; typedef unsigned long size_t; typedef unsigned long uintptr_t; +typedef long int intptr_t; + +typedef const volatile uint16_t io_ro_16; +typedef const volatile uint32_t io_ro_32; +typedef volatile uint16_t io_rw_16; +typedef volatile uint32_t io_rw_32; +typedef volatile uint32_t io_wo_32; #define false 0 #define true 1 typedef int bool; +#define ram_func __attribute__((section(".ramfuncs"),noinline)) + +typedef void (*flash_exit_xip_fn)(void); +typedef void (*flash_flush_cache_fn)(void); +typedef void (*flash_connect_internal_fn)(void); +typedef void (*flash_range_erase_fn)(uint32_t, size_t, uint32_t, uint16_t); +typedef void (*flash_range_program_fn)(uint32_t, const uint8_t*, size_t); +static inline __attribute__((always_inline)) void __compiler_memory_barrier(void) { + __asm__ volatile ("" : : : "memory"); +} + +// https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf +// 13.9. Predefined OTP Data Locations +// OTP_DATA: FLASH_DEVINFO Register + +#define OTP_DATA_FLASH_DEVINFO_CS0_SIZE_BITS 0x0F00 +#define OTP_DATA_FLASH_DEVINFO_CS0_SIZE_LSB 8 +#define OTP_DATA_FLASH_DEVINFO_CS1_SIZE_BITS 0xF000 +#define OTP_DATA_FLASH_DEVINFO_CS1_SIZE_LSB 12 + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2350/hardware_regs/include/hardware/regs/addressmap.h + +#define REG_ALIAS_RW_BITS (0x0 << 12) +#define REG_ALIAS_XOR_BITS (0x1 << 12) +#define REG_ALIAS_SET_BITS (0x2 << 12) +#define REG_ALIAS_CLR_BITS (0x3 << 12) + +#define XIP_BASE 0x10000000 +#define XIP_QMI_BASE 0x400d0000 +#define IO_QSPI_BASE 0x40030000 +#define BOOTRAM_BASE 0x400e0000 + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/hardware_base/include/hardware/address_mapped.h + +#define hw_alias_check_addr(addr) ((uintptr_t)(addr)) +#define hw_set_alias_untyped(addr) ((void *)(REG_ALIAS_SET_BITS + hw_alias_check_addr(addr))) +#define hw_clear_alias_untyped(addr) ((void *)(REG_ALIAS_CLR_BITS + hw_alias_check_addr(addr))) +#define hw_xor_alias_untyped(addr) ((void *)(REG_ALIAS_XOR_BITS + hw_alias_check_addr(addr))) + +__attribute__((always_inline)) +static void hw_set_bits(io_rw_32 *addr, uint32_t mask) { + *(io_rw_32 *) hw_set_alias_untyped((volatile void *) addr) = mask; +} + +__attribute__((always_inline)) +static void hw_clear_bits(io_rw_32 *addr, uint32_t mask) { + *(io_rw_32 *) hw_clear_alias_untyped((volatile void *) addr) = mask; +} + +__attribute__((always_inline)) +static void hw_xor_bits(io_rw_32 *addr, uint32_t mask) { + *(io_rw_32 *) hw_xor_alias_untyped((volatile void *) addr) = mask; +} + +__attribute__((always_inline)) +static void hw_write_masked(io_rw_32 *addr, uint32_t values, uint32_t write_mask) { + hw_xor_bits(addr, (*addr ^ values) & write_mask); +} + + // https://github.com/raspberrypi/pico-sdk // src/rp2_common/pico_platform_compiler/include/pico/platform/compiler.h @@ -88,6 +162,7 @@ static bool pico_processor_state_is_nonsecure(void) { #define BOOTROM_VTABLE_OFFSET 0x00 #define BOOTROM_TABLE_LOOKUP_OFFSET (BOOTROM_FUNC_TABLE_OFFSET + BOOTROM_WELL_KNOWN_PTR_SIZE) + // https://github.com/raspberrypi/pico-sdk // src/common/boot_picoboot_headers/include/boot/picoboot_constants.h @@ -101,6 +176,8 @@ static bool pico_processor_state_is_nonsecure(void) { #define RT_FLAG_FUNC_ARM_SEC 0x0004 #define RT_FLAG_FUNC_ARM_NONSEC 0x0010 +#define RT_FLAG_DATA 0x0040 + // https://github.com/raspberrypi/pico-sdk // src/rp2_common/pico_bootrom/include/pico/bootrom.h @@ -119,6 +196,11 @@ static void *rom_func_lookup_inline(uint32_t code) { } } +__attribute__((always_inline)) +static void *rom_data_lookup_inline(uint32_t code) { + rom_table_lookup_fn rom_table_lookup = (rom_table_lookup_fn) (uintptr_t)*(uint16_t*)(BOOTROM_TABLE_LOOKUP_OFFSET); + return rom_table_lookup(code, RT_FLAG_DATA); +} typedef int (*rom_reboot_fn)(uint32_t flags, uint32_t delay_ms, uint32_t p0, uint32_t p1); @@ -132,7 +214,6 @@ int rom_reboot(uint32_t flags, uint32_t delay_ms, uint32_t p0, uint32_t p1) { // https://github.com/raspberrypi/pico-sdk // src/rp2_common/pico_bootrom/bootrom.c - void reset_usb_boot(uint32_t usb_activity_gpio_pin_mask, uint32_t disable_interface_mask) { uint32_t flags = disable_interface_mask; if (usb_activity_gpio_pin_mask) { @@ -145,9 +226,335 @@ void reset_usb_boot(uint32_t usb_activity_gpio_pin_mask, uint32_t disable_interf } +// https://github.com/raspberrypi/pico-sdk +// src/rp2350/hardware_regs/include/hardware/regs/qmi.h + +#define QMI_DIRECT_CSR_EN_BITS 0x00000001 +#define QMI_DIRECT_CSR_RXEMPTY_BITS 0x00010000 +#define QMI_DIRECT_CSR_TXFULL_BITS 0x00000400 +#define QMI_M1_WFMT_RESET 0x00001000 +#define QMI_M1_WCMD_RESET 0x0000a002 + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2350/hardware_regs/include/hardware/regs/io_qspi.h + +#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS 0x00003000 +#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_LSB 12 +#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_LOW 0x2 +#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_HIGH 0x3 + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2350/hardware_structs/include/hardware/structs/io_qspi.h + +typedef struct { + io_rw_32 inte; // IO_QSPI_PROC0_INTE + io_rw_32 intf; // IO_QSPI_PROC0_INTF + io_ro_32 ints; // IO_QSPI_PROC0_INTS +} io_qspi_irq_ctrl_hw_t; + +typedef struct { + io_ro_32 status; // IO_QSPI_GPIO_QSPI_SCLK_STATUS + io_rw_32 ctrl; // IO_QSPI_GPIO_QSPI_SCLK_CTRL +} io_qspi_status_ctrl_hw_t; + +typedef struct { + io_ro_32 usbphy_dp_status; // IO_QSPI_USBPHY_DP_STATUS + io_rw_32 usbphy_dp_ctrl; // IO_QSPI_USBPHY_DP_CTRL + io_ro_32 usbphy_dm_status; // IO_QSPI_USBPHY_DM_STATUS + io_rw_32 usbphy_dm_ctrl; // IO_QSPI_USBPHY_DM_CTRL + io_qspi_status_ctrl_hw_t io[6]; + uint32_t _pad0[112]; + io_ro_32 irqsummary_proc0_secure; // IO_QSPI_IRQSUMMARY_PROC0_SECURE + io_ro_32 irqsummary_proc0_nonsecure; // IO_QSPI_IRQSUMMARY_PROC0_NONSECURE + io_ro_32 irqsummary_proc1_secure; // IO_QSPI_IRQSUMMARY_PROC1_SECURE + io_ro_32 irqsummary_proc1_nonsecure; // IO_QSPI_IRQSUMMARY_PROC1_NONSECURE + io_ro_32 irqsummary_dormant_wake_secure; // IO_QSPI_IRQSUMMARY_DORMANT_WAKE_SECURE + io_ro_32 irqsummary_dormant_wake_nonsecure; // IO_QSPI_IRQSUMMARY_DORMANT_WAKE_NONSECURE + io_rw_32 intr; // IO_QSPI_INTR + + union { + struct { + io_qspi_irq_ctrl_hw_t proc0_irq_ctrl; + io_qspi_irq_ctrl_hw_t proc1_irq_ctrl; + io_qspi_irq_ctrl_hw_t dormant_wake_irq_ctrl; + }; + io_qspi_irq_ctrl_hw_t irq_ctrl[3]; + }; +} io_qspi_hw_t; + +#define io_qspi_hw ((io_qspi_hw_t *)IO_QSPI_BASE) + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2350/hardware_structs/include/hardware/structs/qmi.h + +typedef struct { + io_rw_32 timing; // QMI_M0_TIMING + io_rw_32 rfmt; // QMI_M0_RFMT + io_rw_32 rcmd; // QMI_M0_RCMD + io_rw_32 wfmt; // QMI_M0_WFMT + io_rw_32 wcmd; // QMI_M0_WCMD +} qmi_mem_hw_t; + +typedef struct { + io_rw_32 direct_csr; // QMI_DIRECT_CSR + io_wo_32 direct_tx; // QMI_DIRECT_TX + io_ro_32 direct_rx; // QMI_DIRECT_RX + qmi_mem_hw_t m[2]; + io_rw_32 atrans[8]; // QMI_ATRANS0 +} qmi_hw_t; + +#define qmi_hw ((qmi_hw_t *)XIP_QMI_BASE) + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/hardware_xip_cache/include/hardware/xip_cache.h + +// Noop unless using XIP Cache-as-SRAM +// Non-noop version in src/rp2_common/hardware_xip_cache/xip_cache.c +static inline void xip_cache_clean_all(void) {} + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/hardware_flash/include/hardware/flash.h + +#define FLASH_PAGE_SIZE (1u << 8) +#define FLASH_SECTOR_SIZE (1u << 12) +#define FLASH_BLOCK_SIZE (1u << 16) + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/hardware_flash/flash.c + +#define BOOT2_SIZE_WORDS 64 +#define FLASH_BLOCK_ERASE_CMD 0xd8 + +static uint32_t boot2_copyout[BOOT2_SIZE_WORDS]; +static bool boot2_copyout_valid = false; + +static ram_func void flash_init_boot2_copyout(void) { + if (boot2_copyout_valid) + return; + for (int i = 0; i < BOOT2_SIZE_WORDS; ++i) + boot2_copyout[i] = ((uint32_t *)BOOTRAM_BASE)[i]; + __compiler_memory_barrier(); + boot2_copyout_valid = true; +} + +static ram_func void flash_enable_xip_via_boot2(void) { + ((void (*)(void))((intptr_t)boot2_copyout+1))(); +} + +// This is a static symbol because the layout of FLASH_DEVINFO is liable to change from device to +// device, so fields must have getters/setters. +static io_rw_16 * ram_func flash_devinfo_ptr(void) { + // Note the lookup returns a pointer to a 32-bit pointer literal in the ROM + io_rw_16 **p = (io_rw_16 **) rom_data_lookup_inline(ROM_DATA_FLASH_DEVINFO16_PTR); + return *p; +} + +// This is a RAM function because may be called during flash programming to enable save/restore of +// QMI window 1 registers on RP2350: +uint8_t ram_func flash_devinfo_get_cs_size(uint8_t cs) { + io_ro_16 *devinfo = (io_ro_16 *) flash_devinfo_ptr(); + if (cs == 0u) { + return (uint8_t) ( + (*devinfo & OTP_DATA_FLASH_DEVINFO_CS0_SIZE_BITS) >> OTP_DATA_FLASH_DEVINFO_CS0_SIZE_LSB + ); + } else { + return (uint8_t) ( + (*devinfo & OTP_DATA_FLASH_DEVINFO_CS1_SIZE_BITS) >> OTP_DATA_FLASH_DEVINFO_CS1_SIZE_LSB + ); + } +} + +// This is specifically for saving/restoring the registers modified by RP2350 +// flash_exit_xip() ROM func, not the entirety of the QMI window state. +typedef struct flash_rp2350_qmi_save_state { + uint32_t timing; + uint32_t rcmd; + uint32_t rfmt; +} flash_rp2350_qmi_save_state_t; + +static ram_func void flash_rp2350_save_qmi_cs1(flash_rp2350_qmi_save_state_t *state) { + state->timing = qmi_hw->m[1].timing; + state->rcmd = qmi_hw->m[1].rcmd; + state->rfmt = qmi_hw->m[1].rfmt; +} + +static ram_func void flash_rp2350_restore_qmi_cs1(const flash_rp2350_qmi_save_state_t *state) { + if (flash_devinfo_get_cs_size(1) == 0) { + // Case 1: The RP2350 ROM sets QMI to a clean (03h read) configuration + // during flash_exit_xip(), even though when CS1 is not enabled via + // FLASH_DEVINFO it does not issue an XIP exit sequence to CS1. In + // this case, restore the original register config for CS1 as it is + // still the correct config. + qmi_hw->m[1].timing = state->timing; + qmi_hw->m[1].rcmd = state->rcmd; + qmi_hw->m[1].rfmt = state->rfmt; + } else { + // Case 2: If RAM is attached to CS1, and the ROM has issued an XIP + // exit sequence to it, then the ROM re-initialisation of the QMI + // registers has actually not gone far enough. The old XIP write mode + // is no longer valid when the QSPI RAM is returned to a serial + // command state. Restore the default 02h serial write command config. + qmi_hw->m[1].wfmt = QMI_M1_WFMT_RESET; + qmi_hw->m[1].wcmd = QMI_M1_WCMD_RESET; + } +} + +void ram_func flash_cs_force(bool high) { + uint32_t field_val = high ? + IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_HIGH : + IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_LOW; + hw_write_masked(&io_qspi_hw->io[1].ctrl, + field_val << IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_LSB, + IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS + ); +} + +// Adapted from flash_range_program() +void ram_func flash_range_write(uint32_t offset, const uint8_t *data, size_t count) { + flash_connect_internal_fn flash_connect_internal_func = (flash_connect_internal_fn)rom_func_lookup_inline(ROM_FUNC_CONNECT_INTERNAL_FLASH); + flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_EXIT_XIP); + flash_range_program_fn flash_range_program_func = (flash_range_program_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_RANGE_PROGRAM); + flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); + flash_init_boot2_copyout(); + xip_cache_clean_all(); + flash_rp2350_qmi_save_state_t qmi_save; + flash_rp2350_save_qmi_cs1(&qmi_save); + + __compiler_memory_barrier(); + + flash_connect_internal_func(); + flash_exit_xip_func(); + flash_range_program_func(offset, data, count); + flash_flush_cache_func(); // Note this is needed to remove CSn IO force as well as cache flushing + flash_enable_xip_via_boot2(); + flash_rp2350_restore_qmi_cs1(&qmi_save); +} + +// Adapted from flash_range_erase() +void ram_func flash_erase_blocks(uint32_t offset, size_t count) { + flash_connect_internal_fn flash_connect_internal_func = (flash_connect_internal_fn)rom_func_lookup_inline(ROM_FUNC_CONNECT_INTERNAL_FLASH); + flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_EXIT_XIP); + flash_range_erase_fn flash_range_erase_func = (flash_range_erase_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_RANGE_ERASE); + flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); + flash_init_boot2_copyout(); + // Commit any pending writes to external RAM, to avoid losing them in the subsequent flush: + xip_cache_clean_all(); + flash_rp2350_qmi_save_state_t qmi_save; + flash_rp2350_save_qmi_cs1(&qmi_save); + + // No flash accesses after this point + __compiler_memory_barrier(); + + flash_connect_internal_func(); + flash_exit_xip_func(); + flash_range_erase_func(offset, count, FLASH_BLOCK_SIZE, FLASH_BLOCK_ERASE_CMD); + flash_flush_cache_func(); // Note this is needed to remove CSn IO force as well as cache flushing + flash_enable_xip_via_boot2(); + flash_rp2350_restore_qmi_cs1(&qmi_save); +} + +void ram_func flash_do_cmd(const uint8_t *txbuf, uint8_t *rxbuf, size_t count) { + flash_connect_internal_fn flash_connect_internal_func = (flash_connect_internal_fn)rom_func_lookup_inline(ROM_FUNC_CONNECT_INTERNAL_FLASH); + flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_EXIT_XIP); + flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); + flash_init_boot2_copyout(); + xip_cache_clean_all(); + + flash_rp2350_qmi_save_state_t qmi_save; + flash_rp2350_save_qmi_cs1(&qmi_save); + + __compiler_memory_barrier(); + flash_connect_internal_func(); + flash_exit_xip_func(); + + flash_cs_force(0); + size_t tx_remaining = count; + size_t rx_remaining = count; + + // QMI version -- no need to bound FIFO contents as QMI stalls on full DIRECT_RX. + hw_set_bits(&qmi_hw->direct_csr, QMI_DIRECT_CSR_EN_BITS); + while (tx_remaining || rx_remaining) { + uint32_t flags = qmi_hw->direct_csr; + bool can_put = !(flags & QMI_DIRECT_CSR_TXFULL_BITS); + bool can_get = !(flags & QMI_DIRECT_CSR_RXEMPTY_BITS); + if (can_put && tx_remaining) { + qmi_hw->direct_tx = *txbuf++; + --tx_remaining; + } + if (can_get && rx_remaining) { + *rxbuf++ = (uint8_t)qmi_hw->direct_rx; + --rx_remaining; + } + } + hw_clear_bits(&qmi_hw->direct_csr, QMI_DIRECT_CSR_EN_BITS); + + flash_cs_force(1); + + flash_flush_cache_func(); + flash_enable_xip_via_boot2(); + flash_rp2350_restore_qmi_cs1(&qmi_save); +} + */ import "C" func enterBootloader() { C.reset_usb_boot(0, 0) } + +func doFlashCommand(tx []byte, rx []byte) error { + if len(tx) != len(rx) { + return errFlashInvalidWriteLength + } + + C.flash_do_cmd( + (*C.uint8_t)(unsafe.Pointer(&tx[0])), + (*C.uint8_t)(unsafe.Pointer(&rx[0])), + C.ulong(len(tx))) + + return nil +} + +// Flash related code +const memoryStart = C.XIP_BASE // memory start for purpose of erase + +func (f flashBlockDevice) writeAt(p []byte, off int64) (n int, err error) { + if writeAddress(off)+uintptr(C.XIP_BASE) > FlashDataEnd() { + return 0, errFlashCannotWritePastEOF + } + + state := interrupt.Disable() + defer interrupt.Restore(state) + + // rp2350 writes to offset, not actual address + // e.g. real address 0x10003000 is written to at + // 0x00003000 + address := writeAddress(off) + padded := flashPad(p, int(f.WriteBlockSize())) + + C.flash_range_write(C.uint32_t(address), + (*C.uint8_t)(unsafe.Pointer(&padded[0])), + C.ulong(len(padded))) + + return len(padded), nil +} + +func (f flashBlockDevice) eraseBlocks(start, length int64) error { + address := writeAddress(start * f.EraseBlockSize()) + if address+uintptr(C.XIP_BASE) > FlashDataEnd() { + return errFlashCannotErasePastEOF + } + + state := interrupt.Disable() + defer interrupt.Restore(state) + + C.flash_erase_blocks(C.uint32_t(address), C.ulong(length*f.EraseBlockSize())) + + return nil +} diff --git a/src/machine/machine_rp2_2350.go b/src/machine/machine_rp2_2350.go index d259e750d8..a6a6aa2eb1 100644 --- a/src/machine/machine_rp2_2350.go +++ b/src/machine/machine_rp2_2350.go @@ -212,10 +212,6 @@ func (clks *clocksType) initTicks() { rp.TICKS.SetTIMER0_CTRL_ENABLE(1) } -func EnterBootloader() { - enterBootloader() -} - // startTick starts the watchdog tick. // On RP2040, the watchdog contained a tick generator used to generate a 1μs tick for the watchdog. This was also // distributed to the system timer. On RP2350, the watchdog instead takes a tick input from the system-level ticks block. See Section 8.5. diff --git a/src/machine/machine_rp2040_flash.go b/src/machine/machine_rp2_flash.go similarity index 99% rename from src/machine/machine_rp2040_flash.go rename to src/machine/machine_rp2_flash.go index 1317c0926a..45263dfac3 100644 --- a/src/machine/machine_rp2040_flash.go +++ b/src/machine/machine_rp2_flash.go @@ -1,4 +1,4 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine diff --git a/targets/pga2350.json b/targets/pga2350.json index 3ff72c26c2..5afe89ec07 100644 --- a/targets/pga2350.json +++ b/targets/pga2350.json @@ -1,4 +1,7 @@ { "inherits": ["rp2350b"], - "build-tags": ["pga2350"] + "build-tags": ["pga2350"], + "ldflags": [ + "--defsym=__flash_size=16M" + ] } \ No newline at end of file diff --git a/targets/pico2.json b/targets/pico2.json index dfdac71ee0..af156d54d8 100644 --- a/targets/pico2.json +++ b/targets/pico2.json @@ -4,5 +4,8 @@ ], "build-tags": ["pico2"], "serial-port": ["2e8a:000A"], - "default-stack-size": 8192 + "default-stack-size": 8192, + "ldflags": [ + "--defsym=__flash_size=4M" + ] } diff --git a/targets/rp2350.json b/targets/rp2350.json index 9070560bcb..0487aa14d9 100644 --- a/targets/rp2350.json +++ b/targets/rp2350.json @@ -12,6 +12,9 @@ "src/device/rp/rp2350.s", "targets/rp2350_embedded_block.s" ], + "ldflags": [ + "--defsym=__flash_size=2M" + ], "linkerscript": "targets/rp2350.ld", "openocd-interface": "picoprobe", "openocd-transport": "swd", diff --git a/targets/rp2350.ld b/targets/rp2350.ld index dbe495cf7c..5296a1fb12 100644 --- a/targets/rp2350.ld +++ b/targets/rp2350.ld @@ -2,7 +2,7 @@ MEMORY { /* 2MiB safe default. */ - FLASH : ORIGIN = 0x10000000, LENGTH = 2048k + FLASH : ORIGIN = 0x10000000, LENGTH = __flash_size /* RAM consists of 8 banks, SRAM0..SRAM7 with striped mapping. */ SRAM : ORIGIN = 0x20000000, LENGTH = 512k /* Banks 8 and 9 use direct mapping which can be @@ -10,7 +10,7 @@ MEMORY i.e: Separate stacks for core0 and core1. */ SRAM4 : ORIGIN = 0x20080000, LENGTH = 4k SRAM5 : ORIGIN = 0x20081000, LENGTH = 4k - FLASH_TEXT (rx) : ORIGIN = 0x10000000, LENGTH = 2048k + FLASH_TEXT (rx) : ORIGIN = 0x10000000, LENGTH = __flash_size RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 512k } diff --git a/targets/tiny2350.json b/targets/tiny2350.json index 185be33202..660e096468 100644 --- a/targets/tiny2350.json +++ b/targets/tiny2350.json @@ -3,5 +3,8 @@ "rp2350" ], "build-tags": ["tiny2350"], + "ldflags": [ + "--defsym=__flash_size=4M" + ], "serial-port": ["2e8a:000f"] } From bbb2b0c95bda6e08f6e5acb28a92c6f8f53db85a Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 17 Mar 2025 09:07:41 +0100 Subject: [PATCH 416/444] builder: use a separate module for command-line set strings Strings that are set via the command line (as in -ldflags="-X ...") are now created in a separate module to avoid type renaming issues. LLVM sometimes renames types or merges types that are structurally the same, and putting these strings in a separate module avoids this issue (and lets llvm.LinkModules deal with the difference). This fixes https://github.com/tinygo-org/tinygo/issues/4810. --- builder/build.go | 81 ++++++++++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/builder/build.go b/builder/build.go index 2d7156b9b7..c2c712456e 100644 --- a/builder/build.go +++ b/builder/build.go @@ -449,8 +449,15 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe if global.IsNil() { return errors.New("global not found: " + globalName) } + globalType := global.GlobalValueType() + if globalType.TypeKind() != llvm.StructTypeKind || globalType.StructName() != "runtime._string" { + // Verify this is indeed a string. This is needed so + // that makeGlobalsModule can just create the right + // globals of string type without checking. + return fmt.Errorf("%s: not a string", globalName) + } name := global.Name() - newGlobal := llvm.AddGlobal(mod, global.GlobalValueType(), name+".tmp") + newGlobal := llvm.AddGlobal(mod, globalType, name+".tmp") global.ReplaceAllUsesWith(newGlobal) global.EraseFromParentAsGlobal() newGlobal.SetName(name) @@ -538,6 +545,15 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } } + // Insert values from -ldflags="-X ..." into the IR. + // This is a separate module, so that the "runtime._string" type + // doesn't need to match precisely. LLVM tends to rename that type + // sometimes, leading to errors. But linking in a separate module + // works fine. See: + // https://github.com/tinygo-org/tinygo/issues/4810 + globalsMod := makeGlobalsModule(ctx, globalValues, machine) + llvm.LinkModules(mod, globalsMod) + // Create runtime.initAll function that calls the runtime // initializer of each package. llvmInitFn := mod.NamedFunction("runtime.initAll") @@ -590,7 +606,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe // Run all optimization passes, which are much more effective now // that the optimizer can see the whole program at once. - err := optimizeProgram(mod, config, globalValues) + err := optimizeProgram(mod, config) if err != nil { return err } @@ -1145,7 +1161,7 @@ func createEmbedObjectFile(data, hexSum, sourceFile, sourceDir, tmpdir string, c // optimizeProgram runs a series of optimizations and transformations that are // needed to convert a program to its final form. Some transformations are not // optional and must be run as the compiler expects them to run. -func optimizeProgram(mod llvm.Module, config *compileopts.Config, globalValues map[string]map[string]string) error { +func optimizeProgram(mod llvm.Module, config *compileopts.Config) error { err := interp.Run(mod, config.Options.InterpTimeout, config.DumpSSA()) if err != nil { return err @@ -1163,12 +1179,6 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config, globalValues m } } - // Insert values from -ldflags="-X ..." into the IR. - err = setGlobalValues(mod, globalValues) - if err != nil { - return err - } - // Run most of the whole-program optimizations (including the whole // O0/O1/O2/Os/Oz optimization pipeline). errs := transform.Optimize(mod, config) @@ -1182,10 +1192,19 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config, globalValues m return nil } -// setGlobalValues sets the global values from the -ldflags="-X ..." compiler -// option in the given module. An error may be returned if the global is not of -// the expected type. -func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) error { +func makeGlobalsModule(ctx llvm.Context, globals map[string]map[string]string, machine llvm.TargetMachine) llvm.Module { + mod := ctx.NewModule("cmdline-globals") + targetData := machine.CreateTargetData() + defer targetData.Dispose() + mod.SetDataLayout(targetData.String()) + + stringType := ctx.StructCreateNamed("runtime._string") + uintptrType := ctx.IntType(targetData.PointerSize() * 8) + stringType.StructSetBody([]llvm.Type{ + llvm.PointerType(ctx.Int8Type(), 0), + uintptrType, + }, false) + var pkgPaths []string for pkgPath := range globals { pkgPaths = append(pkgPaths, pkgPath) @@ -1201,24 +1220,6 @@ func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) erro for _, name := range names { value := pkg[name] globalName := pkgPath + "." + name - global := mod.NamedGlobal(globalName) - if global.IsNil() || !global.Initializer().IsNil() { - // The global either does not exist (optimized away?) or has - // some value, in which case it has already been initialized at - // package init time. - continue - } - - // A strin is a {ptr, len} pair. We need these types to build the - // initializer. - initializerType := global.GlobalValueType() - if initializerType.TypeKind() != llvm.StructTypeKind || initializerType.StructName() == "" { - return fmt.Errorf("%s: not a string", globalName) - } - elementTypes := initializerType.StructElementTypes() - if len(elementTypes) != 2 { - return fmt.Errorf("%s: not a string", globalName) - } // Create a buffer for the string contents. bufInitializer := mod.Context().ConstString(value, false) @@ -1229,22 +1230,20 @@ func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) erro buf.SetLinkage(llvm.PrivateLinkage) // Create the string value, which is a {ptr, len} pair. - zero := llvm.ConstInt(mod.Context().Int32Type(), 0, false) - ptr := llvm.ConstGEP(bufInitializer.Type(), buf, []llvm.Value{zero, zero}) - if ptr.Type() != elementTypes[0] { - return fmt.Errorf("%s: not a string", globalName) - } - length := llvm.ConstInt(elementTypes[1], uint64(len(value)), false) - initializer := llvm.ConstNamedStruct(initializerType, []llvm.Value{ - ptr, + length := llvm.ConstInt(uintptrType, uint64(len(value)), false) + initializer := llvm.ConstNamedStruct(stringType, []llvm.Value{ + buf, length, }) - // Set the initializer. No initializer should be set at this point. + // Create the string global. + global := llvm.AddGlobal(mod, stringType, globalName) global.SetInitializer(initializer) + global.SetAlignment(targetData.PrefTypeAlignment(stringType)) } } - return nil + + return mod } // functionStackSizes keeps stack size information about a single function From d5c70a1cd361ff46214c21200ffb6503aced2811 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 8 Mar 2025 12:43:53 -0800 Subject: [PATCH 417/444] reflect, internal/reflectlite: embed reflectlite types into reflect types --- .../reflectlite}/deepequal.go | 4 +- .../reflectlite/endian_big.go} | 2 +- .../reflectlite/endian_little.go} | 2 +- src/{reflect => internal/reflectlite}/intw.go | 2 +- .../reflectlite}/intw_avr.go | 2 +- .../reflectlite}/intw_test.go | 2 +- src/internal/reflectlite/reflect.go | 2 + src/internal/reflectlite/strconv.go | 347 +++ src/internal/reflectlite/swapper.go | 40 + src/internal/reflectlite/type.go | 1130 +++++++++ src/internal/reflectlite/value.go | 2114 +++++++++++++++++ src/internal/reflectlite/visiblefields.go | 105 + src/reflect/swapper.go | 37 +- src/reflect/type.go | 1143 +-------- src/reflect/value.go | 2007 +--------------- src/reflect/visiblefields.go | 104 +- 16 files changed, 3861 insertions(+), 3182 deletions(-) rename src/{reflect => internal/reflectlite}/deepequal.go (99%) rename src/{reflect/endian-big.go => internal/reflectlite/endian_big.go} (98%) rename src/{reflect/endian-little.go => internal/reflectlite/endian_little.go} (98%) rename src/{reflect => internal/reflectlite}/intw.go (92%) rename src/{reflect => internal/reflectlite}/intw_avr.go (92%) rename src/{reflect => internal/reflectlite}/intw_test.go (97%) create mode 100644 src/internal/reflectlite/strconv.go create mode 100644 src/internal/reflectlite/swapper.go create mode 100644 src/internal/reflectlite/type.go create mode 100644 src/internal/reflectlite/value.go create mode 100644 src/internal/reflectlite/visiblefields.go diff --git a/src/reflect/deepequal.go b/src/internal/reflectlite/deepequal.go similarity index 99% rename from src/reflect/deepequal.go rename to src/internal/reflectlite/deepequal.go index 18a728458c..436dc007f8 100644 --- a/src/reflect/deepequal.go +++ b/src/internal/reflectlite/deepequal.go @@ -4,7 +4,7 @@ // Deep equality test via reflection -package reflect +package reflectlite import "unsafe" @@ -15,7 +15,7 @@ import "unsafe" type visit struct { a1 unsafe.Pointer a2 unsafe.Pointer - typ *rawType + typ *RawType } // Tests for deep equality using reflected types. The map argument tracks diff --git a/src/reflect/endian-big.go b/src/internal/reflectlite/endian_big.go similarity index 98% rename from src/reflect/endian-big.go rename to src/internal/reflectlite/endian_big.go index 94951e200d..5ad792dcc3 100644 --- a/src/reflect/endian-big.go +++ b/src/internal/reflectlite/endian_big.go @@ -1,6 +1,6 @@ //go:build mips -package reflect +package reflectlite import "unsafe" diff --git a/src/reflect/endian-little.go b/src/internal/reflectlite/endian_little.go similarity index 98% rename from src/reflect/endian-little.go rename to src/internal/reflectlite/endian_little.go index 7d7e30059d..035ec01d8b 100644 --- a/src/reflect/endian-little.go +++ b/src/internal/reflectlite/endian_little.go @@ -1,6 +1,6 @@ //go:build !mips -package reflect +package reflectlite import "unsafe" diff --git a/src/reflect/intw.go b/src/internal/reflectlite/intw.go similarity index 92% rename from src/reflect/intw.go rename to src/internal/reflectlite/intw.go index 20fbd4341e..4dd197cefc 100644 --- a/src/reflect/intw.go +++ b/src/internal/reflectlite/intw.go @@ -1,6 +1,6 @@ //go:build !avr -package reflect +package reflectlite // intw is an integer type, used in places where an int is typically required, // except architectures where the size of an int != word size. diff --git a/src/reflect/intw_avr.go b/src/internal/reflectlite/intw_avr.go similarity index 92% rename from src/reflect/intw_avr.go rename to src/internal/reflectlite/intw_avr.go index 8f294eeee2..24b4377777 100644 --- a/src/reflect/intw_avr.go +++ b/src/internal/reflectlite/intw_avr.go @@ -1,6 +1,6 @@ //go:build avr -package reflect +package reflectlite // intw is an integer type, used in places where an int is typically required, // except architectures where the size of an int != word size. diff --git a/src/reflect/intw_test.go b/src/internal/reflectlite/intw_test.go similarity index 97% rename from src/reflect/intw_test.go rename to src/internal/reflectlite/intw_test.go index 1014a9ae4e..b235b88f4c 100644 --- a/src/reflect/intw_test.go +++ b/src/internal/reflectlite/intw_test.go @@ -1,6 +1,6 @@ //go:build !avr -package reflect_test +package reflectlite_test import ( "reflect" diff --git a/src/internal/reflectlite/reflect.go b/src/internal/reflectlite/reflect.go index 938e56a556..df6abd3aa7 100644 --- a/src/internal/reflectlite/reflect.go +++ b/src/internal/reflectlite/reflect.go @@ -1,3 +1,5 @@ +//go:build never + package reflectlite import "reflect" diff --git a/src/internal/reflectlite/strconv.go b/src/internal/reflectlite/strconv.go new file mode 100644 index 0000000000..deabe4a5c7 --- /dev/null +++ b/src/internal/reflectlite/strconv.go @@ -0,0 +1,347 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package reflectlite + +import ( + "unicode/utf8" +) + +// errSyntax indicates that a value does not have the right syntax for the target type. +var errSyntax = badSyntax{} + +type badSyntax struct{} + +func (badSyntax) Error() string { + return "invalid syntax" +} + +func unhex(b byte) (v rune, ok bool) { + c := rune(b) + switch { + case '0' <= c && c <= '9': + return c - '0', true + case 'a' <= c && c <= 'f': + return c - 'a' + 10, true + case 'A' <= c && c <= 'F': + return c - 'A' + 10, true + } + return +} + +const ( + lowerhex = "0123456789abcef" +) + +// unquoteChar decodes the first character or byte in the escaped string +// or character literal represented by the string s. +// It returns four values: +// +// 1. value, the decoded Unicode code point or byte value; +// 2. multibyte, a boolean indicating whether the decoded character requires a multibyte UTF-8 representation; +// 3. tail, the remainder of the string after the character; and +// 4. an error that will be nil if the character is syntactically valid. +// +// The second argument, quote, specifies the type of literal being parsed +// and therefore which escaped quote character is permitted. +// If set to a single quote, it permits the sequence \' and disallows unescaped '. +// If set to a double quote, it permits \" and disallows unescaped ". +// If set to zero, it does not permit either escape and allows both quote characters to appear unescaped. +func unquoteChar(s string, quote byte) (value rune, multibyte bool, tail string, err error) { + // easy cases + if len(s) == 0 { + err = errSyntax + return + } + switch c := s[0]; { + case c == quote && (quote == '\'' || quote == '"'): + err = errSyntax + return + case c >= utf8.RuneSelf: + r, size := utf8.DecodeRuneInString(s) + return r, true, s[size:], nil + case c != '\\': + return rune(s[0]), false, s[1:], nil + } + + // hard case: c is backslash + if len(s) <= 1 { + err = errSyntax + return + } + c := s[1] + s = s[2:] + + switch c { + case 'a': + value = '\a' + case 'b': + value = '\b' + case 'f': + value = '\f' + case 'n': + value = '\n' + case 'r': + value = '\r' + case 't': + value = '\t' + case 'v': + value = '\v' + case 'x', 'u', 'U': + n := 0 + switch c { + case 'x': + n = 2 + case 'u': + n = 4 + case 'U': + n = 8 + } + var v rune + if len(s) < n { + err = errSyntax + return + } + for j := 0; j < n; j++ { + x, ok := unhex(s[j]) + if !ok { + err = errSyntax + return + } + v = v<<4 | x + } + s = s[n:] + if c == 'x' { + // single-byte string, possibly not UTF-8 + value = v + break + } + if v > utf8.MaxRune { + err = errSyntax + return + } + value = v + multibyte = true + case '0', '1', '2', '3', '4', '5', '6', '7': + v := rune(c) - '0' + if len(s) < 2 { + err = errSyntax + return + } + for j := 0; j < 2; j++ { // one digit already; two more + x := rune(s[j]) - '0' + if x < 0 || x > 7 { + err = errSyntax + return + } + v = (v << 3) | x + } + s = s[2:] + if v > 255 { + err = errSyntax + return + } + value = v + case '\\': + value = '\\' + case '\'', '"': + if c != quote { + err = errSyntax + return + } + value = rune(c) + default: + err = errSyntax + return + } + tail = s + return +} + +// unquote interprets s as a single-quoted, double-quoted, +// or backquoted Go string literal, returning the string value +// that s quotes. (If s is single-quoted, it would be a Go +// character literal; unquote returns the corresponding +// one-character string.) +func unquote(s string) (string, error) { + n := len(s) + if n < 2 { + return "", errSyntax + } + quote := s[0] + if quote != s[n-1] { + return "", errSyntax + } + s = s[1 : n-1] + + if quote == '`' { + if contains(s, '`') { + return "", errSyntax + } + if contains(s, '\r') { + // -1 because we know there is at least one \r to remove. + buf := make([]byte, 0, len(s)-1) + for i := 0; i < len(s); i++ { + if s[i] != '\r' { + buf = append(buf, s[i]) + } + } + return string(buf), nil + } + return s, nil + } + if quote != '"' && quote != '\'' { + return "", errSyntax + } + if contains(s, '\n') { + return "", errSyntax + } + + // Is it trivial? Avoid allocation. + if !contains(s, '\\') && !contains(s, quote) { + switch quote { + case '"': + if utf8.ValidString(s) { + return s, nil + } + case '\'': + r, size := utf8.DecodeRuneInString(s) + if size == len(s) && (r != utf8.RuneError || size != 1) { + return s, nil + } + } + } + + var runeTmp [utf8.UTFMax]byte + buf := make([]byte, 0, 3*len(s)/2) // Try to avoid more allocations. + for len(s) > 0 { + c, multibyte, ss, err := unquoteChar(s, quote) + if err != nil { + return "", err + } + s = ss + if c < utf8.RuneSelf || !multibyte { + buf = append(buf, byte(c)) + } else { + n := utf8.EncodeRune(runeTmp[:], c) + buf = append(buf, runeTmp[:n]...) + } + if quote == '\'' && len(s) != 0 { + // single-quoted must be single character + return "", errSyntax + } + } + return string(buf), nil +} + +func quote(s string) string { + buf := make([]byte, 0, 3*len(s)/2) + const quote = '"' + + buf = append(buf, quote) + for width := 0; len(s) > 0; s = s[width:] { + r := rune(s[0]) + width = 1 + if r >= utf8.RuneSelf { + r, width = utf8.DecodeRuneInString(s) + } + if width == 1 && r == utf8.RuneError { + buf = append(buf, `\x`...) + buf = append(buf, lowerhex[s[0]>>4]) + buf = append(buf, lowerhex[s[0]&0xF]) + continue + } + buf = appendEscapedRune(buf, r) + } + buf = append(buf, quote) + return string(buf) +} + +func appendEscapedRune(buf []byte, r rune) []byte { + + const quote = '"' + + var runeTmp [utf8.UTFMax]byte + if r == rune(quote) || r == '\\' { // always backslashed + buf = append(buf, '\\') + buf = append(buf, byte(r)) + return buf + } + if isPrint(r) { + n := utf8.EncodeRune(runeTmp[:], r) + buf = append(buf, runeTmp[:n]...) + return buf + } + switch r { + case '\a': + buf = append(buf, `\a`...) + case '\b': + buf = append(buf, `\b`...) + case '\f': + buf = append(buf, `\f`...) + case '\n': + buf = append(buf, `\n`...) + case '\r': + buf = append(buf, `\r`...) + case '\t': + buf = append(buf, `\t`...) + case '\v': + buf = append(buf, `\v`...) + default: + switch { + case r < ' ' || r == 0x7f: + buf = append(buf, `\x`...) + buf = append(buf, lowerhex[byte(r)>>4]) + buf = append(buf, lowerhex[byte(r)&0xF]) + case !utf8.ValidRune(r): + r = 0xFFFD + fallthrough + case r < 0x10000: + buf = append(buf, `\u`...) + for s := 12; s >= 0; s -= 4 { + buf = append(buf, lowerhex[r>>uint(s)&0xF]) + } + default: + buf = append(buf, `\U`...) + for s := 28; s >= 0; s -= 4 { + buf = append(buf, lowerhex[r>>uint(s)&0xF]) + } + } + } + return buf +} + +// This is only used for struct tags. Assume +func isPrint(r rune) bool { + if r <= 0xFF { + if 0x20 <= r && r <= 0x7E { + // All the ASCII is printable from space through DEL-1. + return true + } + if 0xA1 <= r && r <= 0xFF { + // Similarly for ¡ through ÿ... + return r != 0xAD // ...except for the bizarre soft hyphen. + } + return false + } + + // TinyGo: Skip all other unicode processing + return false +} + +// contains reports whether the string contains the byte c. +func contains(s string, c byte) bool { + return indexByteString(s, c) != -1 +} + +// Index finds the index of the first instance of the specified byte in the string. +// If the byte is not found, this returns -1. +func indexByteString(s string, c byte) int { + for i := 0; i < len(s); i++ { + if s[i] == c { + return i + } + } + return -1 +} diff --git a/src/internal/reflectlite/swapper.go b/src/internal/reflectlite/swapper.go new file mode 100644 index 0000000000..b8d85a9442 --- /dev/null +++ b/src/internal/reflectlite/swapper.go @@ -0,0 +1,40 @@ +package reflectlite + +import "unsafe" + +// Some of code here has been copied from the Go sources: +// https://github.com/golang/go/blob/go1.15.2/src/reflect/swapper.go +// It has the following copyright note: +// +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +func Swapper(slice interface{}) func(i, j int) { + v := ValueOf(slice) + if v.Kind() != Slice { + panic(&ValueError{Method: "Swapper"}) + } + + // Just return Nop func if nothing to swap. + if v.Len() < 2 { + return func(i, j int) {} + } + + typ := v.typecode.Elem() + size := typ.Size() + + header := (*sliceHeader)(v.value) + tmp := unsafe.Pointer(&make([]byte, size)[0]) + + return func(i, j int) { + if uint(i) >= uint(header.len) || uint(j) >= uint(header.len) { + panic("reflect: slice index out of range") + } + val1 := unsafe.Add(header.data, uintptr(i)*size) + val2 := unsafe.Add(header.data, uintptr(j)*size) + memcpy(tmp, val1, size) + memcpy(val1, val2, size) + memcpy(val2, tmp, size) + } +} diff --git a/src/internal/reflectlite/type.go b/src/internal/reflectlite/type.go new file mode 100644 index 0000000000..b81468de40 --- /dev/null +++ b/src/internal/reflectlite/type.go @@ -0,0 +1,1130 @@ +package reflectlite + +import ( + "internal/gclayout" + "internal/itoa" + "unsafe" +) + +// Flags stored in the first byte of the struct field byte array. Must be kept +// up to date with compiler/interface.go. +const ( + structFieldFlagAnonymous = 1 << iota + structFieldFlagHasTag + structFieldFlagIsExported + structFieldFlagIsEmbedded +) + +type Kind uint8 + +// Copied from reflect/type.go +// https://golang.org/src/reflect/type.go?s=8302:8316#L217 +// These constants must match basicTypes and the typeKind* constants in +// compiler/interface.go +const ( + Invalid Kind = iota + Bool + Int + Int8 + Int16 + Int32 + Int64 + Uint + Uint8 + Uint16 + Uint32 + Uint64 + Uintptr + Float32 + Float64 + Complex64 + Complex128 + String + UnsafePointer + Chan + Interface + Pointer + Slice + Array + Func + Map + Struct +) + +// Ptr is the old name for the Pointer kind. +const Ptr = Pointer + +func (k Kind) String() string { + switch k { + case Invalid: + return "invalid" + case Bool: + return "bool" + case Int: + return "int" + case Int8: + return "int8" + case Int16: + return "int16" + case Int32: + return "int32" + case Int64: + return "int64" + case Uint: + return "uint" + case Uint8: + return "uint8" + case Uint16: + return "uint16" + case Uint32: + return "uint32" + case Uint64: + return "uint64" + case Uintptr: + return "uintptr" + case Float32: + return "float32" + case Float64: + return "float64" + case Complex64: + return "complex64" + case Complex128: + return "complex128" + case String: + return "string" + case UnsafePointer: + return "unsafe.Pointer" + case Chan: + return "chan" + case Interface: + return "interface" + case Pointer: + return "ptr" + case Slice: + return "slice" + case Array: + return "array" + case Func: + return "func" + case Map: + return "map" + case Struct: + return "struct" + default: + return "kind" + itoa.Itoa(int(int8(k))) + } +} + +// Copied from reflect/type.go +// https://go.dev/src/reflect/type.go?#L348 + +// ChanDir represents a channel type's direction. +type ChanDir int + +const ( + RecvDir ChanDir = 1 << iota // <-chan + SendDir // chan<- + BothDir = RecvDir | SendDir // chan +) + +// Type represents the minimal interface for a Go type. +type Type interface { + // These should match the reflectlite.Type implementation in Go. + AssignableTo(u Type) bool + Comparable() bool + Elem() Type + Implements(u Type) bool + Kind() Kind + Name() string + PkgPath() string + Size() uintptr + String() string + + // Additional methods shared with reflect.Type. + Align() int + Field(i int) StructField + Key() Type + Len() int + NumField() int + NumMethod() int +} + +// Constants for the 'meta' byte. +const ( + kindMask = 31 // mask to apply to the meta byte to get the Kind value + flagNamed = 32 // flag that is set if this is a named type + flagComparable = 64 // flag that is set if this type is comparable + flagIsBinary = 128 // flag that is set if this type uses the hashmap binary algorithm +) + +// The base type struct. All type structs start with this. +type RawType struct { + meta uint8 // metadata byte, contains kind and flags (see constants above) +} + +// All types that have an element type: named, chan, slice, array, map (but not +// pointer because it doesn't have ptrTo). +type elemType struct { + RawType + numMethod uint16 + ptrTo *RawType + elem *RawType +} + +type ptrType struct { + RawType + numMethod uint16 + elem *RawType +} + +type interfaceType struct { + RawType + ptrTo *RawType + // TODO: methods +} + +type arrayType struct { + RawType + numMethod uint16 + ptrTo *RawType + elem *RawType + arrayLen uintptr + slicePtr *RawType +} + +type mapType struct { + RawType + numMethod uint16 + ptrTo *RawType + elem *RawType + key *RawType +} + +type namedType struct { + RawType + numMethod uint16 + ptrTo *RawType + elem *RawType + pkg *byte + name [1]byte +} + +// Type for struct types. The numField value is intentionally put before ptrTo +// for better struct packing on 32-bit and 64-bit architectures. On these +// architectures, the ptrTo field still has the same offset as in all the other +// type structs. +// The fields array isn't necessarily 1 structField long, instead it is as long +// as numFields. The array is given a length of 1 to satisfy the Go type +// checker. +type structType struct { + RawType + numMethod uint16 + ptrTo *RawType + pkgpath *byte + size uint32 + numField uint16 + fields [1]structField // the remaining fields are all of type structField +} + +type structField struct { + fieldType *RawType + data unsafe.Pointer // various bits of information, packed in a byte array +} + +// Equivalent to (go/types.Type).Underlying(): if this is a named type return +// the underlying type, else just return the type itself. +func (t *RawType) underlying() *RawType { + if t.isNamed() { + return (*elemType)(unsafe.Pointer(t)).elem + } + return t +} + +func (t *RawType) ptrtag() uintptr { + return uintptr(unsafe.Pointer(t)) & 0b11 +} + +func (t *RawType) isNamed() bool { + if tag := t.ptrtag(); tag != 0 { + return false + } + return t.meta&flagNamed != 0 +} + +func TypeOf(i interface{}) Type { + if i == nil { + return nil + } + typecode, _ := decomposeInterface(i) + return (*RawType)(typecode) +} + +func PtrTo(t Type) Type { return PointerTo(t) } + +func PointerTo(t Type) Type { + return pointerTo(t.(*RawType)) +} + +func pointerTo(t *RawType) *RawType { + if t.isNamed() { + return (*elemType)(unsafe.Pointer(t)).ptrTo + } + + switch t.Kind() { + case Pointer: + if tag := t.ptrtag(); tag < 3 { + return (*RawType)(unsafe.Add(unsafe.Pointer(t), 1)) + } + + // TODO(dgryski): This is blocking https://github.com/tinygo-org/tinygo/issues/3131 + // We need to be able to create types that match existing types to prevent typecode equality. + panic("reflect: cannot make *****T type") + case Struct: + return (*structType)(unsafe.Pointer(t)).ptrTo + default: + return (*elemType)(unsafe.Pointer(t)).ptrTo + } +} + +func (t *RawType) String() string { + if t.isNamed() { + s := t.name() + if s[0] == '.' { + return s[1:] + } + return s + } + + switch t.Kind() { + case Chan: + elem := t.elem().String() + switch t.ChanDir() { + case SendDir: + return "chan<- " + elem + case RecvDir: + return "<-chan " + elem + case BothDir: + if elem[0] == '<' { + // typ is recv chan, need parentheses as "<-" associates with leftmost + // chan possible, see: + // * https://golang.org/ref/spec#Channel_types + // * https://github.com/golang/go/issues/39897 + return "chan (" + elem + ")" + } + return "chan " + elem + } + + case Pointer: + return "*" + t.elem().String() + case Slice: + return "[]" + t.elem().String() + case Array: + return "[" + itoa.Itoa(t.Len()) + "]" + t.elem().String() + case Map: + return "map[" + t.key().String() + "]" + t.elem().String() + case Struct: + numField := t.NumField() + if numField == 0 { + return "struct {}" + } + s := "struct {" + for i := 0; i < numField; i++ { + f := t.rawField(i) + s += " " + f.Name + " " + f.Type.String() + if f.Tag != "" { + s += " " + quote(string(f.Tag)) + } + // every field except the last needs a semicolon + if i < numField-1 { + s += ";" + } + } + s += " }" + return s + case Interface: + // TODO(dgryski): Needs actual method set info + return "interface {}" + default: + return t.Kind().String() + } + + return t.Kind().String() +} + +func (t *RawType) Kind() Kind { + if t == nil { + return Invalid + } + + if tag := t.ptrtag(); tag != 0 { + return Pointer + } + + return Kind(t.meta & kindMask) +} + +var ( + errTypeElem = &TypeError{"Elem"} + errTypeKey = &TypeError{"Key"} + errTypeField = &TypeError{"Field"} + errTypeBits = &TypeError{"Bits"} + errTypeLen = &TypeError{"Len"} + errTypeNumField = &TypeError{"NumField"} + errTypeChanDir = &TypeError{"ChanDir"} + errTypeFieldByName = &TypeError{"FieldByName"} + errTypeFieldByIndex = &TypeError{"FieldByIndex"} +) + +// Elem returns the element type for channel, slice and array types, the +// pointed-to value for pointer types, and the key type for map types. +func (t *RawType) Elem() Type { + return t.elem() +} + +func (t *RawType) elem() *RawType { + if tag := t.ptrtag(); tag != 0 { + return (*RawType)(unsafe.Add(unsafe.Pointer(t), -1)) + } + + underlying := t.underlying() + switch underlying.Kind() { + case Pointer: + return (*ptrType)(unsafe.Pointer(underlying)).elem + case Chan, Slice, Array, Map: + return (*elemType)(unsafe.Pointer(underlying)).elem + default: + panic(errTypeElem) + } +} + +func (t *RawType) key() *RawType { + underlying := t.underlying() + if underlying.Kind() != Map { + panic(errTypeKey) + } + return (*mapType)(unsafe.Pointer(underlying)).key +} + +// Field returns the type of the i'th field of this struct type. It panics if t +// is not a struct type. +func (t *RawType) Field(i int) StructField { + field := t.rawField(i) + return StructField{ + Name: field.Name, + PkgPath: field.PkgPath, + Type: field.Type, // note: converts RawType to Type + Tag: field.Tag, + Anonymous: field.Anonymous, + Offset: field.Offset, + Index: []int{i}, + } +} + +func rawStructFieldFromPointer(descriptor *structType, fieldType *RawType, data unsafe.Pointer, flagsByte uint8, name string, offset uint32) rawStructField { + // Read the field tag, if there is one. + var tag string + if flagsByte&structFieldFlagHasTag != 0 { + data = unsafe.Add(data, 1) // C: data+1 + tagLen := uintptr(*(*byte)(data)) + data = unsafe.Add(data, 1) // C: data+1 + tag = *(*string)(unsafe.Pointer(&stringHeader{ + data: data, + len: tagLen, + })) + } + + // Set the PkgPath to some (arbitrary) value if the package path is not + // exported. + pkgPath := "" + if flagsByte&structFieldFlagIsExported == 0 { + // This field is unexported. + pkgPath = readStringZ(unsafe.Pointer(descriptor.pkgpath)) + } + + return rawStructField{ + Name: name, + PkgPath: pkgPath, + Type: fieldType, + Tag: StructTag(tag), + Anonymous: flagsByte&structFieldFlagAnonymous != 0, + Offset: uintptr(offset), + } +} + +// rawField returns nearly the same value as Field but without converting the +// Type member to an interface. +// +// For internal use only. +func (t *RawType) rawField(n int) rawStructField { + if t.Kind() != Struct { + panic(errTypeField) + } + descriptor := (*structType)(unsafe.Pointer(t.underlying())) + if uint(n) >= uint(descriptor.numField) { + panic("reflect: field index out of range") + } + + // Iterate over all the fields to calculate the offset. + // This offset could have been stored directly in the array (to make the + // lookup faster), but by calculating it on-the-fly a bit of storage can be + // saved. + field := (*structField)(unsafe.Add(unsafe.Pointer(&descriptor.fields[0]), uintptr(n)*unsafe.Sizeof(structField{}))) + data := field.data + + // Read some flags of this field, like whether the field is an embedded + // field. See structFieldFlagAnonymous and similar flags. + flagsByte := *(*byte)(data) + data = unsafe.Add(data, 1) + offset, lenOffs := uvarint32(unsafe.Slice((*byte)(data), maxVarintLen32)) + data = unsafe.Add(data, lenOffs) + + name := readStringZ(data) + data = unsafe.Add(data, len(name)) + + return rawStructFieldFromPointer(descriptor, field.fieldType, data, flagsByte, name, offset) +} + +// rawFieldByNameFunc returns nearly the same value as FieldByNameFunc but without converting the +// Type member to an interface. +// +// For internal use only. +func (t *RawType) rawFieldByNameFunc(match func(string) bool) (rawStructField, []int, bool) { + if t.Kind() != Struct { + panic(errTypeField) + } + + type fieldWalker struct { + t *RawType + index []int + } + + queue := make([]fieldWalker, 0, 4) + queue = append(queue, fieldWalker{t, nil}) + + for len(queue) > 0 { + type result struct { + r rawStructField + index []int + } + + var found []result + var nextlevel []fieldWalker + + // For all the structs at this level.. + for _, ll := range queue { + // Iterate over all the fields looking for the matching name + // Also calculate field offset. + + descriptor := (*structType)(unsafe.Pointer(ll.t.underlying())) + field := &descriptor.fields[0] + + for i := uint16(0); i < descriptor.numField; i++ { + data := field.data + + // Read some flags of this field, like whether the field is an embedded + // field. See structFieldFlagAnonymous and similar flags. + flagsByte := *(*byte)(data) + data = unsafe.Add(data, 1) + + offset, lenOffs := uvarint32(unsafe.Slice((*byte)(data), maxVarintLen32)) + data = unsafe.Add(data, lenOffs) + + name := readStringZ(data) + data = unsafe.Add(data, len(name)) + if match(name) { + found = append(found, result{ + rawStructFieldFromPointer(descriptor, field.fieldType, data, flagsByte, name, offset), + append(ll.index[:len(ll.index):len(ll.index)], int(i)), + }) + } + + structOrPtrToStruct := field.fieldType.Kind() == Struct || (field.fieldType.Kind() == Pointer && field.fieldType.elem().Kind() == Struct) + if flagsByte&structFieldFlagIsEmbedded == structFieldFlagIsEmbedded && structOrPtrToStruct { + embedded := field.fieldType + if embedded.Kind() == Pointer { + embedded = embedded.elem() + } + + nextlevel = append(nextlevel, fieldWalker{ + t: embedded, + index: append(ll.index[:len(ll.index):len(ll.index)], int(i)), + }) + } + + // update offset/field pointer if there *is* a next field + if i < descriptor.numField-1 { + // Increment pointer to the next field. + field = (*structField)(unsafe.Add(unsafe.Pointer(field), unsafe.Sizeof(structField{}))) + } + } + } + + // found multiple hits at this level + if len(found) > 1 { + return rawStructField{}, nil, false + } + + // found the field we were looking for + if len(found) == 1 { + r := found[0] + return r.r, r.index, true + } + + // else len(found) == 0, move on to the next level + queue = append(queue[:0], nextlevel...) + } + + // didn't find it + return rawStructField{}, nil, false +} + +// Bits returns the number of bits that this type uses. It is only valid for +// arithmetic types (integers, floats, and complex numbers). For other types, it +// will panic. +func (t *RawType) Bits() int { + kind := t.Kind() + if kind >= Int && kind <= Complex128 { + return int(t.Size()) * 8 + } + panic(errTypeBits) +} + +// Len returns the number of elements in this array. It panics of the type kind +// is not Array. +func (t *RawType) Len() int { + if t.Kind() != Array { + panic(errTypeLen) + } + + return int((*arrayType)(unsafe.Pointer(t.underlying())).arrayLen) +} + +// NumField returns the number of fields of a struct type. It panics for other +// type kinds. +func (t *RawType) NumField() int { + if t.Kind() != Struct { + panic(errTypeNumField) + } + return int((*structType)(unsafe.Pointer(t.underlying())).numField) +} + +// Size returns the size in bytes of a given type. It is similar to +// unsafe.Sizeof. +func (t *RawType) Size() uintptr { + switch t.Kind() { + case Bool, Int8, Uint8: + return 1 + case Int16, Uint16: + return 2 + case Int32, Uint32: + return 4 + case Int64, Uint64: + return 8 + case Int, Uint: + return unsafe.Sizeof(int(0)) + case Uintptr: + return unsafe.Sizeof(uintptr(0)) + case Float32: + return 4 + case Float64: + return 8 + case Complex64: + return 8 + case Complex128: + return 16 + case String: + return unsafe.Sizeof("") + case UnsafePointer, Chan, Map, Pointer: + return unsafe.Sizeof(uintptr(0)) + case Slice: + return unsafe.Sizeof([]int{}) + case Interface: + return unsafe.Sizeof(interface{}(nil)) + case Func: + var f func() + return unsafe.Sizeof(f) + case Array: + return t.elem().Size() * uintptr(t.Len()) + case Struct: + u := t.underlying() + return uintptr((*structType)(unsafe.Pointer(u)).size) + default: + panic("unimplemented: size of type") + } +} + +// Align returns the alignment of this type. It is similar to calling +// unsafe.Alignof. +func (t *RawType) Align() int { + switch t.Kind() { + case Bool, Int8, Uint8: + return int(unsafe.Alignof(int8(0))) + case Int16, Uint16: + return int(unsafe.Alignof(int16(0))) + case Int32, Uint32: + return int(unsafe.Alignof(int32(0))) + case Int64, Uint64: + return int(unsafe.Alignof(int64(0))) + case Int, Uint: + return int(unsafe.Alignof(int(0))) + case Uintptr: + return int(unsafe.Alignof(uintptr(0))) + case Float32: + return int(unsafe.Alignof(float32(0))) + case Float64: + return int(unsafe.Alignof(float64(0))) + case Complex64: + return int(unsafe.Alignof(complex64(0))) + case Complex128: + return int(unsafe.Alignof(complex128(0))) + case String: + return int(unsafe.Alignof("")) + case UnsafePointer, Chan, Map, Pointer: + return int(unsafe.Alignof(uintptr(0))) + case Slice: + return int(unsafe.Alignof([]int(nil))) + case Interface: + return int(unsafe.Alignof(interface{}(nil))) + case Func: + var f func() + return int(unsafe.Alignof(f)) + case Struct: + numField := t.NumField() + alignment := 1 + for i := 0; i < numField; i++ { + fieldAlignment := t.rawField(i).Type.Align() + if fieldAlignment > alignment { + alignment = fieldAlignment + } + } + return alignment + case Array: + return t.elem().Align() + default: + panic("unimplemented: alignment of type") + } +} + +func (r *RawType) gcLayout() unsafe.Pointer { + kind := r.Kind() + + if kind < String { + return gclayout.NoPtrs + } + + switch kind { + case Pointer, UnsafePointer, Chan, Map: + return gclayout.Pointer + case String: + return gclayout.String + case Slice: + return gclayout.Slice + } + + // Unknown (for now); let the conservative pointer scanning handle it + return nil +} + +// FieldAlign returns the alignment if this type is used in a struct field. It +// is currently an alias for Align() but this might change in the future. +func (t *RawType) FieldAlign() int { + return t.Align() +} + +// AssignableTo returns whether a value of type t can be assigned to a variable +// of type u. +func (t *RawType) AssignableTo(u Type) bool { + if t == u.(*RawType) { + return true + } + + if t.underlying() == u.(*RawType).underlying() && (!t.isNamed() || !u.(*RawType).isNamed()) { + return true + } + + if u.Kind() == Interface && u.NumMethod() == 0 { + return true + } + + if u.Kind() == Interface { + panic("reflect: unimplemented: AssignableTo with interface") + } + return false +} + +func (t *RawType) Implements(u Type) bool { + if u.Kind() != Interface { + panic("reflect: non-interface type passed to Type.Implements") + } + return t.AssignableTo(u) +} + +// Comparable returns whether values of this type can be compared to each other. +func (t *RawType) Comparable() bool { + return (t.meta & flagComparable) == flagComparable +} + +// isBinary returns if the hashmapAlgorithmBinary functions can be used on this type +func (t *RawType) isBinary() bool { + return (t.meta & flagIsBinary) == flagIsBinary +} + +func (t *RawType) ChanDir() ChanDir { + if t.Kind() != Chan { + panic(errTypeChanDir) + } + + dir := int((*elemType)(unsafe.Pointer(t)).numMethod) + + // nummethod is overloaded for channel to store channel direction + return ChanDir(dir) +} + +func (t *RawType) NumMethod() int { + + if t.isNamed() { + return int((*namedType)(unsafe.Pointer(t)).numMethod) + } + + switch t.Kind() { + case Pointer: + return int((*ptrType)(unsafe.Pointer(t)).numMethod) + case Struct: + return int((*structType)(unsafe.Pointer(t)).numMethod) + case Interface: + //FIXME: Use len(methods) + return (*interfaceType)(unsafe.Pointer(t)).ptrTo.NumMethod() + } + + // Other types have no methods attached. Note we don't panic here. + return 0 +} + +// Read and return a null terminated string starting from data. +func readStringZ(data unsafe.Pointer) string { + start := data + var len uintptr + for *(*byte)(data) != 0 { + len++ + data = unsafe.Add(data, 1) // C: data++ + } + + return *(*string)(unsafe.Pointer(&stringHeader{ + data: start, + len: len, + })) +} + +func (t *RawType) name() string { + ntype := (*namedType)(unsafe.Pointer(t)) + return readStringZ(unsafe.Pointer(&ntype.name[0])) +} + +func (t *RawType) Name() string { + if t.isNamed() { + name := t.name() + for i := 0; i < len(name); i++ { + if name[i] == '.' { + return name[i+1:] + } + } + panic("corrupt name data") + } + + if kind := t.Kind(); kind < UnsafePointer { + return t.Kind().String() + } else if kind == UnsafePointer { + return "Pointer" + } + + return "" +} + +func (t *RawType) Key() Type { + return t.key() +} + +// OverflowComplex reports whether the complex128 x cannot be represented by type t. +// It panics if t's Kind is not Complex64 or Complex128. +func (t RawType) OverflowComplex(x complex128) bool { + k := t.Kind() + switch k { + case Complex64: + return overflowFloat32(real(x)) || overflowFloat32(imag(x)) + case Complex128: + return false + } + panic("reflect: OverflowComplex of non-complex type") +} + +// OverflowFloat reports whether the float64 x cannot be represented by type t. +// It panics if t's Kind is not Float32 or Float64. +func (t RawType) OverflowFloat(x float64) bool { + k := t.Kind() + switch k { + case Float32: + return overflowFloat32(x) + case Float64: + return false + } + panic("reflect: OverflowFloat of non-float type") +} + +// OverflowInt reports whether the int64 x cannot be represented by type t. +// It panics if t's Kind is not Int, Int8, Int16, Int32, or Int64. +func (t RawType) OverflowInt(x int64) bool { + k := t.Kind() + switch k { + case Int, Int8, Int16, Int32, Int64: + bitSize := t.Size() * 8 + trunc := (x << (64 - bitSize)) >> (64 - bitSize) + return x != trunc + } + panic("reflect: OverflowInt of non-int type") +} + +// OverflowUint reports whether the uint64 x cannot be represented by type t. +// It panics if t's Kind is not Uint, Uintptr, Uint8, Uint16, Uint32, or Uint64. +func (t RawType) OverflowUint(x uint64) bool { + k := t.Kind() + switch k { + case Uint, Uintptr, Uint8, Uint16, Uint32, Uint64: + bitSize := t.Size() * 8 + trunc := (x << (64 - bitSize)) >> (64 - bitSize) + return x != trunc + } + panic("reflect: OverflowUint of non-uint type") +} + +func (t *RawType) PkgPath() string { + if t.isNamed() { + ntype := (*namedType)(unsafe.Pointer(t)) + return readStringZ(unsafe.Pointer(ntype.pkg)) + } + + return "" +} + +func (t *RawType) FieldByName(name string) (StructField, bool) { + if t.Kind() != Struct { + panic(errTypeFieldByName) + } + + field, index, ok := t.rawFieldByNameFunc(func(n string) bool { return n == name }) + if !ok { + return StructField{}, false + } + + return StructField{ + Name: field.Name, + PkgPath: field.PkgPath, + Type: field.Type, // note: converts RawType to Type + Tag: field.Tag, + Anonymous: field.Anonymous, + Offset: field.Offset, + Index: index, + }, true +} + +func (t *RawType) FieldByNameFunc(match func(string) bool) (StructField, bool) { + if t.Kind() != Struct { + panic(TypeError{"FieldByNameFunc"}) + } + + field, index, ok := t.rawFieldByNameFunc(match) + if !ok { + return StructField{}, false + } + + return StructField{ + Name: field.Name, + PkgPath: field.PkgPath, + Type: field.Type, // note: converts RawType to Type + Tag: field.Tag, + Anonymous: field.Anonymous, + Offset: field.Offset, + Index: index, + }, true +} + +func (t *RawType) FieldByIndex(index []int) StructField { + ftype := t + var field rawStructField + + for _, n := range index { + structOrPtrToStruct := ftype.Kind() == Struct || (ftype.Kind() == Pointer && ftype.elem().Kind() == Struct) + if !structOrPtrToStruct { + panic(errTypeFieldByIndex) + } + + if ftype.Kind() == Pointer { + ftype = ftype.elem() + } + + field = ftype.rawField(n) + ftype = field.Type + } + + return StructField{ + Name: field.Name, + PkgPath: field.PkgPath, + Type: field.Type, // note: converts RawType to Type + Tag: field.Tag, + Anonymous: field.Anonymous, + Offset: field.Offset, + Index: index, + } +} + +// A StructField describes a single field in a struct. +type StructField struct { + // Name indicates the field name. + Name string + + // PkgPath is the package path where the struct containing this field is + // declared for unexported fields, or the empty string for exported fields. + PkgPath string + + Type Type + Tag StructTag // field tag string + Offset uintptr + Index []int // index sequence for Type.FieldByIndex + Anonymous bool +} + +// IsExported reports whether the field is exported. +func (f StructField) IsExported() bool { + return f.PkgPath == "" +} + +// rawStructField is the same as StructField but with the Type member replaced +// with RawType. For internal use only. Avoiding this conversion to the Type +// interface improves code size in many cases. +type rawStructField struct { + Name string + PkgPath string + Type *RawType + Tag StructTag + Offset uintptr + Anonymous bool +} + +// A StructTag is the tag string in a struct field. +type StructTag string + +// TODO: it would be feasible to do the key/value splitting at compile time, +// avoiding the code size cost of doing it at runtime + +// Get returns the value associated with key in the tag string. +func (tag StructTag) Get(key string) string { + v, _ := tag.Lookup(key) + return v +} + +// Lookup returns the value associated with key in the tag string. +func (tag StructTag) Lookup(key string) (value string, ok bool) { + for tag != "" { + // Skip leading space. + i := 0 + for i < len(tag) && tag[i] == ' ' { + i++ + } + tag = tag[i:] + if tag == "" { + break + } + + // Scan to colon. A space, a quote or a control character is a syntax error. + // Strictly speaking, control chars include the range [0x7f, 0x9f], not just + // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters + // as it is simpler to inspect the tag's bytes than the tag's runes. + i = 0 + for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { + i++ + } + if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { + break + } + name := string(tag[:i]) + tag = tag[i+1:] + + // Scan quoted string to find value. + i = 1 + for i < len(tag) && tag[i] != '"' { + if tag[i] == '\\' { + i++ + } + i++ + } + if i >= len(tag) { + break + } + qvalue := string(tag[:i+1]) + tag = tag[i+1:] + + if key == name { + value, err := unquote(qvalue) + if err != nil { + break + } + return value, true + } + } + return "", false +} + +// TypeError is the error that is used in a panic when invoking a method on a +// type that is not applicable to that type. +type TypeError struct { + Method string +} + +func (e *TypeError) Error() string { + return "reflect: call of reflect.Type." + e.Method + " on invalid type" +} + +func align(offset uintptr, alignment uintptr) uintptr { + return (offset + alignment - 1) &^ (alignment - 1) +} + +func SliceOf(t Type) Type { + panic("unimplemented: reflect.SliceOf()") +} + +func ArrayOf(n int, t Type) Type { + panic("unimplemented: reflect.ArrayOf()") +} + +func StructOf([]StructField) Type { + panic("unimplemented: reflect.StructOf()") +} + +func MapOf(key, value Type) Type { + panic("unimplemented: reflect.MapOf()") +} + +func FuncOf(in, out []Type, variadic bool) Type { + panic("unimplemented: reflect.FuncOf()") +} + +const maxVarintLen32 = 5 + +// encoding/binary.Uvarint, specialized for uint32 +func uvarint32(buf []byte) (uint32, int) { + var x uint32 + var s uint + for i, b := range buf { + if b < 0x80 { + return x | uint32(b)< unsafe.Sizeof(uintptr(0)) { + return int64(*(*int)(v.value)) + } else { + return int64(int(uintptr(v.value))) + } + case Int8: + if v.isIndirect() { + return int64(*(*int8)(v.value)) + } else { + return int64(int8(uintptr(v.value))) + } + case Int16: + if v.isIndirect() { + return int64(*(*int16)(v.value)) + } else { + return int64(int16(uintptr(v.value))) + } + case Int32: + if v.isIndirect() || unsafe.Sizeof(int32(0)) > unsafe.Sizeof(uintptr(0)) { + return int64(*(*int32)(v.value)) + } else { + return int64(int32(uintptr(v.value))) + } + case Int64: + if v.isIndirect() || unsafe.Sizeof(int64(0)) > unsafe.Sizeof(uintptr(0)) { + return int64(*(*int64)(v.value)) + } else { + return int64(int64(uintptr(v.value))) + } + default: + panic(&ValueError{Method: "Int", Kind: v.Kind()}) + } +} + +// CanUint reports whether Uint can be used without panicking. +func (v Value) CanUint() bool { + switch v.Kind() { + case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: + return true + default: + return false + } +} + +func (v Value) Uint() uint64 { + switch v.Kind() { + case Uintptr: + if v.isIndirect() { + return uint64(*(*uintptr)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + case Uint8: + if v.isIndirect() { + return uint64(*(*uint8)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + case Uint16: + if v.isIndirect() { + return uint64(*(*uint16)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + case Uint: + if v.isIndirect() || unsafe.Sizeof(uint(0)) > unsafe.Sizeof(uintptr(0)) { + return uint64(*(*uint)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + case Uint32: + if v.isIndirect() || unsafe.Sizeof(uint32(0)) > unsafe.Sizeof(uintptr(0)) { + return uint64(*(*uint32)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + case Uint64: + if v.isIndirect() || unsafe.Sizeof(uint64(0)) > unsafe.Sizeof(uintptr(0)) { + return uint64(*(*uint64)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + default: + panic(&ValueError{Method: "Uint", Kind: v.Kind()}) + } +} + +// CanFloat reports whether Float can be used without panicking. +func (v Value) CanFloat() bool { + switch v.Kind() { + case Float32, Float64: + return true + default: + return false + } +} + +func (v Value) Float32() float32 { + switch v.Kind() { + case Float32: + if v.isIndirect() || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) { + // The float is stored as an external value on systems with 16-bit + // pointers. + return *(*float32)(v.value) + } else { + // The float is directly stored in the interface value on systems + // with 32-bit and 64-bit pointers. + return *(*float32)(unsafe.Pointer(&v.value)) + } + + case Float64: + return float32(v.Float()) + + } + + panic(&ValueError{Method: "Float", Kind: v.Kind()}) +} + +func (v Value) Float() float64 { + switch v.Kind() { + case Float32: + if v.isIndirect() || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) { + // The float is stored as an external value on systems with 16-bit + // pointers. + return float64(*(*float32)(v.value)) + } else { + // The float is directly stored in the interface value on systems + // with 32-bit and 64-bit pointers. + return float64(*(*float32)(unsafe.Pointer(&v.value))) + } + case Float64: + if v.isIndirect() || unsafe.Sizeof(float64(0)) > unsafe.Sizeof(uintptr(0)) { + // For systems with 16-bit and 32-bit pointers. + return *(*float64)(v.value) + } else { + // The float is directly stored in the interface value on systems + // with 64-bit pointers. + return *(*float64)(unsafe.Pointer(&v.value)) + } + default: + panic(&ValueError{Method: "Float", Kind: v.Kind()}) + } +} + +// CanComplex reports whether Complex can be used without panicking. +func (v Value) CanComplex() bool { + switch v.Kind() { + case Complex64, Complex128: + return true + default: + return false + } +} + +func (v Value) Complex() complex128 { + switch v.Kind() { + case Complex64: + if v.isIndirect() || unsafe.Sizeof(complex64(0)) > unsafe.Sizeof(uintptr(0)) { + // The complex number is stored as an external value on systems with + // 16-bit and 32-bit pointers. + return complex128(*(*complex64)(v.value)) + } else { + // The complex number is directly stored in the interface value on + // systems with 64-bit pointers. + return complex128(*(*complex64)(unsafe.Pointer(&v.value))) + } + case Complex128: + // This is a 128-bit value, which is always stored as an external value. + // It may be stored in the pointer directly on very uncommon + // architectures with 128-bit pointers, however. + return *(*complex128)(v.value) + default: + panic(&ValueError{Method: "Complex", Kind: v.Kind()}) + } +} + +func (v Value) String() string { + switch v.Kind() { + case String: + // A string value is always bigger than a pointer as it is made of a + // pointer and a length. + return *(*string)(v.value) + default: + // Special case because of the special treatment of .String() in Go. + return "<" + v.typecode.String() + " Value>" + } +} + +func (v Value) Bytes() []byte { + switch v.Kind() { + case Slice: + if v.typecode.elem().Kind() != Uint8 { + panic(&ValueError{Method: "Bytes", Kind: v.Kind()}) + } + return *(*[]byte)(v.value) + + case Array: + v.checkAddressable() + + if v.typecode.elem().Kind() != Uint8 { + panic(&ValueError{Method: "Bytes", Kind: v.Kind()}) + } + + // Small inline arrays are not addressable, so we only have to + // handle addressable arrays which will be stored as pointers + // in v.value + return unsafe.Slice((*byte)(v.value), v.Len()) + } + + panic(&ValueError{Method: "Bytes", Kind: v.Kind()}) +} + +func (v Value) Slice(i, j int) Value { + switch v.Kind() { + case Slice: + hdr := *(*sliceHeader)(v.value) + i, j := uintptr(i), uintptr(j) + + if j < i || hdr.cap < j { + slicePanic() + } + + elemSize := v.typecode.underlying().elem().Size() + + hdr.len = j - i + hdr.cap = hdr.cap - i + hdr.data = unsafe.Add(hdr.data, i*elemSize) + + return Value{ + typecode: v.typecode, + value: unsafe.Pointer(&hdr), + flags: v.flags, + } + + case Array: + v.checkAddressable() + buf, length := buflen(v) + i, j := uintptr(i), uintptr(j) + if j < i || length < j { + slicePanic() + } + + elemSize := v.typecode.underlying().elem().Size() + + var hdr sliceHeader + hdr.len = j - i + hdr.cap = length - i + hdr.data = unsafe.Add(buf, i*elemSize) + + sliceType := (*arrayType)(unsafe.Pointer(v.typecode.underlying())).slicePtr + return Value{ + typecode: sliceType, + value: unsafe.Pointer(&hdr), + flags: v.flags, + } + + case String: + i, j := uintptr(i), uintptr(j) + str := *(*stringHeader)(v.value) + + if j < i || str.len < j { + slicePanic() + } + + hdr := stringHeader{ + data: unsafe.Add(str.data, i), + len: j - i, + } + + return Value{ + typecode: v.typecode, + value: unsafe.Pointer(&hdr), + flags: v.flags, + } + } + + panic(&ValueError{Method: "Slice", Kind: v.Kind()}) +} + +func (v Value) Slice3(i, j, k int) Value { + switch v.Kind() { + case Slice: + hdr := *(*sliceHeader)(v.value) + i, j, k := uintptr(i), uintptr(j), uintptr(k) + + if j < i || k < j || hdr.len < k { + slicePanic() + } + + elemSize := v.typecode.underlying().elem().Size() + + hdr.len = j - i + hdr.cap = k - i + hdr.data = unsafe.Add(hdr.data, i*elemSize) + + return Value{ + typecode: v.typecode, + value: unsafe.Pointer(&hdr), + flags: v.flags, + } + + case Array: + v.checkAddressable() + buf, length := buflen(v) + i, j, k := uintptr(i), uintptr(j), uintptr(k) + if j < i || k < j || length < k { + slicePanic() + } + + elemSize := v.typecode.underlying().elem().Size() + + var hdr sliceHeader + hdr.len = j - i + hdr.cap = k - i + hdr.data = unsafe.Add(buf, i*elemSize) + + sliceType := (*arrayType)(unsafe.Pointer(v.typecode.underlying())).slicePtr + return Value{ + typecode: sliceType, + value: unsafe.Pointer(&hdr), + flags: v.flags, + } + } + + panic("unimplemented: (reflect.Value).Slice3()") +} + +//go:linkname maplen runtime.hashmapLen +func maplen(p unsafe.Pointer) int + +//go:linkname chanlen runtime.chanLen +func chanlen(p unsafe.Pointer) int + +// Len returns the length of this value for slices, strings, arrays, channels, +// and maps. For other types, it panics. +func (v Value) Len() int { + switch v.typecode.Kind() { + case Array: + return v.typecode.Len() + case Chan: + return chanlen(v.pointer()) + case Map: + return maplen(v.pointer()) + case Slice: + return int((*sliceHeader)(v.value).len) + case String: + return int((*stringHeader)(v.value).len) + default: + panic(&ValueError{Method: "Len", Kind: v.Kind()}) + } +} + +//go:linkname chancap runtime.chanCap +func chancap(p unsafe.Pointer) int + +// Cap returns the capacity of this value for arrays, channels and slices. +// For other types, it panics. +func (v Value) Cap() int { + switch v.typecode.Kind() { + case Array: + return v.typecode.Len() + case Chan: + return chancap(v.pointer()) + case Slice: + return int((*sliceHeader)(v.value).cap) + default: + panic(&ValueError{Method: "Cap", Kind: v.Kind()}) + } +} + +//go:linkname mapclear runtime.hashmapClear +func mapclear(p unsafe.Pointer) + +// Clear clears the contents of a map or zeros the contents of a slice +// +// It panics if v's Kind is not Map or Slice. +func (v Value) Clear() { + switch v.typecode.Kind() { + case Map: + mapclear(v.pointer()) + case Slice: + hdr := (*sliceHeader)(v.value) + elemSize := v.typecode.underlying().elem().Size() + memzero(hdr.data, elemSize*hdr.len) + default: + panic(&ValueError{Method: "Clear", Kind: v.Kind()}) + } +} + +// NumField returns the number of fields of this struct. It panics for other +// value types. +func (v Value) NumField() int { + return v.typecode.NumField() +} + +func (v Value) Elem() Value { + switch v.Kind() { + case Ptr: + ptr := v.pointer() + if ptr == nil { + return Value{} + } + // Don't copy RO flags + flags := (v.flags & (valueFlagIndirect | valueFlagExported)) | valueFlagIndirect + return Value{ + typecode: v.typecode.elem(), + value: ptr, + flags: flags, + } + case Interface: + typecode, value := decomposeInterface(*(*interface{})(v.value)) + return Value{ + typecode: (*RawType)(typecode), + value: value, + flags: v.flags &^ valueFlagIndirect, + } + default: + panic(&ValueError{Method: "Elem", Kind: v.Kind()}) + } +} + +// Field returns the value of the i'th field of this struct. +func (v Value) Field(i int) Value { + if v.Kind() != Struct { + panic(&ValueError{Method: "Field", Kind: v.Kind()}) + } + structField := v.typecode.rawField(i) + + // Copy flags but clear EmbedRO; we're not an embedded field anymore + flags := v.flags & ^valueFlagEmbedRO + if structField.PkgPath != "" { + // No PkgPath => not exported. + // Clear exported flag even if the parent was exported. + flags &^= valueFlagExported + + // Update the RO flag + if structField.Anonymous { + // Embedded field + flags |= valueFlagEmbedRO + } else { + flags |= valueFlagStickyRO + } + } else { + // Parent field may not have been exported but we are + flags |= valueFlagExported + } + + size := v.typecode.Size() + fieldType := structField.Type + fieldSize := fieldType.Size() + if v.isIndirect() || fieldSize > unsafe.Sizeof(uintptr(0)) { + // v.value was already a pointer to the value and it should stay that + // way. + return Value{ + flags: flags, + typecode: fieldType, + value: unsafe.Add(v.value, structField.Offset), + } + } + + // The fieldSize is smaller than uintptr, which means that the value will + // have to be stored directly in the interface value. + + if fieldSize == 0 { + // The struct field is zero sized. + // This is a rare situation, but because it's undefined behavior + // to shift the size of the value (zeroing the value), handle this + // situation explicitly. + return Value{ + flags: flags, + typecode: fieldType, + value: unsafe.Pointer(nil), + } + } + + if size > unsafe.Sizeof(uintptr(0)) { + // The value was not stored in the interface before but will be + // afterwards, so load the value (from the correct offset) and return + // it. + ptr := unsafe.Add(v.value, structField.Offset) + value := unsafe.Pointer(loadValue(ptr, fieldSize)) + return Value{ + flags: flags &^ valueFlagIndirect, + typecode: fieldType, + value: value, + } + } + + // The value was already stored directly in the interface and it still + // is. Cut out the part of the value that we need. + value := maskAndShift(uintptr(v.value), structField.Offset, fieldSize) + return Value{ + flags: flags, + typecode: fieldType, + value: unsafe.Pointer(value), + } +} + +var uint8Type = TypeOf(uint8(0)).(*RawType) + +func (v Value) Index(i int) Value { + switch v.Kind() { + case Slice: + // Extract an element from the slice. + slice := *(*sliceHeader)(v.value) + if uint(i) >= uint(slice.len) { + panic("reflect: slice index out of range") + } + flags := (v.flags & (valueFlagExported | valueFlagIndirect)) | valueFlagIndirect | v.flags.ro() + elem := Value{ + typecode: v.typecode.elem(), + flags: flags, + } + elem.value = unsafe.Add(slice.data, elem.typecode.Size()*uintptr(i)) // pointer to new value + return elem + case String: + // Extract a character from a string. + // A string is never stored directly in the interface, but always as a + // pointer to the string value. + // Keeping valueFlagExported if set, but don't set valueFlagIndirect + // otherwise CanSet will return true for string elements (which is bad, + // strings are read-only). + s := *(*stringHeader)(v.value) + if uint(i) >= uint(s.len) { + panic("reflect: string index out of range") + } + return Value{ + typecode: uint8Type, + value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Add(s.data, i)))), + flags: v.flags & valueFlagExported, + } + case Array: + // Extract an element from the array. + elemType := v.typecode.elem() + elemSize := elemType.Size() + size := v.typecode.Size() + if size == 0 { + // The element size is 0 and/or the length of the array is 0. + return Value{ + typecode: v.typecode.elem(), + flags: v.flags, + } + } + if elemSize > unsafe.Sizeof(uintptr(0)) { + // The resulting value doesn't fit in a pointer so must be + // indirect. Also, because size != 0 this implies that the array + // length must be != 0, and thus that the total size is at least + // elemSize. + addr := unsafe.Add(v.value, elemSize*uintptr(i)) // pointer to new value + return Value{ + typecode: v.typecode.elem(), + flags: v.flags, + value: addr, + } + } + + if size > unsafe.Sizeof(uintptr(0)) || v.isIndirect() { + // The element fits in a pointer, but the array is not stored in the pointer directly. + // Load the value from the pointer. + addr := unsafe.Add(v.value, elemSize*uintptr(i)) // pointer to new value + value := addr + if !v.isIndirect() { + // Use a pointer to the value (don't load the value) if the + // 'indirect' flag is set. + value = unsafe.Pointer(loadValue(addr, elemSize)) + } + return Value{ + typecode: v.typecode.elem(), + flags: v.flags, + value: value, + } + } + + // The value fits in a pointer, so extract it with some shifting and + // masking. + offset := elemSize * uintptr(i) + value := maskAndShift(uintptr(v.value), offset, elemSize) + return Value{ + typecode: v.typecode.elem(), + flags: v.flags, + value: unsafe.Pointer(value), + } + default: + panic(&ValueError{Method: "Index", Kind: v.Kind()}) + } +} + +func (v Value) NumMethod() int { + if v.typecode == nil { + panic(&ValueError{Method: "reflect.Value.NumMethod", Kind: Invalid}) + } + return v.typecode.NumMethod() +} + +// OverflowFloat reports whether the float64 x cannot be represented by v's type. +// It panics if v's Kind is not Float32 or Float64. +func (v Value) OverflowFloat(x float64) bool { + k := v.Kind() + switch k { + case Float32: + return overflowFloat32(x) + case Float64: + return false + } + panic(&ValueError{Method: "reflect.Value.OverflowFloat", Kind: v.Kind()}) +} + +func overflowFloat32(x float64) bool { + if x < 0 { + x = -x + } + return math.MaxFloat32 < x && x <= math.MaxFloat64 +} + +func (v Value) MapKeys() []Value { + if v.Kind() != Map { + panic(&ValueError{Method: "MapKeys", Kind: v.Kind()}) + } + + // empty map + if v.Len() == 0 { + return nil + } + + keys := make([]Value, 0, v.Len()) + + it := hashmapNewIterator() + k := New(v.typecode.Key()) + e := New(v.typecode.Elem()) + + keyType := v.typecode.key() + keyTypeIsEmptyInterface := keyType.Kind() == Interface && keyType.NumMethod() == 0 + shouldUnpackInterface := !keyTypeIsEmptyInterface && keyType.Kind() != String && !keyType.isBinary() + + for hashmapNext(v.pointer(), it, k.value, e.value) { + if shouldUnpackInterface { + intf := *(*interface{})(k.value) + v := ValueOf(intf) + keys = append(keys, v) + } else { + keys = append(keys, k.Elem()) + } + k = New(v.typecode.Key()) + } + + return keys +} + +//go:linkname hashmapStringGet runtime.hashmapStringGet +func hashmapStringGet(m unsafe.Pointer, key string, value unsafe.Pointer, valueSize uintptr) bool + +//go:linkname hashmapBinaryGet runtime.hashmapBinaryGet +func hashmapBinaryGet(m unsafe.Pointer, key, value unsafe.Pointer, valueSize uintptr) bool + +//go:linkname hashmapInterfaceGet runtime.hashmapInterfaceGet +func hashmapInterfaceGet(m unsafe.Pointer, key interface{}, value unsafe.Pointer, valueSize uintptr) bool + +func (v Value) MapIndex(key Value) Value { + if v.Kind() != Map { + panic(&ValueError{Method: "MapIndex", Kind: v.Kind()}) + } + + vkey := v.typecode.key() + + // compare key type with actual key type of map + if !key.typecode.AssignableTo(vkey) { + // type error? + panic("reflect.Value.MapIndex: incompatible types for key") + } + + elemType := v.typecode.Elem() + elem := New(elemType) + + if vkey.Kind() == String { + if ok := hashmapStringGet(v.pointer(), *(*string)(key.value), elem.value, elemType.Size()); !ok { + return Value{} + } + return elem.Elem() + } else if vkey.isBinary() { + var keyptr unsafe.Pointer + if key.isIndirect() || key.typecode.Size() > unsafe.Sizeof(uintptr(0)) { + keyptr = key.value + } else { + keyptr = unsafe.Pointer(&key.value) + } + //TODO(dgryski): zero out padding bytes in key, if any + if ok := hashmapBinaryGet(v.pointer(), keyptr, elem.value, elemType.Size()); !ok { + return Value{} + } + return elem.Elem() + } else { + if ok := hashmapInterfaceGet(v.pointer(), key.Interface(), elem.value, elemType.Size()); !ok { + return Value{} + } + return elem.Elem() + } +} + +//go:linkname hashmapNewIterator runtime.hashmapNewIterator +func hashmapNewIterator() unsafe.Pointer + +//go:linkname hashmapNext runtime.hashmapNext +func hashmapNext(m unsafe.Pointer, it unsafe.Pointer, key, value unsafe.Pointer) bool + +func (v Value) MapRange() *MapIter { + if v.Kind() != Map { + panic(&ValueError{Method: "MapRange", Kind: v.Kind()}) + } + + keyType := v.typecode.key() + + keyTypeIsEmptyInterface := keyType.Kind() == Interface && keyType.NumMethod() == 0 + shouldUnpackInterface := !keyTypeIsEmptyInterface && keyType.Kind() != String && !keyType.isBinary() + + return &MapIter{ + m: v, + it: hashmapNewIterator(), + unpackKeyInterface: shouldUnpackInterface, + } +} + +type MapIter struct { + m Value + it unsafe.Pointer + key Value + val Value + + valid bool + unpackKeyInterface bool +} + +func (it *MapIter) Key() Value { + if !it.valid { + panic("reflect.MapIter.Key called on invalid iterator") + } + + if it.unpackKeyInterface { + intf := *(*interface{})(it.key.value) + v := ValueOf(intf) + return v + } + + return it.key.Elem() +} + +func (it *MapIter) Value() Value { + if !it.valid { + panic("reflect.MapIter.Value called on invalid iterator") + } + + return it.val.Elem() +} + +func (it *MapIter) Next() bool { + it.key = New(it.m.typecode.Key()) + it.val = New(it.m.typecode.Elem()) + + it.valid = hashmapNext(it.m.pointer(), it.it, it.key.value, it.val.value) + return it.valid +} + +func (v Value) Set(x Value) { + v.checkAddressable() + v.checkRO() + if !x.typecode.AssignableTo(v.typecode) { + panic("reflect.Value.Set: value of type " + x.typecode.String() + " cannot be assigned to type " + v.typecode.String()) + } + + if v.typecode.Kind() == Interface && x.typecode.Kind() != Interface { + // move the value of x back into the interface, if possible + if x.isIndirect() && x.typecode.Size() <= unsafe.Sizeof(uintptr(0)) { + x.value = unsafe.Pointer(loadValue(x.value, x.typecode.Size())) + } + + intf := composeInterface(unsafe.Pointer(x.typecode), x.value) + x = Value{ + typecode: v.typecode, + value: unsafe.Pointer(&intf), + } + } + + size := v.typecode.Size() + if size <= unsafe.Sizeof(uintptr(0)) && !x.isIndirect() { + storeValue(v.value, size, uintptr(x.value)) + } else { + memcpy(v.value, x.value, size) + } +} + +func (v Value) SetZero() { + v.checkAddressable() + v.checkRO() + size := v.typecode.Size() + memzero(v.value, size) +} + +func (v Value) SetBool(x bool) { + v.checkAddressable() + v.checkRO() + switch v.Kind() { + case Bool: + *(*bool)(v.value) = x + default: + panic(&ValueError{Method: "SetBool", Kind: v.Kind()}) + } +} + +func (v Value) SetInt(x int64) { + v.checkAddressable() + v.checkRO() + switch v.Kind() { + case Int: + *(*int)(v.value) = int(x) + case Int8: + *(*int8)(v.value) = int8(x) + case Int16: + *(*int16)(v.value) = int16(x) + case Int32: + *(*int32)(v.value) = int32(x) + case Int64: + *(*int64)(v.value) = x + default: + panic(&ValueError{Method: "SetInt", Kind: v.Kind()}) + } +} + +func (v Value) SetUint(x uint64) { + v.checkAddressable() + v.checkRO() + switch v.Kind() { + case Uint: + *(*uint)(v.value) = uint(x) + case Uint8: + *(*uint8)(v.value) = uint8(x) + case Uint16: + *(*uint16)(v.value) = uint16(x) + case Uint32: + *(*uint32)(v.value) = uint32(x) + case Uint64: + *(*uint64)(v.value) = x + case Uintptr: + *(*uintptr)(v.value) = uintptr(x) + default: + panic(&ValueError{Method: "SetUint", Kind: v.Kind()}) + } +} + +func (v Value) SetFloat(x float64) { + v.checkAddressable() + v.checkRO() + switch v.Kind() { + case Float32: + *(*float32)(v.value) = float32(x) + case Float64: + *(*float64)(v.value) = x + default: + panic(&ValueError{Method: "SetFloat", Kind: v.Kind()}) + } +} + +func (v Value) SetComplex(x complex128) { + v.checkAddressable() + v.checkRO() + switch v.Kind() { + case Complex64: + *(*complex64)(v.value) = complex64(x) + case Complex128: + *(*complex128)(v.value) = x + default: + panic(&ValueError{Method: "SetComplex", Kind: v.Kind()}) + } +} + +func (v Value) SetString(x string) { + v.checkAddressable() + v.checkRO() + switch v.Kind() { + case String: + *(*string)(v.value) = x + default: + panic(&ValueError{Method: "SetString", Kind: v.Kind()}) + } +} + +func (v Value) SetBytes(x []byte) { + v.checkAddressable() + v.checkRO() + if v.typecode.Kind() != Slice || v.typecode.elem().Kind() != Uint8 { + panic("reflect.Value.SetBytes called on not []byte") + } + + // copy the header contents over + *(*[]byte)(v.value) = x +} + +func (v Value) SetCap(n int) { + panic("unimplemented: (reflect.Value).SetCap()") +} + +func (v Value) SetLen(n int) { + if v.typecode.Kind() != Slice { + panic(&ValueError{Method: "reflect.Value.SetLen", Kind: v.Kind()}) + } + v.checkAddressable() + hdr := (*sliceHeader)(v.value) + if int(uintptr(n)) != n || uintptr(n) > hdr.cap { + panic("reflect.Value.SetLen: slice length out of range") + } + hdr.len = uintptr(n) +} + +func (v Value) checkAddressable() { + if !v.isIndirect() { + panic("reflect: value is not addressable") + } +} + +// OverflowInt reports whether the int64 x cannot be represented by v's type. +// It panics if v's Kind is not Int, Int8, Int16, Int32, or Int64. +func (v Value) OverflowInt(x int64) bool { + switch v.Kind() { + case Int, Int8, Int16, Int32, Int64: + bitSize := v.typecode.Size() * 8 + trunc := (x << (64 - bitSize)) >> (64 - bitSize) + return x != trunc + } + panic(&ValueError{Method: "reflect.Value.OverflowInt", Kind: v.Kind()}) +} + +// OverflowUint reports whether the uint64 x cannot be represented by v's type. +// It panics if v's Kind is not Uint, Uintptr, Uint8, Uint16, Uint32, or Uint64. +func (v Value) OverflowUint(x uint64) bool { + k := v.Kind() + switch k { + case Uint, Uintptr, Uint8, Uint16, Uint32, Uint64: + bitSize := v.typecode.Size() * 8 + trunc := (x << (64 - bitSize)) >> (64 - bitSize) + return x != trunc + } + panic(&ValueError{Method: "reflect.Value.OverflowUint", Kind: v.Kind()}) +} + +func (v Value) CanConvert(t Type) bool { + // TODO: Optimize this to not actually perform a conversion + _, ok := convertOp(v, t) + return ok +} + +func (v Value) Convert(t Type) Value { + if v, ok := convertOp(v, t); ok { + return v + } + + panic("reflect.Value.Convert: value of type " + v.typecode.String() + " cannot be converted to type " + t.String()) +} + +func convertOp(src Value, typ Type) (Value, bool) { + + // Easy check first. Do we even need to do anything? + if src.typecode.underlying() == typ.(*RawType).underlying() { + return Value{ + typecode: typ.(*RawType), + value: src.value, + flags: src.flags, + }, true + } + + if rtype := typ.(*RawType); rtype.Kind() == Interface && rtype.NumMethod() == 0 { + iface := composeInterface(unsafe.Pointer(src.typecode), src.value) + return Value{ + typecode: rtype, + value: unsafe.Pointer(&iface), + flags: valueFlagExported, + }, true + } + + switch src.Kind() { + case Int, Int8, Int16, Int32, Int64: + switch rtype := typ.(*RawType); rtype.Kind() { + case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: + return cvtInt(src, rtype), true + case Float32, Float64: + return cvtIntFloat(src, rtype), true + case String: + return cvtIntString(src, rtype), true + } + + case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: + switch rtype := typ.(*RawType); rtype.Kind() { + case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: + return cvtUint(src, rtype), true + case Float32, Float64: + return cvtUintFloat(src, rtype), true + case String: + return cvtUintString(src, rtype), true + } + + case Float32, Float64: + switch rtype := typ.(*RawType); rtype.Kind() { + case Int, Int8, Int16, Int32, Int64: + return cvtFloatInt(src, rtype), true + case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: + return cvtFloatUint(src, rtype), true + case Float32, Float64: + return cvtFloat(src, rtype), true + } + + /* + case Complex64, Complex128: + switch src.Kind() { + case Complex64, Complex128: + return cvtComplex + } + */ + + case Slice: + switch rtype := typ.(*RawType); rtype.Kind() { + case Array: + if src.typecode.elem() == rtype.elem() && rtype.Len() <= src.Len() { + return Value{ + typecode: rtype, + value: (*sliceHeader)(src.value).data, + flags: src.flags | valueFlagIndirect, + }, true + } + case Pointer: + if rtype.Elem().Kind() == Array { + if src.typecode.elem() == rtype.elem().elem() && rtype.elem().Len() <= src.Len() { + return Value{ + typecode: rtype, + value: (*sliceHeader)(src.value).data, + flags: src.flags & (valueFlagExported | valueFlagRO), + }, true + } + } + case String: + if !src.typecode.elem().isNamed() { + switch src.Type().Elem().Kind() { + case Uint8: + return cvtBytesString(src, rtype), true + case Int32: + return cvtRunesString(src, rtype), true + } + } + } + + case String: + rtype := typ.(*RawType) + if typ.Kind() == Slice && !rtype.elem().isNamed() { + switch typ.Elem().Kind() { + case Uint8: + return cvtStringBytes(src, rtype), true + case Int32: + return cvtStringRunes(src, rtype), true + } + } + } + + // TODO(dgryski): Unimplemented: + // Chan + // Non-defined pointers types with same underlying base type + // Interface <-> Type conversions + + return Value{}, false +} + +func cvtInt(v Value, t *RawType) Value { + return makeInt(v.flags, uint64(v.Int()), t) +} + +func cvtUint(v Value, t *RawType) Value { + return makeInt(v.flags, v.Uint(), t) +} + +func cvtIntFloat(v Value, t *RawType) Value { + return makeFloat(v.flags, float64(v.Int()), t) +} + +func cvtUintFloat(v Value, t *RawType) Value { + return makeFloat(v.flags, float64(v.Uint()), t) +} + +func cvtFloatInt(v Value, t *RawType) Value { + return makeInt(v.flags, uint64(int64(v.Float())), t) +} + +func cvtFloatUint(v Value, t *RawType) Value { + return makeInt(v.flags, uint64(v.Float()), t) +} + +func cvtFloat(v Value, t *RawType) Value { + if v.Type().Kind() == Float32 && t.Kind() == Float32 { + // Don't do any conversion if both types have underlying type float32. + // This avoids converting to float64 and back, which will + // convert a signaling NaN to a quiet NaN. See issue 36400. + return makeFloat32(v.flags, v.Float32(), t) + } + return makeFloat(v.flags, v.Float(), t) +} + +//go:linkname stringToBytes runtime.stringToBytes +func stringToBytes(x string) []byte + +func cvtStringBytes(v Value, t *RawType) Value { + b := stringToBytes(*(*string)(v.value)) + return Value{ + typecode: t, + value: unsafe.Pointer(&b), + flags: v.flags, + } +} + +//go:linkname stringFromBytes runtime.stringFromBytes +func stringFromBytes(x []byte) string + +func cvtBytesString(v Value, t *RawType) Value { + s := stringFromBytes(*(*[]byte)(v.value)) + return Value{ + typecode: t, + value: unsafe.Pointer(&s), + flags: v.flags, + } +} + +func makeInt(flags valueFlags, bits uint64, t *RawType) Value { + size := t.Size() + + v := Value{ + typecode: t, + flags: flags, + } + + ptr := unsafe.Pointer(&v.value) + if size > unsafe.Sizeof(uintptr(0)) { + ptr = alloc(size, nil) + v.value = ptr + } + + switch size { + case 1: + *(*uint8)(ptr) = uint8(bits) + case 2: + *(*uint16)(ptr) = uint16(bits) + case 4: + *(*uint32)(ptr) = uint32(bits) + case 8: + *(*uint64)(ptr) = bits + } + return v +} + +func makeFloat(flags valueFlags, f float64, t *RawType) Value { + size := t.Size() + + v := Value{ + typecode: t, + flags: flags, + } + + ptr := unsafe.Pointer(&v.value) + if size > unsafe.Sizeof(uintptr(0)) { + ptr = alloc(size, nil) + v.value = ptr + } + + switch size { + case 4: + *(*float32)(ptr) = float32(f) + case 8: + *(*float64)(ptr) = f + } + return v +} + +func makeFloat32(flags valueFlags, f float32, t *RawType) Value { + v := Value{ + typecode: t, + flags: flags, + } + *(*float32)(unsafe.Pointer(&v.value)) = float32(f) + return v +} + +func cvtIntString(src Value, t *RawType) Value { + panic("cvtUintString: unimplemented") +} + +func cvtUintString(src Value, t *RawType) Value { + panic("cvtUintString: unimplemented") +} + +func cvtStringRunes(src Value, t *RawType) Value { + panic("cvsStringRunes: unimplemented") +} + +func cvtRunesString(src Value, t *RawType) Value { + panic("cvsRunesString: unimplemented") +} + +//go:linkname slicePanic runtime.slicePanic +func slicePanic() + +func MakeSlice(typ Type, len, cap int) Value { + if typ.Kind() != Slice { + panic("reflect.MakeSlice of non-slice type") + } + + rtype := typ.(*RawType) + + ulen := uint(len) + ucap := uint(cap) + maxSize := (^uintptr(0)) / 2 + elem := rtype.elem() + elementSize := elem.Size() + if elementSize > 1 { + maxSize /= uintptr(elementSize) + } + if ulen > ucap || ucap > uint(maxSize) { + slicePanic() + } + + // This can't overflow because of the above checks. + size := uintptr(ucap) * elementSize + + var slice sliceHeader + slice.cap = uintptr(ucap) + slice.len = uintptr(ulen) + layout := elem.gcLayout() + + slice.data = alloc(size, layout) + + return Value{ + typecode: rtype, + value: unsafe.Pointer(&slice), + flags: valueFlagExported, + } +} + +var zerobuffer unsafe.Pointer + +const zerobufferLen = 32 + +func init() { + // 32 characters of zero bytes + zerobufferStr := "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + s := (*stringHeader)(unsafe.Pointer(&zerobufferStr)) + zerobuffer = s.data +} + +func Zero(typ Type) Value { + size := typ.Size() + if size <= unsafe.Sizeof(uintptr(0)) { + return Value{ + typecode: typ.(*RawType), + value: nil, + flags: valueFlagExported | valueFlagRO, + } + } + + if size <= zerobufferLen { + return Value{ + typecode: typ.(*RawType), + value: unsafe.Pointer(zerobuffer), + flags: valueFlagExported | valueFlagRO, + } + } + + return Value{ + typecode: typ.(*RawType), + value: alloc(size, nil), + flags: valueFlagExported | valueFlagRO, + } +} + +// New is the reflect equivalent of the new(T) keyword, returning a pointer to a +// new value of the given type. +func New(typ Type) Value { + return Value{ + typecode: pointerTo(typ.(*RawType)), + value: alloc(typ.Size(), nil), + flags: valueFlagExported, + } +} + +type funcHeader struct { + Context unsafe.Pointer + Code unsafe.Pointer +} + +type SliceHeader struct { + Data uintptr + Len intw + Cap intw +} + +// Slice header that matches the underlying structure. Used for when we switch +// to a precise GC, which needs to know exactly where pointers live. +type sliceHeader struct { + data unsafe.Pointer + len uintptr + cap uintptr +} + +type StringHeader struct { + Data uintptr + Len intw +} + +// Like sliceHeader, this type is used internally to make sure pointer and +// non-pointer fields match those of actual strings. +type stringHeader struct { + data unsafe.Pointer + len uintptr +} + +// Verify SliceHeader and StringHeader sizes. +// See https://github.com/tinygo-org/tinygo/pull/4156 +// and https://github.com/tinygo-org/tinygo/issues/1284. +var ( + _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(SliceHeader{})]byte{} + _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(sliceHeader{})]byte{} + _ [unsafe.Sizeof("")]byte = [unsafe.Sizeof(StringHeader{})]byte{} + _ [unsafe.Sizeof("")]byte = [unsafe.Sizeof(stringHeader{})]byte{} +) + +type ValueError struct { + Method string + Kind Kind +} + +func (e *ValueError) Error() string { + if e.Kind == 0 { + return "reflect: call of " + e.Method + " on zero Value" + } + return "reflect: call of " + e.Method + " on " + e.Kind.String() + " Value" +} + +//go:linkname memcpy runtime.memcpy +func memcpy(dst, src unsafe.Pointer, size uintptr) + +//go:linkname memzero runtime.memzero +func memzero(ptr unsafe.Pointer, size uintptr) + +//go:linkname alloc runtime.alloc +func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer + +//go:linkname sliceAppend runtime.sliceAppend +func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen uintptr, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) + +//go:linkname sliceCopy runtime.sliceCopy +func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr) int + +// Copy copies the contents of src into dst until either +// dst has been filled or src has been exhausted. +func Copy(dst, src Value) int { + compatibleTypes := false || + // dst and src are both slices or arrays with equal types + ((dst.typecode.Kind() == Slice || dst.typecode.Kind() == Array) && + (src.typecode.Kind() == Slice || src.typecode.Kind() == Array) && + (dst.typecode.elem() == src.typecode.elem())) || + // dst is array or slice of uint8 and src is string + ((dst.typecode.Kind() == Slice || dst.typecode.Kind() == Array) && + dst.typecode.elem().Kind() == Uint8 && + src.typecode.Kind() == String) + + if !compatibleTypes { + panic("Copy: type mismatch: " + dst.typecode.String() + "/" + src.typecode.String()) + } + + // Can read from an unaddressable array but not write to one. + if dst.typecode.Kind() == Array && !dst.isIndirect() { + panic("reflect.Copy: unaddressable array value") + } + + dstbuf, dstlen := buflen(dst) + srcbuf, srclen := buflen(src) + + if srclen > 0 { + dst.checkRO() + } + + return sliceCopy(dstbuf, srcbuf, dstlen, srclen, dst.typecode.elem().Size()) +} + +func buflen(v Value) (unsafe.Pointer, uintptr) { + var buf unsafe.Pointer + var len uintptr + switch v.typecode.Kind() { + case Slice: + hdr := (*sliceHeader)(v.value) + buf = hdr.data + len = hdr.len + case Array: + if v.isIndirect() || v.typecode.Size() > unsafe.Sizeof(uintptr(0)) { + buf = v.value + } else { + buf = unsafe.Pointer(&v.value) + } + len = uintptr(v.Len()) + case String: + hdr := (*stringHeader)(v.value) + buf = hdr.data + len = hdr.len + default: + // This shouldn't happen + panic("reflect.Copy: not slice or array or string") + } + + return buf, len +} + +//go:linkname sliceGrow runtime.sliceGrow +func sliceGrow(buf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) + +// extend slice to hold n new elements +func extendSlice(v Value, n int) sliceHeader { + if v.Kind() != Slice { + panic(&ValueError{Method: "extendSlice", Kind: v.Kind()}) + } + + var old sliceHeader + if v.value != nil { + old = *(*sliceHeader)(v.value) + } + + nbuf, nlen, ncap := sliceGrow(old.data, old.len, old.cap, old.len+uintptr(n), v.typecode.elem().Size()) + + return sliceHeader{ + data: nbuf, + len: nlen + uintptr(n), + cap: ncap, + } +} + +// Append appends the values x to a slice s and returns the resulting slice. +// As in Go, each x's value must be assignable to the slice's element type. +func Append(v Value, x ...Value) Value { + if v.Kind() != Slice { + panic(&ValueError{Method: "Append", Kind: v.Kind()}) + } + oldLen := v.Len() + newslice := extendSlice(v, len(x)) + v.flags = valueFlagExported + v.value = (unsafe.Pointer)(&newslice) + for i, xx := range x { + v.Index(oldLen + i).Set(xx) + } + return v +} + +// AppendSlice appends a slice t to a slice s and returns the resulting slice. +// The slices s and t must have the same element type. +func AppendSlice(s, t Value) Value { + if s.typecode.Kind() != Slice || t.typecode.Kind() != Slice || s.typecode != t.typecode { + // Not a very helpful error message, but shortened to just one error to + // keep code size down. + panic("reflect.AppendSlice: invalid types") + } + if !s.isExported() || !t.isExported() { + // One of the sides was not exported, so can't access the data. + panic("reflect.AppendSlice: unexported") + } + sSlice := (*sliceHeader)(s.value) + tSlice := (*sliceHeader)(t.value) + elemSize := s.typecode.elem().Size() + ptr, len, cap := sliceAppend(sSlice.data, tSlice.data, sSlice.len, sSlice.cap, tSlice.len, elemSize) + result := &sliceHeader{ + data: ptr, + len: len, + cap: cap, + } + return Value{ + typecode: s.typecode, + value: unsafe.Pointer(result), + flags: valueFlagExported, + } +} + +// Grow increases the slice's capacity, if necessary, to guarantee space for +// another n elements. After Grow(n), at least n elements can be appended +// to the slice without another allocation. +// +// It panics if v's Kind is not a Slice or if n is negative or too large to +// allocate the memory. +func (v Value) Grow(n int) { + v.checkAddressable() + if n < 0 { + panic("reflect.Grow: negative length") + } + if v.Kind() != Slice { + panic(&ValueError{Method: "Grow", Kind: v.Kind()}) + } + slice := (*sliceHeader)(v.value) + newslice := extendSlice(v, n) + // Only copy the new data and cap: the len remains unchanged. + slice.data = newslice.data + slice.cap = newslice.cap +} + +//go:linkname hashmapStringSet runtime.hashmapStringSet +func hashmapStringSet(m unsafe.Pointer, key string, value unsafe.Pointer) + +//go:linkname hashmapBinarySet runtime.hashmapBinarySet +func hashmapBinarySet(m unsafe.Pointer, key, value unsafe.Pointer) + +//go:linkname hashmapInterfaceSet runtime.hashmapInterfaceSet +func hashmapInterfaceSet(m unsafe.Pointer, key interface{}, value unsafe.Pointer) + +//go:linkname hashmapStringDelete runtime.hashmapStringDelete +func hashmapStringDelete(m unsafe.Pointer, key string) + +//go:linkname hashmapBinaryDelete runtime.hashmapBinaryDelete +func hashmapBinaryDelete(m unsafe.Pointer, key unsafe.Pointer) + +//go:linkname hashmapInterfaceDelete runtime.hashmapInterfaceDelete +func hashmapInterfaceDelete(m unsafe.Pointer, key interface{}) + +func (v Value) SetMapIndex(key, elem Value) { + v.checkRO() + if v.Kind() != Map { + panic(&ValueError{Method: "SetMapIndex", Kind: v.Kind()}) + } + + vkey := v.typecode.key() + + // compare key type with actual key type of map + if !key.typecode.AssignableTo(vkey) { + panic("reflect.Value.SetMapIndex: incompatible types for key") + } + + // if elem is the zero Value, it means delete + del := elem == Value{} + + if !del && !elem.typecode.AssignableTo(v.typecode.elem()) { + panic("reflect.Value.SetMapIndex: incompatible types for value") + } + + // make elem an interface if it needs to be converted + if v.typecode.elem().Kind() == Interface && elem.typecode.Kind() != Interface { + intf := composeInterface(unsafe.Pointer(elem.typecode), elem.value) + elem = Value{ + typecode: v.typecode.elem(), + value: unsafe.Pointer(&intf), + } + } + + if key.Kind() == String { + if del { + hashmapStringDelete(v.pointer(), *(*string)(key.value)) + } else { + var elemptr unsafe.Pointer + if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) { + elemptr = elem.value + } else { + elemptr = unsafe.Pointer(&elem.value) + } + hashmapStringSet(v.pointer(), *(*string)(key.value), elemptr) + } + + } else if key.typecode.isBinary() { + var keyptr unsafe.Pointer + if key.isIndirect() || key.typecode.Size() > unsafe.Sizeof(uintptr(0)) { + keyptr = key.value + } else { + keyptr = unsafe.Pointer(&key.value) + } + + if del { + hashmapBinaryDelete(v.pointer(), keyptr) + } else { + var elemptr unsafe.Pointer + if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) { + elemptr = elem.value + } else { + elemptr = unsafe.Pointer(&elem.value) + } + hashmapBinarySet(v.pointer(), keyptr, elemptr) + } + } else { + if del { + hashmapInterfaceDelete(v.pointer(), key.Interface()) + } else { + var elemptr unsafe.Pointer + if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) { + elemptr = elem.value + } else { + elemptr = unsafe.Pointer(&elem.value) + } + + hashmapInterfaceSet(v.pointer(), key.Interface(), elemptr) + } + } +} + +// FieldByIndex returns the nested field corresponding to index. +func (v Value) FieldByIndex(index []int) Value { + if len(index) == 1 { + return v.Field(index[0]) + } + if v.Kind() != Struct { + panic(&ValueError{"FieldByIndex", v.Kind()}) + } + for i, x := range index { + if i > 0 { + if v.Kind() == Pointer && v.typecode.elem().Kind() == Struct { + if v.IsNil() { + panic("reflect: indirection through nil pointer to embedded struct") + } + v = v.Elem() + } + } + v = v.Field(x) + } + return v +} + +// FieldByIndexErr returns the nested field corresponding to index. +func (v Value) FieldByIndexErr(index []int) (Value, error) { + return Value{}, &ValueError{Method: "FieldByIndexErr"} +} + +func (v Value) FieldByName(name string) Value { + if v.Kind() != Struct { + panic(&ValueError{"FieldByName", v.Kind()}) + } + + if field, ok := v.typecode.FieldByName(name); ok { + return v.FieldByIndex(field.Index) + } + return Value{} +} + +func (v Value) FieldByNameFunc(match func(string) bool) Value { + if v.Kind() != Struct { + panic(&ValueError{"FieldByName", v.Kind()}) + } + + if field, ok := v.typecode.FieldByNameFunc(match); ok { + return v.FieldByIndex(field.Index) + } + return Value{} +} + +//go:linkname hashmapMake runtime.hashmapMake +func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) unsafe.Pointer + +// MakeMapWithSize creates a new map with the specified type and initial space +// for approximately n elements. +func MakeMapWithSize(typ Type, n int) Value { + + // TODO(dgryski): deduplicate these? runtime and reflect both need them. + const ( + hashmapAlgorithmBinary uint8 = iota + hashmapAlgorithmString + hashmapAlgorithmInterface + ) + + if typ.Kind() != Map { + panic(&ValueError{Method: "MakeMap", Kind: typ.Kind()}) + } + + if n < 0 { + panic("reflect.MakeMapWithSize: negative size hint") + } + + key := typ.Key().(*RawType) + val := typ.Elem().(*RawType) + + var alg uint8 + + if key.Kind() == String { + alg = hashmapAlgorithmString + } else if key.isBinary() { + alg = hashmapAlgorithmBinary + } else { + alg = hashmapAlgorithmInterface + } + + m := hashmapMake(key.Size(), val.Size(), uintptr(n), alg) + + return Value{ + typecode: typ.(*RawType), + value: m, + flags: valueFlagExported, + } +} + +// MakeMap creates a new map with the specified type. +func MakeMap(typ Type) Value { + return MakeMapWithSize(typ, 8) +} + +func (v Value) Call(in []Value) []Value { + panic("unimplemented: (reflect.Value).Call()") +} + +func (v Value) CallSlice(in []Value) []Value { + panic("unimplemented: (reflect.Value).CallSlice()") +} + +func (v Value) Method(i int) Value { + panic("unimplemented: (reflect.Value).Method()") +} + +func (v Value) MethodByName(name string) Value { + panic("unimplemented: (reflect.Value).MethodByName()") +} + +func (v Value) Recv() (x Value, ok bool) { + panic("unimplemented: (reflect.Value).Recv()") +} + +func NewAt(typ Type, p unsafe.Pointer) Value { + panic("unimplemented: reflect.New()") +} diff --git a/src/internal/reflectlite/visiblefields.go b/src/internal/reflectlite/visiblefields.go new file mode 100644 index 0000000000..b21af6178f --- /dev/null +++ b/src/internal/reflectlite/visiblefields.go @@ -0,0 +1,105 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package reflectlite + +// VisibleFields returns all the visible fields in t, which must be a +// struct type. A field is defined as visible if it's accessible +// directly with a FieldByName call. The returned fields include fields +// inside anonymous struct members and unexported fields. They follow +// the same order found in the struct, with anonymous fields followed +// immediately by their promoted fields. +// +// For each element e of the returned slice, the corresponding field +// can be retrieved from a value v of type t by calling v.FieldByIndex(e.Index). +func VisibleFields(t Type) []StructField { + if t == nil { + panic("reflect: VisibleFields(nil)") + } + if t.Kind() != Struct { + panic("reflect.VisibleFields of non-struct type") + } + w := &visibleFieldsWalker{ + byName: make(map[string]int), + visiting: make(map[Type]bool), + fields: make([]StructField, 0, t.NumField()), + index: make([]int, 0, 2), + } + w.walk(t) + // Remove all the fields that have been hidden. + // Use an in-place removal that avoids copying in + // the common case that there are no hidden fields. + j := 0 + for i := range w.fields { + f := &w.fields[i] + if f.Name == "" { + continue + } + if i != j { + // A field has been removed. We need to shuffle + // all the subsequent elements up. + w.fields[j] = *f + } + j++ + } + return w.fields[:j] +} + +type visibleFieldsWalker struct { + byName map[string]int + visiting map[Type]bool + fields []StructField + index []int +} + +// walk walks all the fields in the struct type t, visiting +// fields in index preorder and appending them to w.fields +// (this maintains the required ordering). +// Fields that have been overridden have their +// Name field cleared. +func (w *visibleFieldsWalker) walk(t Type) { + if w.visiting[t] { + return + } + w.visiting[t] = true + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + w.index = append(w.index, i) + add := true + if oldIndex, ok := w.byName[f.Name]; ok { + old := &w.fields[oldIndex] + if len(w.index) == len(old.Index) { + // Fields with the same name at the same depth + // cancel one another out. Set the field name + // to empty to signify that has happened, and + // there's no need to add this field. + old.Name = "" + add = false + } else if len(w.index) < len(old.Index) { + // The old field loses because it's deeper than the new one. + old.Name = "" + } else { + // The old field wins because it's shallower than the new one. + add = false + } + } + if add { + // Copy the index so that it's not overwritten + // by the other appends. + f.Index = append([]int(nil), w.index...) + w.byName[f.Name] = len(w.fields) + w.fields = append(w.fields, f) + } + if f.Anonymous { + if f.Type.Kind() == Pointer { + f.Type = f.Type.Elem() + } + if f.Type.Kind() == Struct { + w.walk(f.Type) + } + } + w.index = w.index[:len(w.index)-1] + } + delete(w.visiting, t) +} diff --git a/src/reflect/swapper.go b/src/reflect/swapper.go index a2fa44cef0..d49e33e04c 100644 --- a/src/reflect/swapper.go +++ b/src/reflect/swapper.go @@ -1,40 +1,7 @@ package reflect -import "unsafe" - -// Some of code here has been copied from the Go sources: -// https://github.com/golang/go/blob/go1.15.2/src/reflect/swapper.go -// It has the following copyright note: -// -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +import "internal/reflectlite" func Swapper(slice interface{}) func(i, j int) { - v := ValueOf(slice) - if v.Kind() != Slice { - panic(&ValueError{Method: "Swapper"}) - } - - // Just return Nop func if nothing to swap. - if v.Len() < 2 { - return func(i, j int) {} - } - - typ := v.typecode.Elem() - size := typ.Size() - - header := (*sliceHeader)(v.value) - tmp := unsafe.Pointer(&make([]byte, size)[0]) - - return func(i, j int) { - if uint(i) >= uint(header.len) || uint(j) >= uint(header.len) { - panic("reflect: slice index out of range") - } - val1 := unsafe.Add(header.data, uintptr(i)*size) - val2 := unsafe.Add(header.data, uintptr(j)*size) - memcpy(tmp, val1, size) - memcpy(val1, val2, size) - memcpy(val2, tmp, size) - } + return reflectlite.Swapper(slice) } diff --git a/src/reflect/type.go b/src/reflect/type.go index c81d6ba554..e5417f8e8f 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -64,130 +64,50 @@ package reflect import ( - "internal/gclayout" - "internal/itoa" + "internal/reflectlite" "unsafe" ) -// Flags stored in the first byte of the struct field byte array. Must be kept -// up to date with compiler/interface.go. -const ( - structFieldFlagAnonymous = 1 << iota - structFieldFlagHasTag - structFieldFlagIsExported - structFieldFlagIsEmbedded -) - -type Kind uint8 +type Kind = reflectlite.Kind -// Copied from reflect/type.go -// https://golang.org/src/reflect/type.go?s=8302:8316#L217 -// These constants must match basicTypes and the typeKind* constants in -// compiler/interface.go const ( - Invalid Kind = iota - Bool - Int - Int8 - Int16 - Int32 - Int64 - Uint - Uint8 - Uint16 - Uint32 - Uint64 - Uintptr - Float32 - Float64 - Complex64 - Complex128 - String - UnsafePointer - Chan - Interface - Pointer - Slice - Array - Func - Map - Struct + Invalid Kind = reflectlite.Invalid + Bool Kind = reflectlite.Bool + Int Kind = reflectlite.Int + Int8 Kind = reflectlite.Int8 + Int16 Kind = reflectlite.Int16 + Int32 Kind = reflectlite.Int32 + Int64 Kind = reflectlite.Int64 + Uint Kind = reflectlite.Uint + Uint8 Kind = reflectlite.Uint8 + Uint16 Kind = reflectlite.Uint16 + Uint32 Kind = reflectlite.Uint32 + Uint64 Kind = reflectlite.Uint64 + Uintptr Kind = reflectlite.Uintptr + Float32 Kind = reflectlite.Float32 + Float64 Kind = reflectlite.Float64 + Complex64 Kind = reflectlite.Complex64 + Complex128 Kind = reflectlite.Complex128 + Array Kind = reflectlite.Array + Chan Kind = reflectlite.Chan + Func Kind = reflectlite.Func + Interface Kind = reflectlite.Interface + Map Kind = reflectlite.Map + Pointer Kind = reflectlite.Pointer + Slice Kind = reflectlite.Slice + String Kind = reflectlite.String + Struct Kind = reflectlite.Struct + UnsafePointer Kind = reflectlite.UnsafePointer ) -// Ptr is the old name for the Pointer kind. -const Ptr = Pointer - -func (k Kind) String() string { - switch k { - case Invalid: - return "invalid" - case Bool: - return "bool" - case Int: - return "int" - case Int8: - return "int8" - case Int16: - return "int16" - case Int32: - return "int32" - case Int64: - return "int64" - case Uint: - return "uint" - case Uint8: - return "uint8" - case Uint16: - return "uint16" - case Uint32: - return "uint32" - case Uint64: - return "uint64" - case Uintptr: - return "uintptr" - case Float32: - return "float32" - case Float64: - return "float64" - case Complex64: - return "complex64" - case Complex128: - return "complex128" - case String: - return "string" - case UnsafePointer: - return "unsafe.Pointer" - case Chan: - return "chan" - case Interface: - return "interface" - case Pointer: - return "ptr" - case Slice: - return "slice" - case Array: - return "array" - case Func: - return "func" - case Map: - return "map" - case Struct: - return "struct" - default: - return "kind" + itoa.Itoa(int(int8(k))) - } -} - -// Copied from reflect/type.go -// https://go.dev/src/reflect/type.go?#L348 +const Ptr = reflectlite.Ptr -// ChanDir represents a channel type's direction. -type ChanDir int +type ChanDir = reflectlite.ChanDir const ( - RecvDir ChanDir = 1 << iota // <-chan - SendDir // chan<- - BothDir = RecvDir | SendDir // chan + RecvDir = reflectlite.RecvDir + SendDir = reflectlite.SendDir + BothDir = reflectlite.BothDir ) // Method represents a single method. @@ -411,1015 +331,80 @@ type Type interface { OverflowUint(x uint64) bool } -// Constants for the 'meta' byte. -const ( - kindMask = 31 // mask to apply to the meta byte to get the Kind value - flagNamed = 32 // flag that is set if this is a named type - flagComparable = 64 // flag that is set if this type is comparable - flagIsBinary = 128 // flag that is set if this type uses the hashmap binary algorithm -) - -// The base type struct. All type structs start with this. type rawType struct { - meta uint8 // metadata byte, contains kind and flags (see constants above) -} - -// All types that have an element type: named, chan, slice, array, map (but not -// pointer because it doesn't have ptrTo). -type elemType struct { - rawType - numMethod uint16 - ptrTo *rawType - elem *rawType -} - -type ptrType struct { - rawType - numMethod uint16 - elem *rawType -} - -type interfaceType struct { - rawType - ptrTo *rawType - // TODO: methods -} - -type arrayType struct { - rawType - numMethod uint16 - ptrTo *rawType - elem *rawType - arrayLen uintptr - slicePtr *rawType -} - -type mapType struct { - rawType - numMethod uint16 - ptrTo *rawType - elem *rawType - key *rawType -} - -type namedType struct { - rawType - numMethod uint16 - ptrTo *rawType - elem *rawType - pkg *byte - name [1]byte + reflectlite.RawType } -// Type for struct types. The numField value is intentionally put before ptrTo -// for better struct packing on 32-bit and 64-bit architectures. On these -// architectures, the ptrTo field still has the same offset as in all the other -// type structs. -// The fields array isn't necessarily 1 structField long, instead it is as long -// as numFields. The array is given a length of 1 to satisfy the Go type -// checker. -type structType struct { - rawType - numMethod uint16 - ptrTo *rawType - pkgpath *byte - size uint32 - numField uint16 - fields [1]structField // the remaining fields are all of type structField +func toType(t reflectlite.Type) Type { + return (*rawType)(unsafe.Pointer(t.(*reflectlite.RawType))) } -type structField struct { - fieldType *rawType - data unsafe.Pointer // various bits of information, packed in a byte array -} - -// Equivalent to (go/types.Type).Underlying(): if this is a named type return -// the underlying type, else just return the type itself. -func (t *rawType) underlying() *rawType { - if t.isNamed() { - return (*elemType)(unsafe.Pointer(t)).elem - } - return t -} - -func (t *rawType) ptrtag() uintptr { - return uintptr(unsafe.Pointer(t)) & 0b11 -} - -func (t *rawType) isNamed() bool { - if tag := t.ptrtag(); tag != 0 { - return false - } - - return t.meta&flagNamed != 0 +func toRawType(t Type) *reflectlite.RawType { + return (*reflectlite.RawType)(unsafe.Pointer(t.(*rawType))) } func TypeOf(i interface{}) Type { - if i == nil { - return nil - } - typecode, _ := decomposeInterface(i) - return (*rawType)(typecode) -} - -func PtrTo(t Type) Type { return PointerTo(t) } - -func PointerTo(t Type) Type { - return pointerTo(t.(*rawType)) -} - -func pointerTo(t *rawType) *rawType { - if t.isNamed() { - return (*elemType)(unsafe.Pointer(t)).ptrTo - } - - switch t.Kind() { - case Pointer: - if tag := t.ptrtag(); tag < 3 { - return (*rawType)(unsafe.Add(unsafe.Pointer(t), 1)) - } - - // TODO(dgryski): This is blocking https://github.com/tinygo-org/tinygo/issues/3131 - // We need to be able to create types that match existing types to prevent typecode equality. - panic("reflect: cannot make *****T type") - case Struct: - return (*structType)(unsafe.Pointer(t)).ptrTo - default: - return (*elemType)(unsafe.Pointer(t)).ptrTo - } -} - -func (t *rawType) String() string { - if t.isNamed() { - s := t.name() - if s[0] == '.' { - return s[1:] - } - return s - } - - switch t.Kind() { - case Chan: - elem := t.elem().String() - switch t.ChanDir() { - case SendDir: - return "chan<- " + elem - case RecvDir: - return "<-chan " + elem - case BothDir: - if elem[0] == '<' { - // typ is recv chan, need parentheses as "<-" associates with leftmost - // chan possible, see: - // * https://golang.org/ref/spec#Channel_types - // * https://github.com/golang/go/issues/39897 - return "chan (" + elem + ")" - } - return "chan " + elem - } - - case Pointer: - return "*" + t.elem().String() - case Slice: - return "[]" + t.elem().String() - case Array: - return "[" + itoa.Itoa(t.Len()) + "]" + t.elem().String() - case Map: - return "map[" + t.key().String() + "]" + t.elem().String() - case Struct: - numField := t.NumField() - if numField == 0 { - return "struct {}" - } - s := "struct {" - for i := 0; i < numField; i++ { - f := t.rawField(i) - s += " " + f.Name + " " + f.Type.String() - if f.Tag != "" { - s += " " + quote(string(f.Tag)) - } - // every field except the last needs a semicolon - if i < numField-1 { - s += ";" - } - } - s += " }" - return s - case Interface: - // TODO(dgryski): Needs actual method set info - return "interface {}" - default: - return t.Kind().String() - } - - return t.Kind().String() -} - -func (t *rawType) Kind() Kind { - if t == nil { - return Invalid - } - - if tag := t.ptrtag(); tag != 0 { - return Pointer - } - - return Kind(t.meta & kindMask) -} - -var ( - errTypeElem = &TypeError{"Elem"} - errTypeKey = &TypeError{"Key"} - errTypeField = &TypeError{"Field"} - errTypeBits = &TypeError{"Bits"} - errTypeLen = &TypeError{"Len"} - errTypeNumField = &TypeError{"NumField"} - errTypeChanDir = &TypeError{"ChanDir"} - errTypeFieldByName = &TypeError{"FieldByName"} - errTypeFieldByIndex = &TypeError{"FieldByIndex"} -) - -// Elem returns the element type for channel, slice and array types, the -// pointed-to value for pointer types, and the key type for map types. -func (t *rawType) Elem() Type { - return t.elem() -} - -func (t *rawType) elem() *rawType { - if tag := t.ptrtag(); tag != 0 { - return (*rawType)(unsafe.Add(unsafe.Pointer(t), -1)) - } - - underlying := t.underlying() - switch underlying.Kind() { - case Pointer: - return (*ptrType)(unsafe.Pointer(underlying)).elem - case Chan, Slice, Array, Map: - return (*elemType)(unsafe.Pointer(underlying)).elem - default: - panic(errTypeElem) - } -} - -func (t *rawType) key() *rawType { - underlying := t.underlying() - if underlying.Kind() != Map { - panic(errTypeKey) - } - return (*mapType)(unsafe.Pointer(underlying)).key -} - -// Field returns the type of the i'th field of this struct type. It panics if t -// is not a struct type. -func (t *rawType) Field(i int) StructField { - field := t.rawField(i) - return StructField{ - Name: field.Name, - PkgPath: field.PkgPath, - Type: field.Type, // note: converts rawType to Type - Tag: field.Tag, - Anonymous: field.Anonymous, - Offset: field.Offset, - Index: []int{i}, - } -} - -func rawStructFieldFromPointer(descriptor *structType, fieldType *rawType, data unsafe.Pointer, flagsByte uint8, name string, offset uint32) rawStructField { - // Read the field tag, if there is one. - var tag string - if flagsByte&structFieldFlagHasTag != 0 { - data = unsafe.Add(data, 1) // C: data+1 - tagLen := uintptr(*(*byte)(data)) - data = unsafe.Add(data, 1) // C: data+1 - tag = *(*string)(unsafe.Pointer(&stringHeader{ - data: data, - len: tagLen, - })) - } - - // Set the PkgPath to some (arbitrary) value if the package path is not - // exported. - pkgPath := "" - if flagsByte&structFieldFlagIsExported == 0 { - // This field is unexported. - pkgPath = readStringZ(unsafe.Pointer(descriptor.pkgpath)) - } - - return rawStructField{ - Name: name, - PkgPath: pkgPath, - Type: fieldType, - Tag: StructTag(tag), - Anonymous: flagsByte&structFieldFlagAnonymous != 0, - Offset: uintptr(offset), - } -} - -// rawField returns nearly the same value as Field but without converting the -// Type member to an interface. -// -// For internal use only. -func (t *rawType) rawField(n int) rawStructField { - if t.Kind() != Struct { - panic(errTypeField) - } - descriptor := (*structType)(unsafe.Pointer(t.underlying())) - if uint(n) >= uint(descriptor.numField) { - panic("reflect: field index out of range") - } - - // Iterate over all the fields to calculate the offset. - // This offset could have been stored directly in the array (to make the - // lookup faster), but by calculating it on-the-fly a bit of storage can be - // saved. - field := (*structField)(unsafe.Add(unsafe.Pointer(&descriptor.fields[0]), uintptr(n)*unsafe.Sizeof(structField{}))) - data := field.data - - // Read some flags of this field, like whether the field is an embedded - // field. See structFieldFlagAnonymous and similar flags. - flagsByte := *(*byte)(data) - data = unsafe.Add(data, 1) - offset, lenOffs := uvarint32(unsafe.Slice((*byte)(data), maxVarintLen32)) - data = unsafe.Add(data, lenOffs) - - name := readStringZ(data) - data = unsafe.Add(data, len(name)) - - return rawStructFieldFromPointer(descriptor, field.fieldType, data, flagsByte, name, offset) -} - -// rawFieldByNameFunc returns nearly the same value as FieldByNameFunc but without converting the -// Type member to an interface. -// -// For internal use only. -func (t *rawType) rawFieldByNameFunc(match func(string) bool) (rawStructField, []int, bool) { - if t.Kind() != Struct { - panic(errTypeField) - } - - type fieldWalker struct { - t *rawType - index []int - } - - queue := make([]fieldWalker, 0, 4) - queue = append(queue, fieldWalker{t, nil}) - - for len(queue) > 0 { - type result struct { - r rawStructField - index []int - } - - var found []result - var nextlevel []fieldWalker - - // For all the structs at this level.. - for _, ll := range queue { - // Iterate over all the fields looking for the matching name - // Also calculate field offset. - - descriptor := (*structType)(unsafe.Pointer(ll.t.underlying())) - field := &descriptor.fields[0] - - for i := uint16(0); i < descriptor.numField; i++ { - data := field.data - - // Read some flags of this field, like whether the field is an embedded - // field. See structFieldFlagAnonymous and similar flags. - flagsByte := *(*byte)(data) - data = unsafe.Add(data, 1) - - offset, lenOffs := uvarint32(unsafe.Slice((*byte)(data), maxVarintLen32)) - data = unsafe.Add(data, lenOffs) - - name := readStringZ(data) - data = unsafe.Add(data, len(name)) - if match(name) { - found = append(found, result{ - rawStructFieldFromPointer(descriptor, field.fieldType, data, flagsByte, name, offset), - append(ll.index[:len(ll.index):len(ll.index)], int(i)), - }) - } - - structOrPtrToStruct := field.fieldType.Kind() == Struct || (field.fieldType.Kind() == Pointer && field.fieldType.elem().Kind() == Struct) - if flagsByte&structFieldFlagIsEmbedded == structFieldFlagIsEmbedded && structOrPtrToStruct { - embedded := field.fieldType - if embedded.Kind() == Pointer { - embedded = embedded.elem() - } - - nextlevel = append(nextlevel, fieldWalker{ - t: embedded, - index: append(ll.index[:len(ll.index):len(ll.index)], int(i)), - }) - } - - // update offset/field pointer if there *is* a next field - if i < descriptor.numField-1 { - // Increment pointer to the next field. - field = (*structField)(unsafe.Add(unsafe.Pointer(field), unsafe.Sizeof(structField{}))) - } - } - } - - // found multiple hits at this level - if len(found) > 1 { - return rawStructField{}, nil, false - } - - // found the field we were looking for - if len(found) == 1 { - r := found[0] - return r.r, r.index, true - } - - // else len(found) == 0, move on to the next level - queue = append(queue[:0], nextlevel...) - } - - // didn't find it - return rawStructField{}, nil, false -} - -// Bits returns the number of bits that this type uses. It is only valid for -// arithmetic types (integers, floats, and complex numbers). For other types, it -// will panic. -func (t *rawType) Bits() int { - kind := t.Kind() - if kind >= Int && kind <= Complex128 { - return int(t.Size()) * 8 - } - panic(errTypeBits) + return toType(reflectlite.TypeOf(i)) } -// Len returns the number of elements in this array. It panics of the type kind -// is not Array. -func (t *rawType) Len() int { - if t.Kind() != Array { - panic(errTypeLen) - } - - return int((*arrayType)(unsafe.Pointer(t.underlying())).arrayLen) -} - -// NumField returns the number of fields of a struct type. It panics for other -// type kinds. -func (t *rawType) NumField() int { - if t.Kind() != Struct { - panic(errTypeNumField) - } - return int((*structType)(unsafe.Pointer(t.underlying())).numField) -} - -// Size returns the size in bytes of a given type. It is similar to -// unsafe.Sizeof. -func (t *rawType) Size() uintptr { - switch t.Kind() { - case Bool, Int8, Uint8: - return 1 - case Int16, Uint16: - return 2 - case Int32, Uint32: - return 4 - case Int64, Uint64: - return 8 - case Int, Uint: - return unsafe.Sizeof(int(0)) - case Uintptr: - return unsafe.Sizeof(uintptr(0)) - case Float32: - return 4 - case Float64: - return 8 - case Complex64: - return 8 - case Complex128: - return 16 - case String: - return unsafe.Sizeof("") - case UnsafePointer, Chan, Map, Pointer: - return unsafe.Sizeof(uintptr(0)) - case Slice: - return unsafe.Sizeof([]int{}) - case Interface: - return unsafe.Sizeof(interface{}(nil)) - case Func: - var f func() - return unsafe.Sizeof(f) - case Array: - return t.elem().Size() * uintptr(t.Len()) - case Struct: - u := t.underlying() - return uintptr((*structType)(unsafe.Pointer(u)).size) - default: - panic("unimplemented: size of type") - } -} - -// Align returns the alignment of this type. It is similar to calling -// unsafe.Alignof. -func (t *rawType) Align() int { - switch t.Kind() { - case Bool, Int8, Uint8: - return int(unsafe.Alignof(int8(0))) - case Int16, Uint16: - return int(unsafe.Alignof(int16(0))) - case Int32, Uint32: - return int(unsafe.Alignof(int32(0))) - case Int64, Uint64: - return int(unsafe.Alignof(int64(0))) - case Int, Uint: - return int(unsafe.Alignof(int(0))) - case Uintptr: - return int(unsafe.Alignof(uintptr(0))) - case Float32: - return int(unsafe.Alignof(float32(0))) - case Float64: - return int(unsafe.Alignof(float64(0))) - case Complex64: - return int(unsafe.Alignof(complex64(0))) - case Complex128: - return int(unsafe.Alignof(complex128(0))) - case String: - return int(unsafe.Alignof("")) - case UnsafePointer, Chan, Map, Pointer: - return int(unsafe.Alignof(uintptr(0))) - case Slice: - return int(unsafe.Alignof([]int(nil))) - case Interface: - return int(unsafe.Alignof(interface{}(nil))) - case Func: - var f func() - return int(unsafe.Alignof(f)) - case Struct: - numField := t.NumField() - alignment := 1 - for i := 0; i < numField; i++ { - fieldAlignment := t.rawField(i).Type.Align() - if fieldAlignment > alignment { - alignment = fieldAlignment - } - } - return alignment - case Array: - return t.elem().Align() - default: - panic("unimplemented: alignment of type") - } -} - -func (r *rawType) gcLayout() unsafe.Pointer { - kind := r.Kind() - - if kind < String { - return gclayout.NoPtrs - } - - switch kind { - case Pointer, UnsafePointer, Chan, Map: - return gclayout.Pointer - case String: - return gclayout.String - case Slice: - return gclayout.Slice - } - - // Unknown (for now); let the conservative pointer scanning handle it - return nil +func PtrTo(t Type) Type { + return PointerTo(t) } -// FieldAlign returns the alignment if this type is used in a struct field. It -// is currently an alias for Align() but this might change in the future. -func (t *rawType) FieldAlign() int { - return t.Align() +func PointerTo(t Type) Type { + return toType(reflectlite.PointerTo(toRawType(t))) } -// AssignableTo returns whether a value of type t can be assigned to a variable -// of type u. func (t *rawType) AssignableTo(u Type) bool { - if t == u.(*rawType) { - return true - } - - if t.underlying() == u.(*rawType).underlying() && (!t.isNamed() || !u.(*rawType).isNamed()) { - return true - } - - if u.Kind() == Interface && u.NumMethod() == 0 { - return true - } - - if u.Kind() == Interface { - panic("reflect: unimplemented: AssignableTo with interface") - } - return false -} - -func (t *rawType) Implements(u Type) bool { - if u.Kind() != Interface { - panic("reflect: non-interface type passed to Type.Implements") - } - return t.AssignableTo(u) -} - -// Comparable returns whether values of this type can be compared to each other. -func (t *rawType) Comparable() bool { - return (t.meta & flagComparable) == flagComparable -} - -// isBinary returns if the hashmapAlgorithmBinary functions can be used on this type -func (t *rawType) isBinary() bool { - return (t.meta & flagIsBinary) == flagIsBinary -} - -func (t *rawType) ChanDir() ChanDir { - if t.Kind() != Chan { - panic(errTypeChanDir) - } - - dir := int((*elemType)(unsafe.Pointer(t)).numMethod) - - // nummethod is overloaded for channel to store channel direction - return ChanDir(dir) + return t.RawType.AssignableTo(&(u.(*rawType).RawType)) } func (t *rawType) ConvertibleTo(u Type) bool { panic("unimplemented: (reflect.Type).ConvertibleTo()") } -func (t *rawType) IsVariadic() bool { - panic("unimplemented: (reflect.Type).IsVariadic()") -} - -func (t *rawType) NumIn() int { - panic("unimplemented: (reflect.Type).NumIn()") -} - -func (t *rawType) NumOut() int { - panic("unimplemented: (reflect.Type).NumOut()") -} - -func (t *rawType) NumMethod() int { - - if t.isNamed() { - return int((*namedType)(unsafe.Pointer(t)).numMethod) - } - - switch t.Kind() { - case Pointer: - return int((*ptrType)(unsafe.Pointer(t)).numMethod) - case Struct: - return int((*structType)(unsafe.Pointer(t)).numMethod) - case Interface: - //FIXME: Use len(methods) - return (*interfaceType)(unsafe.Pointer(t)).ptrTo.NumMethod() - } - - // Other types have no methods attached. Note we don't panic here. - return 0 -} - -// Read and return a null terminated string starting from data. -func readStringZ(data unsafe.Pointer) string { - start := data - var len uintptr - for *(*byte)(data) != 0 { - len++ - data = unsafe.Add(data, 1) // C: data++ - } - - return *(*string)(unsafe.Pointer(&stringHeader{ - data: start, - len: len, - })) -} - -func (t *rawType) name() string { - ntype := (*namedType)(unsafe.Pointer(t)) - return readStringZ(unsafe.Pointer(&ntype.name[0])) -} - -func (t *rawType) Name() string { - if t.isNamed() { - name := t.name() - for i := 0; i < len(name); i++ { - if name[i] == '.' { - return name[i+1:] - } - } - panic("corrupt name data") - } - - if kind := t.Kind(); kind < UnsafePointer { - return t.Kind().String() - } else if kind == UnsafePointer { - return "Pointer" - } - - return "" -} - -func (t *rawType) Key() Type { - return t.key() +func (t *rawType) Implements(u Type) bool { + return t.RawType.Implements(&(u.(*rawType).RawType)) } -func (t rawType) In(i int) Type { +func (t *rawType) In(i int) Type { panic("unimplemented: (reflect.Type).In()") } -func (t rawType) Out(i int) Type { - panic("unimplemented: (reflect.Type).Out()") -} - -// OverflowComplex reports whether the complex128 x cannot be represented by type t. -// It panics if t's Kind is not Complex64 or Complex128. -func (t rawType) OverflowComplex(x complex128) bool { - k := t.Kind() - switch k { - case Complex64: - return overflowFloat32(real(x)) || overflowFloat32(imag(x)) - case Complex128: - return false - } - panic("reflect: OverflowComplex of non-complex type") -} - -// OverflowFloat reports whether the float64 x cannot be represented by type t. -// It panics if t's Kind is not Float32 or Float64. -func (t rawType) OverflowFloat(x float64) bool { - k := t.Kind() - switch k { - case Float32: - return overflowFloat32(x) - case Float64: - return false - } - panic("reflect: OverflowFloat of non-float type") -} - -// OverflowInt reports whether the int64 x cannot be represented by type t. -// It panics if t's Kind is not Int, Int8, Int16, Int32, or Int64. -func (t rawType) OverflowInt(x int64) bool { - k := t.Kind() - switch k { - case Int, Int8, Int16, Int32, Int64: - bitSize := t.Size() * 8 - trunc := (x << (64 - bitSize)) >> (64 - bitSize) - return x != trunc - } - panic("reflect: OverflowInt of non-int type") +func (t *rawType) IsVariadic() bool { + panic("unimplemented: (reflect.Type).IsVariadic()") } -// OverflowUint reports whether the uint64 x cannot be represented by type t. -// It panics if t's Kind is not Uint, Uintptr, Uint8, Uint16, Uint32, or Uint64. -func (t rawType) OverflowUint(x uint64) bool { - k := t.Kind() - switch k { - case Uint, Uintptr, Uint8, Uint16, Uint32, Uint64: - bitSize := t.Size() * 8 - trunc := (x << (64 - bitSize)) >> (64 - bitSize) - return x != trunc - } - panic("reflect: OverflowUint of non-uint type") +func (t *rawType) Key() Type { + return toType(t.RawType.Key()) } -func (t rawType) Method(i int) Method { +func (t *rawType) Method(i int) Method { panic("unimplemented: (reflect.Type).Method()") } -func (t rawType) MethodByName(name string) (Method, bool) { +func (t *rawType) MethodByName(name string) (Method, bool) { panic("unimplemented: (reflect.Type).MethodByName()") } -func (t *rawType) PkgPath() string { - if t.isNamed() { - ntype := (*namedType)(unsafe.Pointer(t)) - return readStringZ(unsafe.Pointer(ntype.pkg)) - } - - return "" -} - -func (t *rawType) FieldByName(name string) (StructField, bool) { - if t.Kind() != Struct { - panic(errTypeFieldByName) - } - - field, index, ok := t.rawFieldByNameFunc(func(n string) bool { return n == name }) - if !ok { - return StructField{}, false - } - - return StructField{ - Name: field.Name, - PkgPath: field.PkgPath, - Type: field.Type, // note: converts rawType to Type - Tag: field.Tag, - Anonymous: field.Anonymous, - Offset: field.Offset, - Index: index, - }, true -} - -func (t *rawType) FieldByNameFunc(match func(string) bool) (StructField, bool) { - if t.Kind() != Struct { - panic(TypeError{"FieldByNameFunc"}) - } - - field, index, ok := t.rawFieldByNameFunc(match) - if !ok { - return StructField{}, false - } - - return StructField{ - Name: field.Name, - PkgPath: field.PkgPath, - Type: field.Type, // note: converts rawType to Type - Tag: field.Tag, - Anonymous: field.Anonymous, - Offset: field.Offset, - Index: index, - }, true -} - -func (t *rawType) FieldByIndex(index []int) StructField { - ftype := t - var field rawStructField - - for _, n := range index { - structOrPtrToStruct := ftype.Kind() == Struct || (ftype.Kind() == Pointer && ftype.elem().Kind() == Struct) - if !structOrPtrToStruct { - panic(errTypeFieldByIndex) - } - - if ftype.Kind() == Pointer { - ftype = ftype.elem() - } - - field = ftype.rawField(n) - ftype = field.Type - } - - return StructField{ - Name: field.Name, - PkgPath: field.PkgPath, - Type: field.Type, // note: converts rawType to Type - Tag: field.Tag, - Anonymous: field.Anonymous, - Offset: field.Offset, - Index: index, - } -} - -// A StructField describes a single field in a struct. -type StructField struct { - // Name indicates the field name. - Name string - - // PkgPath is the package path where the struct containing this field is - // declared for unexported fields, or the empty string for exported fields. - PkgPath string - - Type Type - Tag StructTag // field tag string - Offset uintptr - Index []int // index sequence for Type.FieldByIndex - Anonymous bool -} - -// IsExported reports whether the field is exported. -func (f StructField) IsExported() bool { - return f.PkgPath == "" -} - -// rawStructField is the same as StructField but with the Type member replaced -// with rawType. For internal use only. Avoiding this conversion to the Type -// interface improves code size in many cases. -type rawStructField struct { - Name string - PkgPath string - Type *rawType - Tag StructTag - Offset uintptr - Anonymous bool -} - -// A StructTag is the tag string in a struct field. -type StructTag string - -// TODO: it would be feasible to do the key/value splitting at compile time, -// avoiding the code size cost of doing it at runtime - -// Get returns the value associated with key in the tag string. -func (tag StructTag) Get(key string) string { - v, _ := tag.Lookup(key) - return v -} - -// Lookup returns the value associated with key in the tag string. -func (tag StructTag) Lookup(key string) (value string, ok bool) { - for tag != "" { - // Skip leading space. - i := 0 - for i < len(tag) && tag[i] == ' ' { - i++ - } - tag = tag[i:] - if tag == "" { - break - } - - // Scan to colon. A space, a quote or a control character is a syntax error. - // Strictly speaking, control chars include the range [0x7f, 0x9f], not just - // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters - // as it is simpler to inspect the tag's bytes than the tag's runes. - i = 0 - for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { - i++ - } - if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { - break - } - name := string(tag[:i]) - tag = tag[i+1:] - - // Scan quoted string to find value. - i = 1 - for i < len(tag) && tag[i] != '"' { - if tag[i] == '\\' { - i++ - } - i++ - } - if i >= len(tag) { - break - } - qvalue := string(tag[:i+1]) - tag = tag[i+1:] - - if key == name { - value, err := unquote(qvalue) - if err != nil { - break - } - return value, true - } - } - return "", false -} - -// TypeError is the error that is used in a panic when invoking a method on a -// type that is not applicable to that type. -type TypeError struct { - Method string -} - -func (e *TypeError) Error() string { - return "reflect: call of reflect.Type." + e.Method + " on invalid type" -} - -func align(offset uintptr, alignment uintptr) uintptr { - return (offset + alignment - 1) &^ (alignment - 1) -} - -func SliceOf(t Type) Type { - panic("unimplemented: reflect.SliceOf()") -} - -func ArrayOf(n int, t Type) Type { - panic("unimplemented: reflect.ArrayOf()") +func (t *rawType) NumIn() int { + panic("unimplemented: (reflect.Type).NumIn()") } -func StructOf([]StructField) Type { - panic("unimplemented: reflect.StructOf()") +func (t *rawType) NumOut() int { + panic("unimplemented: (reflect.Type).NumOut()") } -func MapOf(key, value Type) Type { - panic("unimplemented: reflect.MapOf()") +func (t *rawType) Out(i int) Type { + panic("unimplemented: (reflect.Type).Out()") } -func FuncOf(in, out []Type, variadic bool) Type { - panic("unimplemented: reflect.FuncOf()") +func (t *rawType) Elem() Type { + return toType(t.RawType.Elem()) } -const maxVarintLen32 = 5 - -// encoding/binary.Uvarint, specialized for uint32 -func uvarint32(buf []byte) (uint32, int) { - var x uint32 - var s uint - for i, b := range buf { - if b < 0x80 { - return x | uint32(b)< unsafe.Sizeof(uintptr(0)) { - return int64(*(*int)(v.value)) - } else { - return int64(int(uintptr(v.value))) - } - case Int8: - if v.isIndirect() { - return int64(*(*int8)(v.value)) - } else { - return int64(int8(uintptr(v.value))) - } - case Int16: - if v.isIndirect() { - return int64(*(*int16)(v.value)) - } else { - return int64(int16(uintptr(v.value))) - } - case Int32: - if v.isIndirect() || unsafe.Sizeof(int32(0)) > unsafe.Sizeof(uintptr(0)) { - return int64(*(*int32)(v.value)) - } else { - return int64(int32(uintptr(v.value))) - } - case Int64: - if v.isIndirect() || unsafe.Sizeof(int64(0)) > unsafe.Sizeof(uintptr(0)) { - return int64(*(*int64)(v.value)) - } else { - return int64(int64(uintptr(v.value))) - } - default: - panic(&ValueError{Method: "Int", Kind: v.Kind()}) - } -} - -// CanUint reports whether Uint can be used without panicking. -func (v Value) CanUint() bool { - switch v.Kind() { - case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: - return true - default: - return false - } -} - -func (v Value) Uint() uint64 { - switch v.Kind() { - case Uintptr: - if v.isIndirect() { - return uint64(*(*uintptr)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - case Uint8: - if v.isIndirect() { - return uint64(*(*uint8)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - case Uint16: - if v.isIndirect() { - return uint64(*(*uint16)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - case Uint: - if v.isIndirect() || unsafe.Sizeof(uint(0)) > unsafe.Sizeof(uintptr(0)) { - return uint64(*(*uint)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - case Uint32: - if v.isIndirect() || unsafe.Sizeof(uint32(0)) > unsafe.Sizeof(uintptr(0)) { - return uint64(*(*uint32)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - case Uint64: - if v.isIndirect() || unsafe.Sizeof(uint64(0)) > unsafe.Sizeof(uintptr(0)) { - return uint64(*(*uint64)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - default: - panic(&ValueError{Method: "Uint", Kind: v.Kind()}) - } -} - -// CanFloat reports whether Float can be used without panicking. -func (v Value) CanFloat() bool { - switch v.Kind() { - case Float32, Float64: - return true - default: - return false - } -} - -func (v Value) Float32() float32 { - switch v.Kind() { - case Float32: - if v.isIndirect() || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) { - // The float is stored as an external value on systems with 16-bit - // pointers. - return *(*float32)(v.value) - } else { - // The float is directly stored in the interface value on systems - // with 32-bit and 64-bit pointers. - return *(*float32)(unsafe.Pointer(&v.value)) - } - - case Float64: - return float32(v.Float()) - - } - - panic(&ValueError{Method: "Float", Kind: v.Kind()}) -} - -func (v Value) Float() float64 { - switch v.Kind() { - case Float32: - if v.isIndirect() || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) { - // The float is stored as an external value on systems with 16-bit - // pointers. - return float64(*(*float32)(v.value)) - } else { - // The float is directly stored in the interface value on systems - // with 32-bit and 64-bit pointers. - return float64(*(*float32)(unsafe.Pointer(&v.value))) - } - case Float64: - if v.isIndirect() || unsafe.Sizeof(float64(0)) > unsafe.Sizeof(uintptr(0)) { - // For systems with 16-bit and 32-bit pointers. - return *(*float64)(v.value) - } else { - // The float is directly stored in the interface value on systems - // with 64-bit pointers. - return *(*float64)(unsafe.Pointer(&v.value)) - } - default: - panic(&ValueError{Method: "Float", Kind: v.Kind()}) - } -} - -// CanComplex reports whether Complex can be used without panicking. -func (v Value) CanComplex() bool { - switch v.Kind() { - case Complex64, Complex128: - return true - default: - return false - } -} - -func (v Value) Complex() complex128 { - switch v.Kind() { - case Complex64: - if v.isIndirect() || unsafe.Sizeof(complex64(0)) > unsafe.Sizeof(uintptr(0)) { - // The complex number is stored as an external value on systems with - // 16-bit and 32-bit pointers. - return complex128(*(*complex64)(v.value)) - } else { - // The complex number is directly stored in the interface value on - // systems with 64-bit pointers. - return complex128(*(*complex64)(unsafe.Pointer(&v.value))) - } - case Complex128: - // This is a 128-bit value, which is always stored as an external value. - // It may be stored in the pointer directly on very uncommon - // architectures with 128-bit pointers, however. - return *(*complex128)(v.value) - default: - panic(&ValueError{Method: "Complex", Kind: v.Kind()}) - } -} - -func (v Value) String() string { - switch v.Kind() { - case String: - // A string value is always bigger than a pointer as it is made of a - // pointer and a length. - return *(*string)(v.value) - default: - // Special case because of the special treatment of .String() in Go. - return "<" + v.typecode.String() + " Value>" - } -} - -func (v Value) Bytes() []byte { - switch v.Kind() { - case Slice: - if v.typecode.elem().Kind() != Uint8 { - panic(&ValueError{Method: "Bytes", Kind: v.Kind()}) - } - return *(*[]byte)(v.value) - - case Array: - v.checkAddressable() - - if v.typecode.elem().Kind() != Uint8 { - panic(&ValueError{Method: "Bytes", Kind: v.Kind()}) - } - - // Small inline arrays are not addressable, so we only have to - // handle addressable arrays which will be stored as pointers - // in v.value - return unsafe.Slice((*byte)(v.value), v.Len()) - } - - panic(&ValueError{Method: "Bytes", Kind: v.Kind()}) + return Value{v.Value.Addr()} } func (v Value) Slice(i, j int) Value { - switch v.Kind() { - case Slice: - hdr := *(*sliceHeader)(v.value) - i, j := uintptr(i), uintptr(j) - - if j < i || hdr.cap < j { - slicePanic() - } - - elemSize := v.typecode.underlying().elem().Size() - - hdr.len = j - i - hdr.cap = hdr.cap - i - hdr.data = unsafe.Add(hdr.data, i*elemSize) - - return Value{ - typecode: v.typecode, - value: unsafe.Pointer(&hdr), - flags: v.flags, - } - - case Array: - v.checkAddressable() - buf, length := buflen(v) - i, j := uintptr(i), uintptr(j) - if j < i || length < j { - slicePanic() - } - - elemSize := v.typecode.underlying().elem().Size() - - var hdr sliceHeader - hdr.len = j - i - hdr.cap = length - i - hdr.data = unsafe.Add(buf, i*elemSize) - - sliceType := (*arrayType)(unsafe.Pointer(v.typecode.underlying())).slicePtr - return Value{ - typecode: sliceType, - value: unsafe.Pointer(&hdr), - flags: v.flags, - } - - case String: - i, j := uintptr(i), uintptr(j) - str := *(*stringHeader)(v.value) - - if j < i || str.len < j { - slicePanic() - } - - hdr := stringHeader{ - data: unsafe.Add(str.data, i), - len: j - i, - } - - return Value{ - typecode: v.typecode, - value: unsafe.Pointer(&hdr), - flags: v.flags, - } - } - - panic(&ValueError{Method: "Slice", Kind: v.Kind()}) + return Value{v.Value.Slice(i, j)} } func (v Value) Slice3(i, j, k int) Value { - switch v.Kind() { - case Slice: - hdr := *(*sliceHeader)(v.value) - i, j, k := uintptr(i), uintptr(j), uintptr(k) - - if j < i || k < j || hdr.len < k { - slicePanic() - } - - elemSize := v.typecode.underlying().elem().Size() - - hdr.len = j - i - hdr.cap = k - i - hdr.data = unsafe.Add(hdr.data, i*elemSize) - - return Value{ - typecode: v.typecode, - value: unsafe.Pointer(&hdr), - flags: v.flags, - } - - case Array: - v.checkAddressable() - buf, length := buflen(v) - i, j, k := uintptr(i), uintptr(j), uintptr(k) - if j < i || k < j || length < k { - slicePanic() - } - - elemSize := v.typecode.underlying().elem().Size() - - var hdr sliceHeader - hdr.len = j - i - hdr.cap = k - i - hdr.data = unsafe.Add(buf, i*elemSize) - - sliceType := (*arrayType)(unsafe.Pointer(v.typecode.underlying())).slicePtr - return Value{ - typecode: sliceType, - value: unsafe.Pointer(&hdr), - flags: v.flags, - } - } - - panic("unimplemented: (reflect.Value).Slice3()") -} - -//go:linkname maplen runtime.hashmapLen -func maplen(p unsafe.Pointer) int - -//go:linkname chanlen runtime.chanLen -func chanlen(p unsafe.Pointer) int - -// Len returns the length of this value for slices, strings, arrays, channels, -// and maps. For other types, it panics. -func (v Value) Len() int { - switch v.typecode.Kind() { - case Array: - return v.typecode.Len() - case Chan: - return chanlen(v.pointer()) - case Map: - return maplen(v.pointer()) - case Slice: - return int((*sliceHeader)(v.value).len) - case String: - return int((*stringHeader)(v.value).len) - default: - panic(&ValueError{Method: "Len", Kind: v.Kind()}) - } -} - -//go:linkname chancap runtime.chanCap -func chancap(p unsafe.Pointer) int - -// Cap returns the capacity of this value for arrays, channels and slices. -// For other types, it panics. -func (v Value) Cap() int { - switch v.typecode.Kind() { - case Array: - return v.typecode.Len() - case Chan: - return chancap(v.pointer()) - case Slice: - return int((*sliceHeader)(v.value).cap) - default: - panic(&ValueError{Method: "Cap", Kind: v.Kind()}) - } -} - -//go:linkname mapclear runtime.hashmapClear -func mapclear(p unsafe.Pointer) - -// Clear clears the contents of a map or zeros the contents of a slice -// -// It panics if v's Kind is not Map or Slice. -func (v Value) Clear() { - switch v.typecode.Kind() { - case Map: - mapclear(v.pointer()) - case Slice: - hdr := (*sliceHeader)(v.value) - elemSize := v.typecode.underlying().elem().Size() - memzero(hdr.data, elemSize*hdr.len) - default: - panic(&ValueError{Method: "Clear", Kind: v.Kind()}) - } -} - -// NumField returns the number of fields of this struct. It panics for other -// value types. -func (v Value) NumField() int { - return v.typecode.NumField() + return Value{v.Value.Slice3(i, j, k)} } func (v Value) Elem() Value { - switch v.Kind() { - case Ptr: - ptr := v.pointer() - if ptr == nil { - return Value{} - } - // Don't copy RO flags - flags := (v.flags & (valueFlagIndirect | valueFlagExported)) | valueFlagIndirect - return Value{ - typecode: v.typecode.elem(), - value: ptr, - flags: flags, - } - case Interface: - typecode, value := decomposeInterface(*(*interface{})(v.value)) - return Value{ - typecode: (*rawType)(typecode), - value: value, - flags: v.flags &^ valueFlagIndirect, - } - default: - panic(&ValueError{Method: "Elem", Kind: v.Kind()}) - } + return Value{v.Value.Elem()} } // Field returns the value of the i'th field of this struct. func (v Value) Field(i int) Value { - if v.Kind() != Struct { - panic(&ValueError{Method: "Field", Kind: v.Kind()}) - } - structField := v.typecode.rawField(i) - - // Copy flags but clear EmbedRO; we're not an embedded field anymore - flags := v.flags & ^valueFlagEmbedRO - if structField.PkgPath != "" { - // No PkgPath => not exported. - // Clear exported flag even if the parent was exported. - flags &^= valueFlagExported - - // Update the RO flag - if structField.Anonymous { - // Embedded field - flags |= valueFlagEmbedRO - } else { - flags |= valueFlagStickyRO - } - } else { - // Parent field may not have been exported but we are - flags |= valueFlagExported - } - - size := v.typecode.Size() - fieldType := structField.Type - fieldSize := fieldType.Size() - if v.isIndirect() || fieldSize > unsafe.Sizeof(uintptr(0)) { - // v.value was already a pointer to the value and it should stay that - // way. - return Value{ - flags: flags, - typecode: fieldType, - value: unsafe.Add(v.value, structField.Offset), - } - } - - // The fieldSize is smaller than uintptr, which means that the value will - // have to be stored directly in the interface value. - - if fieldSize == 0 { - // The struct field is zero sized. - // This is a rare situation, but because it's undefined behavior - // to shift the size of the value (zeroing the value), handle this - // situation explicitly. - return Value{ - flags: flags, - typecode: fieldType, - value: unsafe.Pointer(nil), - } - } - - if size > unsafe.Sizeof(uintptr(0)) { - // The value was not stored in the interface before but will be - // afterwards, so load the value (from the correct offset) and return - // it. - ptr := unsafe.Add(v.value, structField.Offset) - value := unsafe.Pointer(loadValue(ptr, fieldSize)) - return Value{ - flags: flags &^ valueFlagIndirect, - typecode: fieldType, - value: value, - } - } - - // The value was already stored directly in the interface and it still - // is. Cut out the part of the value that we need. - value := maskAndShift(uintptr(v.value), structField.Offset, fieldSize) - return Value{ - flags: flags, - typecode: fieldType, - value: unsafe.Pointer(value), - } + return Value{v.Value.Field(i)} } -var uint8Type = TypeOf(uint8(0)).(*rawType) - func (v Value) Index(i int) Value { - switch v.Kind() { - case Slice: - // Extract an element from the slice. - slice := *(*sliceHeader)(v.value) - if uint(i) >= uint(slice.len) { - panic("reflect: slice index out of range") - } - flags := (v.flags & (valueFlagExported | valueFlagIndirect)) | valueFlagIndirect | v.flags.ro() - elem := Value{ - typecode: v.typecode.elem(), - flags: flags, - } - elem.value = unsafe.Add(slice.data, elem.typecode.Size()*uintptr(i)) // pointer to new value - return elem - case String: - // Extract a character from a string. - // A string is never stored directly in the interface, but always as a - // pointer to the string value. - // Keeping valueFlagExported if set, but don't set valueFlagIndirect - // otherwise CanSet will return true for string elements (which is bad, - // strings are read-only). - s := *(*stringHeader)(v.value) - if uint(i) >= uint(s.len) { - panic("reflect: string index out of range") - } - return Value{ - typecode: uint8Type, - value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Add(s.data, i)))), - flags: v.flags & valueFlagExported, - } - case Array: - // Extract an element from the array. - elemType := v.typecode.elem() - elemSize := elemType.Size() - size := v.typecode.Size() - if size == 0 { - // The element size is 0 and/or the length of the array is 0. - return Value{ - typecode: v.typecode.elem(), - flags: v.flags, - } - } - if elemSize > unsafe.Sizeof(uintptr(0)) { - // The resulting value doesn't fit in a pointer so must be - // indirect. Also, because size != 0 this implies that the array - // length must be != 0, and thus that the total size is at least - // elemSize. - addr := unsafe.Add(v.value, elemSize*uintptr(i)) // pointer to new value - return Value{ - typecode: v.typecode.elem(), - flags: v.flags, - value: addr, - } - } - - if size > unsafe.Sizeof(uintptr(0)) || v.isIndirect() { - // The element fits in a pointer, but the array is not stored in the pointer directly. - // Load the value from the pointer. - addr := unsafe.Add(v.value, elemSize*uintptr(i)) // pointer to new value - value := addr - if !v.isIndirect() { - // Use a pointer to the value (don't load the value) if the - // 'indirect' flag is set. - value = unsafe.Pointer(loadValue(addr, elemSize)) - } - return Value{ - typecode: v.typecode.elem(), - flags: v.flags, - value: value, - } - } - - // The value fits in a pointer, so extract it with some shifting and - // masking. - offset := elemSize * uintptr(i) - value := maskAndShift(uintptr(v.value), offset, elemSize) - return Value{ - typecode: v.typecode.elem(), - flags: v.flags, - value: unsafe.Pointer(value), - } - default: - panic(&ValueError{Method: "Index", Kind: v.Kind()}) - } -} - -func (v Value) NumMethod() int { - if v.typecode == nil { - panic(&ValueError{Method: "reflect.Value.NumMethod", Kind: Invalid}) - } - return v.typecode.NumMethod() -} - -// OverflowFloat reports whether the float64 x cannot be represented by v's type. -// It panics if v's Kind is not Float32 or Float64. -func (v Value) OverflowFloat(x float64) bool { - k := v.Kind() - switch k { - case Float32: - return overflowFloat32(x) - case Float64: - return false - } - panic(&ValueError{Method: "reflect.Value.OverflowFloat", Kind: v.Kind()}) -} - -func overflowFloat32(x float64) bool { - if x < 0 { - x = -x - } - return math.MaxFloat32 < x && x <= math.MaxFloat64 + return Value{v.Value.Index(i)} } func (v Value) MapKeys() []Value { - if v.Kind() != Map { - panic(&ValueError{Method: "MapKeys", Kind: v.Kind()}) - } - - // empty map - if v.Len() == 0 { - return nil - } - - keys := make([]Value, 0, v.Len()) - - it := hashmapNewIterator() - k := New(v.typecode.Key()) - e := New(v.typecode.Elem()) - - keyType := v.typecode.key() - keyTypeIsEmptyInterface := keyType.Kind() == Interface && keyType.NumMethod() == 0 - shouldUnpackInterface := !keyTypeIsEmptyInterface && keyType.Kind() != String && !keyType.isBinary() - - for hashmapNext(v.pointer(), it, k.value, e.value) { - if shouldUnpackInterface { - intf := *(*interface{})(k.value) - v := ValueOf(intf) - keys = append(keys, v) - } else { - keys = append(keys, k.Elem()) - } - k = New(v.typecode.Key()) - } - - return keys + keys := v.Value.MapKeys() + return *(*[]Value)(unsafe.Pointer(&keys)) } -//go:linkname hashmapStringGet runtime.hashmapStringGet -func hashmapStringGet(m unsafe.Pointer, key string, value unsafe.Pointer, valueSize uintptr) bool - -//go:linkname hashmapBinaryGet runtime.hashmapBinaryGet -func hashmapBinaryGet(m unsafe.Pointer, key, value unsafe.Pointer, valueSize uintptr) bool - -//go:linkname hashmapInterfaceGet runtime.hashmapInterfaceGet -func hashmapInterfaceGet(m unsafe.Pointer, key interface{}, value unsafe.Pointer, valueSize uintptr) bool - func (v Value) MapIndex(key Value) Value { - if v.Kind() != Map { - panic(&ValueError{Method: "MapIndex", Kind: v.Kind()}) - } - - vkey := v.typecode.key() - - // compare key type with actual key type of map - if !key.typecode.AssignableTo(vkey) { - // type error? - panic("reflect.Value.MapIndex: incompatible types for key") - } - - elemType := v.typecode.Elem() - elem := New(elemType) - - if vkey.Kind() == String { - if ok := hashmapStringGet(v.pointer(), *(*string)(key.value), elem.value, elemType.Size()); !ok { - return Value{} - } - return elem.Elem() - } else if vkey.isBinary() { - var keyptr unsafe.Pointer - if key.isIndirect() || key.typecode.Size() > unsafe.Sizeof(uintptr(0)) { - keyptr = key.value - } else { - keyptr = unsafe.Pointer(&key.value) - } - //TODO(dgryski): zero out padding bytes in key, if any - if ok := hashmapBinaryGet(v.pointer(), keyptr, elem.value, elemType.Size()); !ok { - return Value{} - } - return elem.Elem() - } else { - if ok := hashmapInterfaceGet(v.pointer(), key.Interface(), elem.value, elemType.Size()); !ok { - return Value{} - } - return elem.Elem() - } + return Value{v.Value.MapIndex(key.Value)} } //go:linkname hashmapNewIterator runtime.hashmapNewIterator @@ -1102,988 +68,99 @@ func hashmapNewIterator() unsafe.Pointer func hashmapNext(m unsafe.Pointer, it unsafe.Pointer, key, value unsafe.Pointer) bool func (v Value) MapRange() *MapIter { - if v.Kind() != Map { - panic(&ValueError{Method: "MapRange", Kind: v.Kind()}) - } - - keyType := v.typecode.key() - - keyTypeIsEmptyInterface := keyType.Kind() == Interface && keyType.NumMethod() == 0 - shouldUnpackInterface := !keyTypeIsEmptyInterface && keyType.Kind() != String && !keyType.isBinary() - - return &MapIter{ - m: v, - it: hashmapNewIterator(), - unpackKeyInterface: shouldUnpackInterface, - } + return (*MapIter)(v.Value.MapRange()) } -type MapIter struct { - m Value - it unsafe.Pointer - key Value - val Value - - valid bool - unpackKeyInterface bool -} +type MapIter reflectlite.MapIter func (it *MapIter) Key() Value { - if !it.valid { - panic("reflect.MapIter.Key called on invalid iterator") - } - - if it.unpackKeyInterface { - intf := *(*interface{})(it.key.value) - v := ValueOf(intf) - return v - } - - return it.key.Elem() + return Value{((*reflectlite.MapIter)(it)).Key()} } func (it *MapIter) Value() Value { - if !it.valid { - panic("reflect.MapIter.Value called on invalid iterator") - } - - return it.val.Elem() + return Value{((*reflectlite.MapIter)(it)).Value()} } func (it *MapIter) Next() bool { - it.key = New(it.m.typecode.Key()) - it.val = New(it.m.typecode.Elem()) - - it.valid = hashmapNext(it.m.pointer(), it.it, it.key.value, it.val.value) - return it.valid + return ((*reflectlite.MapIter)(it)).Next() } func (v Value) Set(x Value) { - v.checkAddressable() - v.checkRO() - if !x.typecode.AssignableTo(v.typecode) { - panic("reflect.Value.Set: value of type " + x.typecode.String() + " cannot be assigned to type " + v.typecode.String()) - } - - if v.typecode.Kind() == Interface && x.typecode.Kind() != Interface { - // move the value of x back into the interface, if possible - if x.isIndirect() && x.typecode.Size() <= unsafe.Sizeof(uintptr(0)) { - x.value = unsafe.Pointer(loadValue(x.value, x.typecode.Size())) - } - - intf := composeInterface(unsafe.Pointer(x.typecode), x.value) - x = Value{ - typecode: v.typecode, - value: unsafe.Pointer(&intf), - } - } - - size := v.typecode.Size() - if size <= unsafe.Sizeof(uintptr(0)) && !x.isIndirect() { - storeValue(v.value, size, uintptr(x.value)) - } else { - memcpy(v.value, x.value, size) - } -} - -func (v Value) SetZero() { - v.checkAddressable() - v.checkRO() - size := v.typecode.Size() - memzero(v.value, size) -} - -func (v Value) SetBool(x bool) { - v.checkAddressable() - v.checkRO() - switch v.Kind() { - case Bool: - *(*bool)(v.value) = x - default: - panic(&ValueError{Method: "SetBool", Kind: v.Kind()}) - } -} - -func (v Value) SetInt(x int64) { - v.checkAddressable() - v.checkRO() - switch v.Kind() { - case Int: - *(*int)(v.value) = int(x) - case Int8: - *(*int8)(v.value) = int8(x) - case Int16: - *(*int16)(v.value) = int16(x) - case Int32: - *(*int32)(v.value) = int32(x) - case Int64: - *(*int64)(v.value) = x - default: - panic(&ValueError{Method: "SetInt", Kind: v.Kind()}) - } -} - -func (v Value) SetUint(x uint64) { - v.checkAddressable() - v.checkRO() - switch v.Kind() { - case Uint: - *(*uint)(v.value) = uint(x) - case Uint8: - *(*uint8)(v.value) = uint8(x) - case Uint16: - *(*uint16)(v.value) = uint16(x) - case Uint32: - *(*uint32)(v.value) = uint32(x) - case Uint64: - *(*uint64)(v.value) = x - case Uintptr: - *(*uintptr)(v.value) = uintptr(x) - default: - panic(&ValueError{Method: "SetUint", Kind: v.Kind()}) - } -} - -func (v Value) SetFloat(x float64) { - v.checkAddressable() - v.checkRO() - switch v.Kind() { - case Float32: - *(*float32)(v.value) = float32(x) - case Float64: - *(*float64)(v.value) = x - default: - panic(&ValueError{Method: "SetFloat", Kind: v.Kind()}) - } -} - -func (v Value) SetComplex(x complex128) { - v.checkAddressable() - v.checkRO() - switch v.Kind() { - case Complex64: - *(*complex64)(v.value) = complex64(x) - case Complex128: - *(*complex128)(v.value) = x - default: - panic(&ValueError{Method: "SetComplex", Kind: v.Kind()}) - } -} - -func (v Value) SetString(x string) { - v.checkAddressable() - v.checkRO() - switch v.Kind() { - case String: - *(*string)(v.value) = x - default: - panic(&ValueError{Method: "SetString", Kind: v.Kind()}) - } -} - -func (v Value) SetBytes(x []byte) { - v.checkAddressable() - v.checkRO() - if v.typecode.Kind() != Slice || v.typecode.elem().Kind() != Uint8 { - panic("reflect.Value.SetBytes called on not []byte") - } - - // copy the header contents over - *(*[]byte)(v.value) = x -} - -func (v Value) SetCap(n int) { - panic("unimplemented: (reflect.Value).SetCap()") -} - -func (v Value) SetLen(n int) { - if v.typecode.Kind() != Slice { - panic(&ValueError{Method: "reflect.Value.SetLen", Kind: v.Kind()}) - } - v.checkAddressable() - hdr := (*sliceHeader)(v.value) - if int(uintptr(n)) != n || uintptr(n) > hdr.cap { - panic("reflect.Value.SetLen: slice length out of range") - } - hdr.len = uintptr(n) -} - -func (v Value) checkAddressable() { - if !v.isIndirect() { - panic("reflect: value is not addressable") - } -} - -// OverflowInt reports whether the int64 x cannot be represented by v's type. -// It panics if v's Kind is not Int, Int8, Int16, Int32, or Int64. -func (v Value) OverflowInt(x int64) bool { - switch v.Kind() { - case Int, Int8, Int16, Int32, Int64: - bitSize := v.typecode.Size() * 8 - trunc := (x << (64 - bitSize)) >> (64 - bitSize) - return x != trunc - } - panic(&ValueError{Method: "reflect.Value.OverflowInt", Kind: v.Kind()}) -} - -// OverflowUint reports whether the uint64 x cannot be represented by v's type. -// It panics if v's Kind is not Uint, Uintptr, Uint8, Uint16, Uint32, or Uint64. -func (v Value) OverflowUint(x uint64) bool { - k := v.Kind() - switch k { - case Uint, Uintptr, Uint8, Uint16, Uint32, Uint64: - bitSize := v.typecode.Size() * 8 - trunc := (x << (64 - bitSize)) >> (64 - bitSize) - return x != trunc - } - panic(&ValueError{Method: "reflect.Value.OverflowUint", Kind: v.Kind()}) + v.Value.Set(x.Value) } func (v Value) CanConvert(t Type) bool { - // TODO: Optimize this to not actually perform a conversion - _, ok := convertOp(v, t) - return ok + return v.Value.CanConvert(toRawType(t)) } func (v Value) Convert(t Type) Value { - if v, ok := convertOp(v, t); ok { - return v - } - - panic("reflect.Value.Convert: value of type " + v.typecode.String() + " cannot be converted to type " + t.String()) -} - -func convertOp(src Value, typ Type) (Value, bool) { - - // Easy check first. Do we even need to do anything? - if src.typecode.underlying() == typ.(*rawType).underlying() { - return Value{ - typecode: typ.(*rawType), - value: src.value, - flags: src.flags, - }, true - } - - if rtype := typ.(*rawType); rtype.Kind() == Interface && rtype.NumMethod() == 0 { - iface := composeInterface(unsafe.Pointer(src.typecode), src.value) - return Value{ - typecode: rtype, - value: unsafe.Pointer(&iface), - flags: valueFlagExported, - }, true - } - - switch src.Kind() { - case Int, Int8, Int16, Int32, Int64: - switch rtype := typ.(*rawType); rtype.Kind() { - case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: - return cvtInt(src, rtype), true - case Float32, Float64: - return cvtIntFloat(src, rtype), true - case String: - return cvtIntString(src, rtype), true - } - - case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: - switch rtype := typ.(*rawType); rtype.Kind() { - case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: - return cvtUint(src, rtype), true - case Float32, Float64: - return cvtUintFloat(src, rtype), true - case String: - return cvtUintString(src, rtype), true - } - - case Float32, Float64: - switch rtype := typ.(*rawType); rtype.Kind() { - case Int, Int8, Int16, Int32, Int64: - return cvtFloatInt(src, rtype), true - case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: - return cvtFloatUint(src, rtype), true - case Float32, Float64: - return cvtFloat(src, rtype), true - } - - /* - case Complex64, Complex128: - switch src.Kind() { - case Complex64, Complex128: - return cvtComplex - } - */ - - case Slice: - switch rtype := typ.(*rawType); rtype.Kind() { - case Array: - if src.typecode.elem() == rtype.elem() && rtype.Len() <= src.Len() { - return Value{ - typecode: rtype, - value: (*sliceHeader)(src.value).data, - flags: src.flags | valueFlagIndirect, - }, true - } - case Pointer: - if rtype.Elem().Kind() == Array { - if src.typecode.elem() == rtype.elem().elem() && rtype.elem().Len() <= src.Len() { - return Value{ - typecode: rtype, - value: (*sliceHeader)(src.value).data, - flags: src.flags & (valueFlagExported | valueFlagRO), - }, true - } - } - case String: - if !src.typecode.elem().isNamed() { - switch src.Type().Elem().Kind() { - case Uint8: - return cvtBytesString(src, rtype), true - case Int32: - return cvtRunesString(src, rtype), true - } - } - } - - case String: - rtype := typ.(*rawType) - if typ.Kind() == Slice && !rtype.elem().isNamed() { - switch typ.Elem().Kind() { - case Uint8: - return cvtStringBytes(src, rtype), true - case Int32: - return cvtStringRunes(src, rtype), true - } - } - } - - // TODO(dgryski): Unimplemented: - // Chan - // Non-defined pointers types with same underlying base type - // Interface <-> Type conversions - - return Value{}, false -} - -func cvtInt(v Value, t *rawType) Value { - return makeInt(v.flags, uint64(v.Int()), t) -} - -func cvtUint(v Value, t *rawType) Value { - return makeInt(v.flags, v.Uint(), t) -} - -func cvtIntFloat(v Value, t *rawType) Value { - return makeFloat(v.flags, float64(v.Int()), t) -} - -func cvtUintFloat(v Value, t *rawType) Value { - return makeFloat(v.flags, float64(v.Uint()), t) -} - -func cvtFloatInt(v Value, t *rawType) Value { - return makeInt(v.flags, uint64(int64(v.Float())), t) -} - -func cvtFloatUint(v Value, t *rawType) Value { - return makeInt(v.flags, uint64(v.Float()), t) -} - -func cvtFloat(v Value, t *rawType) Value { - if v.Type().Kind() == Float32 && t.Kind() == Float32 { - // Don't do any conversion if both types have underlying type float32. - // This avoids converting to float64 and back, which will - // convert a signaling NaN to a quiet NaN. See issue 36400. - return makeFloat32(v.flags, v.Float32(), t) - } - return makeFloat(v.flags, v.Float(), t) -} - -//go:linkname stringToBytes runtime.stringToBytes -func stringToBytes(x string) []byte - -func cvtStringBytes(v Value, t *rawType) Value { - b := stringToBytes(*(*string)(v.value)) - return Value{ - typecode: t, - value: unsafe.Pointer(&b), - flags: v.flags, - } -} - -//go:linkname stringFromBytes runtime.stringFromBytes -func stringFromBytes(x []byte) string - -func cvtBytesString(v Value, t *rawType) Value { - s := stringFromBytes(*(*[]byte)(v.value)) - return Value{ - typecode: t, - value: unsafe.Pointer(&s), - flags: v.flags, - } -} - -func makeInt(flags valueFlags, bits uint64, t *rawType) Value { - size := t.Size() - - v := Value{ - typecode: t, - flags: flags, - } - - ptr := unsafe.Pointer(&v.value) - if size > unsafe.Sizeof(uintptr(0)) { - ptr = alloc(size, nil) - v.value = ptr - } - - switch size { - case 1: - *(*uint8)(ptr) = uint8(bits) - case 2: - *(*uint16)(ptr) = uint16(bits) - case 4: - *(*uint32)(ptr) = uint32(bits) - case 8: - *(*uint64)(ptr) = bits - } - return v -} - -func makeFloat(flags valueFlags, f float64, t *rawType) Value { - size := t.Size() - - v := Value{ - typecode: t, - flags: flags, - } - - ptr := unsafe.Pointer(&v.value) - if size > unsafe.Sizeof(uintptr(0)) { - ptr = alloc(size, nil) - v.value = ptr - } - - switch size { - case 4: - *(*float32)(ptr) = float32(f) - case 8: - *(*float64)(ptr) = f - } - return v -} - -func makeFloat32(flags valueFlags, f float32, t *rawType) Value { - v := Value{ - typecode: t, - flags: flags, - } - *(*float32)(unsafe.Pointer(&v.value)) = float32(f) - return v -} - -func cvtIntString(src Value, t *rawType) Value { - panic("cvtUintString: unimplemented") -} - -func cvtUintString(src Value, t *rawType) Value { - panic("cvtUintString: unimplemented") -} - -func cvtStringRunes(src Value, t *rawType) Value { - panic("cvsStringRunes: unimplemented") -} - -func cvtRunesString(src Value, t *rawType) Value { - panic("cvsRunesString: unimplemented") + return Value{v.Value.Convert(toRawType(t))} } //go:linkname slicePanic runtime.slicePanic func slicePanic() func MakeSlice(typ Type, len, cap int) Value { - if typ.Kind() != Slice { - panic("reflect.MakeSlice of non-slice type") - } - - rtype := typ.(*rawType) - - ulen := uint(len) - ucap := uint(cap) - maxSize := (^uintptr(0)) / 2 - elem := rtype.elem() - elementSize := elem.Size() - if elementSize > 1 { - maxSize /= uintptr(elementSize) - } - if ulen > ucap || ucap > uint(maxSize) { - slicePanic() - } - - // This can't overflow because of the above checks. - size := uintptr(ucap) * elementSize - - var slice sliceHeader - slice.cap = uintptr(ucap) - slice.len = uintptr(ulen) - layout := elem.gcLayout() - - slice.data = alloc(size, layout) - - return Value{ - typecode: rtype, - value: unsafe.Pointer(&slice), - flags: valueFlagExported, - } -} - -var zerobuffer unsafe.Pointer - -const zerobufferLen = 32 - -func init() { - // 32 characters of zero bytes - zerobufferStr := "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - s := (*stringHeader)(unsafe.Pointer(&zerobufferStr)) - zerobuffer = s.data + return Value{reflectlite.MakeSlice(toRawType(typ), len, cap)} } func Zero(typ Type) Value { - size := typ.Size() - if size <= unsafe.Sizeof(uintptr(0)) { - return Value{ - typecode: typ.(*rawType), - value: nil, - flags: valueFlagExported | valueFlagRO, - } - } - - if size <= zerobufferLen { - return Value{ - typecode: typ.(*rawType), - value: unsafe.Pointer(zerobuffer), - flags: valueFlagExported | valueFlagRO, - } - } - - return Value{ - typecode: typ.(*rawType), - value: alloc(size, nil), - flags: valueFlagExported | valueFlagRO, - } + return Value{reflectlite.Zero(toRawType(typ))} } // New is the reflect equivalent of the new(T) keyword, returning a pointer to a // new value of the given type. func New(typ Type) Value { - return Value{ - typecode: pointerTo(typ.(*rawType)), - value: alloc(typ.Size(), nil), - flags: valueFlagExported, - } + return Value{reflectlite.New(toRawType(typ))} } -type funcHeader struct { - Context unsafe.Pointer - Code unsafe.Pointer -} - -type SliceHeader struct { - Data uintptr - Len intw - Cap intw -} - -// Slice header that matches the underlying structure. Used for when we switch -// to a precise GC, which needs to know exactly where pointers live. -type sliceHeader struct { - data unsafe.Pointer - len uintptr - cap uintptr -} - -type StringHeader struct { - Data uintptr - Len intw -} - -// Like sliceHeader, this type is used internally to make sure pointer and -// non-pointer fields match those of actual strings. -type stringHeader struct { - data unsafe.Pointer - len uintptr -} - -// Verify SliceHeader and StringHeader sizes. -// See https://github.com/tinygo-org/tinygo/pull/4156 -// and https://github.com/tinygo-org/tinygo/issues/1284. -var ( - _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(SliceHeader{})]byte{} - _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(sliceHeader{})]byte{} - _ [unsafe.Sizeof("")]byte = [unsafe.Sizeof(StringHeader{})]byte{} - _ [unsafe.Sizeof("")]byte = [unsafe.Sizeof(stringHeader{})]byte{} -) - -type ValueError struct { - Method string - Kind Kind -} - -func (e *ValueError) Error() string { - if e.Kind == 0 { - return "reflect: call of " + e.Method + " on zero Value" - } - return "reflect: call of " + e.Method + " on " + e.Kind.String() + " Value" -} - -//go:linkname memcpy runtime.memcpy -func memcpy(dst, src unsafe.Pointer, size uintptr) - -//go:linkname memzero runtime.memzero -func memzero(ptr unsafe.Pointer, size uintptr) - -//go:linkname alloc runtime.alloc -func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer - -//go:linkname sliceAppend runtime.sliceAppend -func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen uintptr, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) - -//go:linkname sliceCopy runtime.sliceCopy -func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr) int +type ValueError = reflectlite.ValueError // Copy copies the contents of src into dst until either // dst has been filled or src has been exhausted. func Copy(dst, src Value) int { - compatibleTypes := false || - // dst and src are both slices or arrays with equal types - ((dst.typecode.Kind() == Slice || dst.typecode.Kind() == Array) && - (src.typecode.Kind() == Slice || src.typecode.Kind() == Array) && - (dst.typecode.elem() == src.typecode.elem())) || - // dst is array or slice of uint8 and src is string - ((dst.typecode.Kind() == Slice || dst.typecode.Kind() == Array) && - dst.typecode.elem().Kind() == Uint8 && - src.typecode.Kind() == String) - - if !compatibleTypes { - panic("Copy: type mismatch: " + dst.typecode.String() + "/" + src.typecode.String()) - } - - // Can read from an unaddressable array but not write to one. - if dst.typecode.Kind() == Array && !dst.isIndirect() { - panic("reflect.Copy: unaddressable array value") - } - - dstbuf, dstlen := buflen(dst) - srcbuf, srclen := buflen(src) - - if srclen > 0 { - dst.checkRO() - } - - return sliceCopy(dstbuf, srcbuf, dstlen, srclen, dst.typecode.elem().Size()) -} - -func buflen(v Value) (unsafe.Pointer, uintptr) { - var buf unsafe.Pointer - var len uintptr - switch v.typecode.Kind() { - case Slice: - hdr := (*sliceHeader)(v.value) - buf = hdr.data - len = hdr.len - case Array: - if v.isIndirect() || v.typecode.Size() > unsafe.Sizeof(uintptr(0)) { - buf = v.value - } else { - buf = unsafe.Pointer(&v.value) - } - len = uintptr(v.Len()) - case String: - hdr := (*stringHeader)(v.value) - buf = hdr.data - len = hdr.len - default: - // This shouldn't happen - panic("reflect.Copy: not slice or array or string") - } - - return buf, len -} - -//go:linkname sliceGrow runtime.sliceGrow -func sliceGrow(buf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) - -// extend slice to hold n new elements -func extendSlice(v Value, n int) sliceHeader { - if v.Kind() != Slice { - panic(&ValueError{Method: "extendSlice", Kind: v.Kind()}) - } - - var old sliceHeader - if v.value != nil { - old = *(*sliceHeader)(v.value) - } - - nbuf, nlen, ncap := sliceGrow(old.data, old.len, old.cap, old.len+uintptr(n), v.typecode.elem().Size()) - - return sliceHeader{ - data: nbuf, - len: nlen + uintptr(n), - cap: ncap, - } + return reflectlite.Copy(dst.Value, src.Value) } // Append appends the values x to a slice s and returns the resulting slice. // As in Go, each x's value must be assignable to the slice's element type. func Append(v Value, x ...Value) Value { - if v.Kind() != Slice { - panic(&ValueError{Method: "Append", Kind: v.Kind()}) - } - oldLen := v.Len() - newslice := extendSlice(v, len(x)) - v.flags = valueFlagExported - v.value = (unsafe.Pointer)(&newslice) - for i, xx := range x { - v.Index(oldLen + i).Set(xx) - } - return v + y := *(*[]reflectlite.Value)(unsafe.Pointer(&x)) + return Value{reflectlite.Append(v.Value, y...)} } // AppendSlice appends a slice t to a slice s and returns the resulting slice. // The slices s and t must have the same element type. func AppendSlice(s, t Value) Value { - if s.typecode.Kind() != Slice || t.typecode.Kind() != Slice || s.typecode != t.typecode { - // Not a very helpful error message, but shortened to just one error to - // keep code size down. - panic("reflect.AppendSlice: invalid types") - } - if !s.isExported() || !t.isExported() { - // One of the sides was not exported, so can't access the data. - panic("reflect.AppendSlice: unexported") - } - sSlice := (*sliceHeader)(s.value) - tSlice := (*sliceHeader)(t.value) - elemSize := s.typecode.elem().Size() - ptr, len, cap := sliceAppend(sSlice.data, tSlice.data, sSlice.len, sSlice.cap, tSlice.len, elemSize) - result := &sliceHeader{ - data: ptr, - len: len, - cap: cap, - } - return Value{ - typecode: s.typecode, - value: unsafe.Pointer(result), - flags: valueFlagExported, - } + return Value{reflectlite.AppendSlice(s.Value, t.Value)} } -// Grow increases the slice's capacity, if necessary, to guarantee space for -// another n elements. After Grow(n), at least n elements can be appended -// to the slice without another allocation. -// -// It panics if v's Kind is not a Slice or if n is negative or too large to -// allocate the memory. -func (v Value) Grow(n int) { - v.checkAddressable() - if n < 0 { - panic("reflect.Grow: negative length") - } - if v.Kind() != Slice { - panic(&ValueError{Method: "Grow", Kind: v.Kind()}) - } - slice := (*sliceHeader)(v.value) - newslice := extendSlice(v, n) - // Only copy the new data and cap: the len remains unchanged. - slice.data = newslice.data - slice.cap = newslice.cap -} - -//go:linkname hashmapStringSet runtime.hashmapStringSet -func hashmapStringSet(m unsafe.Pointer, key string, value unsafe.Pointer) - -//go:linkname hashmapBinarySet runtime.hashmapBinarySet -func hashmapBinarySet(m unsafe.Pointer, key, value unsafe.Pointer) - -//go:linkname hashmapInterfaceSet runtime.hashmapInterfaceSet -func hashmapInterfaceSet(m unsafe.Pointer, key interface{}, value unsafe.Pointer) - -//go:linkname hashmapStringDelete runtime.hashmapStringDelete -func hashmapStringDelete(m unsafe.Pointer, key string) - -//go:linkname hashmapBinaryDelete runtime.hashmapBinaryDelete -func hashmapBinaryDelete(m unsafe.Pointer, key unsafe.Pointer) - -//go:linkname hashmapInterfaceDelete runtime.hashmapInterfaceDelete -func hashmapInterfaceDelete(m unsafe.Pointer, key interface{}) - func (v Value) SetMapIndex(key, elem Value) { - v.checkRO() - if v.Kind() != Map { - panic(&ValueError{Method: "SetMapIndex", Kind: v.Kind()}) - } - - vkey := v.typecode.key() - - // compare key type with actual key type of map - if !key.typecode.AssignableTo(vkey) { - panic("reflect.Value.SetMapIndex: incompatible types for key") - } - - // if elem is the zero Value, it means delete - del := elem == Value{} - - if !del && !elem.typecode.AssignableTo(v.typecode.elem()) { - panic("reflect.Value.SetMapIndex: incompatible types for value") - } - - // make elem an interface if it needs to be converted - if v.typecode.elem().Kind() == Interface && elem.typecode.Kind() != Interface { - intf := composeInterface(unsafe.Pointer(elem.typecode), elem.value) - elem = Value{ - typecode: v.typecode.elem(), - value: unsafe.Pointer(&intf), - } - } - - if key.Kind() == String { - if del { - hashmapStringDelete(v.pointer(), *(*string)(key.value)) - } else { - var elemptr unsafe.Pointer - if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) { - elemptr = elem.value - } else { - elemptr = unsafe.Pointer(&elem.value) - } - hashmapStringSet(v.pointer(), *(*string)(key.value), elemptr) - } - - } else if key.typecode.isBinary() { - var keyptr unsafe.Pointer - if key.isIndirect() || key.typecode.Size() > unsafe.Sizeof(uintptr(0)) { - keyptr = key.value - } else { - keyptr = unsafe.Pointer(&key.value) - } - - if del { - hashmapBinaryDelete(v.pointer(), keyptr) - } else { - var elemptr unsafe.Pointer - if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) { - elemptr = elem.value - } else { - elemptr = unsafe.Pointer(&elem.value) - } - hashmapBinarySet(v.pointer(), keyptr, elemptr) - } - } else { - if del { - hashmapInterfaceDelete(v.pointer(), key.Interface()) - } else { - var elemptr unsafe.Pointer - if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) { - elemptr = elem.value - } else { - elemptr = unsafe.Pointer(&elem.value) - } - - hashmapInterfaceSet(v.pointer(), key.Interface(), elemptr) - } - } + v.Value.SetMapIndex(key.Value, elem.Value) } // FieldByIndex returns the nested field corresponding to index. func (v Value) FieldByIndex(index []int) Value { - if len(index) == 1 { - return v.Field(index[0]) - } - if v.Kind() != Struct { - panic(&ValueError{"FieldByIndex", v.Kind()}) - } - for i, x := range index { - if i > 0 { - if v.Kind() == Pointer && v.typecode.elem().Kind() == Struct { - if v.IsNil() { - panic("reflect: indirection through nil pointer to embedded struct") - } - v = v.Elem() - } - } - v = v.Field(x) - } - return v + return Value{v.Value.FieldByIndex(index)} } // FieldByIndexErr returns the nested field corresponding to index. func (v Value) FieldByIndexErr(index []int) (Value, error) { - return Value{}, &ValueError{Method: "FieldByIndexErr"} + out, err := v.Value.FieldByIndexErr(index) + return Value{out}, err } func (v Value) FieldByName(name string) Value { - if v.Kind() != Struct { - panic(&ValueError{"FieldByName", v.Kind()}) - } - - if field, ok := v.typecode.FieldByName(name); ok { - return v.FieldByIndex(field.Index) - } - return Value{} + return Value{v.Value.FieldByName(name)} } func (v Value) FieldByNameFunc(match func(string) bool) Value { - if v.Kind() != Struct { - panic(&ValueError{"FieldByName", v.Kind()}) - } - - if field, ok := v.typecode.FieldByNameFunc(match); ok { - return v.FieldByIndex(field.Index) - } - return Value{} + return Value{v.Value.FieldByNameFunc(match)} } //go:linkname hashmapMake runtime.hashmapMake func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) unsafe.Pointer -// MakeMapWithSize creates a new map with the specified type and initial space -// for approximately n elements. -func MakeMapWithSize(typ Type, n int) Value { - - // TODO(dgryski): deduplicate these? runtime and reflect both need them. - const ( - hashmapAlgorithmBinary uint8 = iota - hashmapAlgorithmString - hashmapAlgorithmInterface - ) - - if typ.Kind() != Map { - panic(&ValueError{Method: "MakeMap", Kind: typ.Kind()}) - } - - if n < 0 { - panic("reflect.MakeMapWithSize: negative size hint") - } - - key := typ.Key().(*rawType) - val := typ.Elem().(*rawType) - - var alg uint8 - - if key.Kind() == String { - alg = hashmapAlgorithmString - } else if key.isBinary() { - alg = hashmapAlgorithmBinary - } else { - alg = hashmapAlgorithmInterface - } - - m := hashmapMake(key.Size(), val.Size(), uintptr(n), alg) - - return Value{ - typecode: typ.(*rawType), - value: m, - flags: valueFlagExported, - } -} - type SelectDir int const ( @@ -2113,7 +190,13 @@ func (v Value) Close() { // MakeMap creates a new map with the specified type. func MakeMap(typ Type) Value { - return MakeMapWithSize(typ, 8) + return Value{reflectlite.MakeMap(toRawType(typ))} +} + +// MakeMapWithSize creates a new map with the specified type and initial space +// for approximately n elements. +func MakeMapWithSize(typ Type, n int) Value { + return Value{reflectlite.MakeMapWithSize(toRawType(typ), n)} } func (v Value) Call(in []Value) []Value { @@ -2124,6 +207,10 @@ func (v Value) CallSlice(in []Value) []Value { panic("unimplemented: (reflect.Value).CallSlice()") } +func (v Value) Equal(u Value) bool { + return v.Value.Equal(u.Value) +} + func (v Value) Method(i int) Value { panic("unimplemented: (reflect.Value).Method()") } diff --git a/src/reflect/visiblefields.go b/src/reflect/visiblefields.go index 9375faa110..ea7a0fd008 100644 --- a/src/reflect/visiblefields.go +++ b/src/reflect/visiblefields.go @@ -1,105 +1,7 @@ -// Copyright 2021 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - package reflect -// VisibleFields returns all the visible fields in t, which must be a -// struct type. A field is defined as visible if it's accessible -// directly with a FieldByName call. The returned fields include fields -// inside anonymous struct members and unexported fields. They follow -// the same order found in the struct, with anonymous fields followed -// immediately by their promoted fields. -// -// For each element e of the returned slice, the corresponding field -// can be retrieved from a value v of type t by calling v.FieldByIndex(e.Index). -func VisibleFields(t Type) []StructField { - if t == nil { - panic("reflect: VisibleFields(nil)") - } - if t.Kind() != Struct { - panic("reflect.VisibleFields of non-struct type") - } - w := &visibleFieldsWalker{ - byName: make(map[string]int), - visiting: make(map[Type]bool), - fields: make([]StructField, 0, t.NumField()), - index: make([]int, 0, 2), - } - w.walk(t) - // Remove all the fields that have been hidden. - // Use an in-place removal that avoids copying in - // the common case that there are no hidden fields. - j := 0 - for i := range w.fields { - f := &w.fields[i] - if f.Name == "" { - continue - } - if i != j { - // A field has been removed. We need to shuffle - // all the subsequent elements up. - w.fields[j] = *f - } - j++ - } - return w.fields[:j] -} - -type visibleFieldsWalker struct { - byName map[string]int - visiting map[Type]bool - fields []StructField - index []int -} +import "internal/reflectlite" -// walk walks all the fields in the struct type t, visiting -// fields in index preorder and appending them to w.fields -// (this maintains the required ordering). -// Fields that have been overridden have their -// Name field cleared. -func (w *visibleFieldsWalker) walk(t Type) { - if w.visiting[t] { - return - } - w.visiting[t] = true - for i := 0; i < t.NumField(); i++ { - f := t.Field(i) - w.index = append(w.index, i) - add := true - if oldIndex, ok := w.byName[f.Name]; ok { - old := &w.fields[oldIndex] - if len(w.index) == len(old.Index) { - // Fields with the same name at the same depth - // cancel one another out. Set the field name - // to empty to signify that has happened, and - // there's no need to add this field. - old.Name = "" - add = false - } else if len(w.index) < len(old.Index) { - // The old field loses because it's deeper than the new one. - old.Name = "" - } else { - // The old field wins because it's shallower than the new one. - add = false - } - } - if add { - // Copy the index so that it's not overwritten - // by the other appends. - f.Index = append([]int(nil), w.index...) - w.byName[f.Name] = len(w.fields) - w.fields = append(w.fields, f) - } - if f.Anonymous { - if f.Type.Kind() == Pointer { - f.Type = f.Type.Elem() - } - if f.Type.Kind() == Struct { - w.walk(f.Type) - } - } - w.index = w.index[:len(w.index)-1] - } - delete(w.visiting, t) +func VisibleFields(t Type) []StructField { + return reflectlite.VisibleFields(toRawType(t)) } From 9e143efd73284d23b42fb8f4661e6b01c7ac6b2d Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 8 Mar 2025 12:49:23 -0800 Subject: [PATCH 418/444] reflect: add Go 1.24 iter.Seq[2] methods --- src/reflect/iter.go | 166 ++++++++++++++++++++++++++++++++++++++++++++ src/reflect/type.go | 32 +++++++++ 2 files changed, 198 insertions(+) create mode 100644 src/reflect/iter.go diff --git a/src/reflect/iter.go b/src/reflect/iter.go new file mode 100644 index 0000000000..90a9469bb5 --- /dev/null +++ b/src/reflect/iter.go @@ -0,0 +1,166 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package reflect + +import "iter" + +func rangeNum[T int8 | int16 | int32 | int64 | int | + uint8 | uint16 | uint32 | uint64 | uint | + uintptr, N int64 | uint64](v N) iter.Seq[Value] { + return func(yield func(v Value) bool) { + // cannot use range T(v) because no core type. + for i := T(0); i < T(v); i++ { + if !yield(ValueOf(i)) { + return + } + } + } +} + +// Seq returns an iter.Seq[Value] that loops over the elements of v. +// If v's kind is Func, it must be a function that has no results and +// that takes a single argument of type func(T) bool for some type T. +// If v's kind is Pointer, the pointer element type must have kind Array. +// Otherwise v's kind must be Int, Int8, Int16, Int32, Int64, +// Uint, Uint8, Uint16, Uint32, Uint64, Uintptr, +// Array, Chan, Map, Slice, or String. +func (v Value) Seq() iter.Seq[Value] { + // TODO: canRangeFunc + // if canRangeFunc(v.typ()) { + // return func(yield func(Value) bool) { + // rf := MakeFunc(v.Type().In(0), func(in []Value) []Value { + // return []Value{ValueOf(yield(in[0]))} + // }) + // v.Call([]Value{rf}) + // } + // } + switch v.Kind() { + case Int: + return rangeNum[int](v.Int()) + case Int8: + return rangeNum[int8](v.Int()) + case Int16: + return rangeNum[int16](v.Int()) + case Int32: + return rangeNum[int32](v.Int()) + case Int64: + return rangeNum[int64](v.Int()) + case Uint: + return rangeNum[uint](v.Uint()) + case Uint8: + return rangeNum[uint8](v.Uint()) + case Uint16: + return rangeNum[uint16](v.Uint()) + case Uint32: + return rangeNum[uint32](v.Uint()) + case Uint64: + return rangeNum[uint64](v.Uint()) + case Uintptr: + return rangeNum[uintptr](v.Uint()) + case Pointer: + if v.Elem().Kind() != Array { + break + } + return func(yield func(Value) bool) { + v = v.Elem() + for i := 0; i < v.Len(); i++ { + if !yield(ValueOf(i)) { + return + } + } + } + case Array, Slice: + return func(yield func(Value) bool) { + for i := 0; i < v.Len(); i++ { + if !yield(ValueOf(i)) { + return + } + } + } + case String: + return func(yield func(Value) bool) { + for i := range v.String() { + if !yield(ValueOf(i)) { + return + } + } + } + case Map: + return func(yield func(Value) bool) { + i := v.MapRange() + for i.Next() { + if !yield(i.Key()) { + return + } + } + } + case Chan: + return func(yield func(Value) bool) { + for value, ok := v.Recv(); ok; value, ok = v.Recv() { + if !yield(value) { + return + } + } + } + } + panic("reflect: " + v.Type().String() + " cannot produce iter.Seq[Value]") +} + +// Seq2 returns an iter.Seq2[Value, Value] that loops over the elements of v. +// If v's kind is Func, it must be a function that has no results and +// that takes a single argument of type func(K, V) bool for some type K, V. +// If v's kind is Pointer, the pointer element type must have kind Array. +// Otherwise v's kind must be Array, Map, Slice, or String. +func (v Value) Seq2() iter.Seq2[Value, Value] { + // TODO: canRangeFunc2 + // if canRangeFunc2(v.typ()) { + // return func(yield func(Value, Value) bool) { + // rf := MakeFunc(v.Type().In(0), func(in []Value) []Value { + // return []Value{ValueOf(yield(in[0], in[1]))} + // }) + // v.Call([]Value{rf}) + // } + // } + switch v.Kind() { + case Pointer: + if v.Elem().Kind() != Array { + break + } + return func(yield func(Value, Value) bool) { + v = v.Elem() + for i := 0; i < v.Len(); i++ { + if !yield(ValueOf(i), v.Index(i)) { + return + } + } + } + case Array, Slice: + return func(yield func(Value, Value) bool) { + for i := 0; i < v.Len(); i++ { + if !yield(ValueOf(i), v.Index(i)) { + return + } + } + } + case String: + return func(yield func(Value, Value) bool) { + for i, v := range v.String() { + if !yield(ValueOf(i), ValueOf(v)) { + return + } + } + } + case Map: + return func(yield func(Value, Value) bool) { + i := v.MapRange() + for i.Next() { + if !yield(i.Key(), i.Value()) { + return + } + } + } + } + panic("reflect: " + v.Type().String() + " cannot produce iter.Seq2[Value, Value]") +} diff --git a/src/reflect/type.go b/src/reflect/type.go index e5417f8e8f..08c73bb2ea 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -329,6 +329,12 @@ type Type interface { // OverflowUint reports whether the uint64 x cannot be represented by type t. // It panics if t's Kind is not Uint, Uintptr, Uint8, Uint16, Uint32, or Uint64. OverflowUint(x uint64) bool + + // CanSeq reports whether a [Value] with this type can be iterated over using [Value.Seq]. + CanSeq() bool + + // CanSeq2 reports whether a [Value] with this type can be iterated over using [Value.Seq2]. + CanSeq2() bool } type rawType struct { @@ -359,6 +365,32 @@ func (t *rawType) AssignableTo(u Type) bool { return t.RawType.AssignableTo(&(u.(*rawType).RawType)) } +func (t *rawType) CanSeq() bool { + switch t.Kind() { + case Int8, Int16, Int32, Int64, Int, Uint8, Uint16, Uint32, Uint64, Uint, Uintptr, Array, Slice, Chan, String, Map: + return true + case Func: + return false // TODO: implement canRangeFunc + // return canRangeFunc(&t.) + case Pointer: + return t.Elem().Kind() == Array + } + return false +} + +func (t *rawType) CanSeq2() bool { + switch t.Kind() { + case Array, Slice, String, Map: + return true + case Func: + return false // TODO: implement canRangeFunc2 + // return canRangeFunc2(&t.t) + case Pointer: + return t.Elem().Kind() == Array + } + return false +} + func (t *rawType) ConvertibleTo(u Type) bool { panic("unimplemented: (reflect.Type).ConvertibleTo()") } From 99a618385b796c3960033c795c886e61b1c09461 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 8 Mar 2025 12:53:15 -0800 Subject: [PATCH 419/444] runtime: use package reflectlite --- src/runtime/hashmap.go | 28 ++++++++++++++-------------- src/runtime/interface.go | 28 ++++++++++++++-------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index ad42fda753..894d92a1ba 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -6,7 +6,7 @@ package runtime // https://golang.org/src/runtime/map.go import ( - "reflect" + "internal/reflectlite" "tinygo" "unsafe" ) @@ -539,8 +539,8 @@ func hashmapStringDelete(m *hashmap, key string) { // a field is exported and thus allows circumventing the type system. // The hash function needs it as it also needs to hash unexported struct fields. // -//go:linkname valueInterfaceUnsafe reflect.valueInterfaceUnsafe -func valueInterfaceUnsafe(v reflect.Value) interface{} +//go:linkname valueInterfaceUnsafe internal/reflectlite.valueInterfaceUnsafe +func valueInterfaceUnsafe(v reflectlite.Value) interface{} func hashmapFloat32Hash(ptr unsafe.Pointer, seed uintptr) uint32 { f := *(*uint32)(ptr) @@ -561,7 +561,7 @@ func hashmapFloat64Hash(ptr unsafe.Pointer, seed uintptr) uint32 { } func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 { - x := reflect.ValueOf(itf) + x := reflectlite.ValueOf(itf) if x.RawType() == nil { return 0 // nil interface } @@ -574,39 +574,39 @@ func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 { } switch x.RawType().Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + case reflectlite.Int, reflectlite.Int8, reflectlite.Int16, reflectlite.Int32, reflectlite.Int64: return hash32(ptr, x.RawType().Size(), seed) - case reflect.Bool, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + case reflectlite.Bool, reflectlite.Uint, reflectlite.Uint8, reflectlite.Uint16, reflectlite.Uint32, reflectlite.Uint64, reflectlite.Uintptr: return hash32(ptr, x.RawType().Size(), seed) - case reflect.Float32: + case reflectlite.Float32: // It should be possible to just has the contents. However, NaN != NaN // so if you're using lots of NaNs as map keys (you shouldn't) then hash // time may become exponential. To fix that, it would be better to // return a random number instead: // https://research.swtch.com/randhash return hashmapFloat32Hash(ptr, seed) - case reflect.Float64: + case reflectlite.Float64: return hashmapFloat64Hash(ptr, seed) - case reflect.Complex64: + case reflectlite.Complex64: rptr, iptr := ptr, unsafe.Add(ptr, 4) return hashmapFloat32Hash(rptr, seed) ^ hashmapFloat32Hash(iptr, seed) - case reflect.Complex128: + case reflectlite.Complex128: rptr, iptr := ptr, unsafe.Add(ptr, 8) return hashmapFloat64Hash(rptr, seed) ^ hashmapFloat64Hash(iptr, seed) - case reflect.String: + case reflectlite.String: return hashmapStringHash(x.String(), seed) - case reflect.Chan, reflect.Ptr, reflect.UnsafePointer: + case reflectlite.Chan, reflectlite.Ptr, reflectlite.UnsafePointer: // It might seem better to just return the pointer, but that won't // result in an evenly distributed hashmap. Instead, hash the pointer // like most other types. return hash32(ptr, x.RawType().Size(), seed) - case reflect.Array: + case reflectlite.Array: var hash uint32 for i := 0; i < x.Len(); i++ { hash ^= hashmapInterfaceHash(valueInterfaceUnsafe(x.Index(i)), seed) } return hash - case reflect.Struct: + case reflectlite.Struct: var hash uint32 for i := 0; i < x.NumField(); i++ { hash ^= hashmapInterfaceHash(valueInterfaceUnsafe(x.Field(i)), seed) diff --git a/src/runtime/interface.go b/src/runtime/interface.go index b9813225f2..e1d263a7e3 100644 --- a/src/runtime/interface.go +++ b/src/runtime/interface.go @@ -6,7 +6,7 @@ package runtime // anything (including non-pointers). import ( - "reflect" + "internal/reflectlite" "unsafe" ) @@ -27,12 +27,12 @@ func decomposeInterface(i _interface) (unsafe.Pointer, unsafe.Pointer) { // Return true iff both interfaces are equal. func interfaceEqual(x, y interface{}) bool { - return reflectValueEqual(reflect.ValueOf(x), reflect.ValueOf(y)) + return reflectValueEqual(reflectlite.ValueOf(x), reflectlite.ValueOf(y)) } -func reflectValueEqual(x, y reflect.Value) bool { +func reflectValueEqual(x, y reflectlite.Value) bool { // Note: doing a x.Type() == y.Type() comparison would not work here as that - // would introduce an infinite recursion: comparing two reflect.Type values + // would introduce an infinite recursion: comparing two reflectlite.Type values // is done with this reflectValueEqual runtime call. if x.RawType() == nil || y.RawType() == nil { // One of them is nil. @@ -46,35 +46,35 @@ func reflectValueEqual(x, y reflect.Value) bool { } switch x.RawType().Kind() { - case reflect.Bool: + case reflectlite.Bool: return x.Bool() == y.Bool() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + case reflectlite.Int, reflectlite.Int8, reflectlite.Int16, reflectlite.Int32, reflectlite.Int64: return x.Int() == y.Int() - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + case reflectlite.Uint, reflectlite.Uint8, reflectlite.Uint16, reflectlite.Uint32, reflectlite.Uint64, reflectlite.Uintptr: return x.Uint() == y.Uint() - case reflect.Float32, reflect.Float64: + case reflectlite.Float32, reflectlite.Float64: return x.Float() == y.Float() - case reflect.Complex64, reflect.Complex128: + case reflectlite.Complex64, reflectlite.Complex128: return x.Complex() == y.Complex() - case reflect.String: + case reflectlite.String: return x.String() == y.String() - case reflect.Chan, reflect.Ptr, reflect.UnsafePointer: + case reflectlite.Chan, reflectlite.Ptr, reflectlite.UnsafePointer: return x.UnsafePointer() == y.UnsafePointer() - case reflect.Array: + case reflectlite.Array: for i := 0; i < x.Len(); i++ { if !reflectValueEqual(x.Index(i), y.Index(i)) { return false } } return true - case reflect.Struct: + case reflectlite.Struct: for i := 0; i < x.NumField(); i++ { if !reflectValueEqual(x.Field(i), y.Field(i)) { return false } } return true - case reflect.Interface: + case reflectlite.Interface: return reflectValueEqual(x.Elem(), y.Elem()) default: runtimePanic("comparing un-comparable type") From c8d0b87a678bcb772b4a36f5911eb3e4b10d0c64 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 8 Mar 2025 15:45:37 -0800 Subject: [PATCH 420/444] internal/reflectlite, reflect: handle different StructField --- src/internal/reflectlite/type.go | 2 +- src/reflect/deepequal.go | 7 ++++ src/reflect/type.go | 63 ++++++++++++++++++++++++++++++-- src/reflect/visiblefields.go | 8 +++- 4 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 src/reflect/deepequal.go diff --git a/src/internal/reflectlite/type.go b/src/internal/reflectlite/type.go index b81468de40..10350676c6 100644 --- a/src/internal/reflectlite/type.go +++ b/src/internal/reflectlite/type.go @@ -294,7 +294,6 @@ func (t *RawType) String() string { } return s } - switch t.Kind() { case Chan: elem := t.elem().String() @@ -977,6 +976,7 @@ func (t *RawType) FieldByIndex(index []int) StructField { } // A StructField describes a single field in a struct. +// This must be kept in sync with [reflect.StructField]. type StructField struct { // Name indicates the field name. Name string diff --git a/src/reflect/deepequal.go b/src/reflect/deepequal.go new file mode 100644 index 0000000000..362385aed8 --- /dev/null +++ b/src/reflect/deepequal.go @@ -0,0 +1,7 @@ +package reflect + +import "internal/reflectlite" + +func DeepEqual(x, y interface{}) bool { + return reflectlite.DeepEqual(x, y) +} diff --git a/src/reflect/type.go b/src/reflect/type.go index 08c73bb2ea..5116108dc7 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -342,6 +342,9 @@ type rawType struct { } func toType(t reflectlite.Type) Type { + if t == nil { + return nil + } return (*rawType)(unsafe.Pointer(t.(*reflectlite.RawType))) } @@ -395,6 +398,30 @@ func (t *rawType) ConvertibleTo(u Type) bool { panic("unimplemented: (reflect.Type).ConvertibleTo()") } +func (t *rawType) Elem() Type { + return toType(t.RawType.Elem()) +} + +func (t *rawType) Field(i int) StructField { + f := t.RawType.Field(i) + return toStructField(f) +} + +func (t *rawType) FieldByIndex(index []int) StructField { + f := t.RawType.FieldByIndex(index) + return toStructField(f) +} + +func (t *rawType) FieldByName(name string) (StructField, bool) { + f, ok := t.RawType.FieldByName(name) + return toStructField(f), ok +} + +func (t *rawType) FieldByNameFunc(match func(string) bool) (StructField, bool) { + f, ok := t.RawType.FieldByNameFunc(match) + return toStructField(f), ok +} + func (t *rawType) Implements(u Type) bool { return t.RawType.Implements(&(u.(*rawType).RawType)) } @@ -431,11 +458,41 @@ func (t *rawType) Out(i int) Type { panic("unimplemented: (reflect.Type).Out()") } -func (t *rawType) Elem() Type { - return toType(t.RawType.Elem()) +// A StructField describes a single field in a struct. +// This must be kept in sync with [reflectlite.StructField]. +type StructField struct { + // Name indicates the field name. + Name string + + // PkgPath is the package path where the struct containing this field is + // declared for unexported fields, or the empty string for exported fields. + PkgPath string + + Type Type + Tag StructTag // field tag string + Offset uintptr + Index []int // index sequence for Type.FieldByIndex + Anonymous bool +} + +func toStructField(f reflectlite.StructField) StructField { + return StructField{ + Name: f.Name, + PkgPath: f.PkgPath, + Type: toType(f.Type), + Tag: f.Tag, + Offset: f.Offset, + Index: f.Index, + Anonymous: f.Anonymous, + } +} + +// IsExported reports whether the field is exported. +func (f StructField) IsExported() bool { + return f.PkgPath == "" } -type StructField = reflectlite.StructField +type StructTag = reflectlite.StructTag func TypeFor[T any]() Type { return toType(reflectlite.TypeFor[T]()) diff --git a/src/reflect/visiblefields.go b/src/reflect/visiblefields.go index ea7a0fd008..ac722da070 100644 --- a/src/reflect/visiblefields.go +++ b/src/reflect/visiblefields.go @@ -1,7 +1,11 @@ package reflect -import "internal/reflectlite" +import ( + "internal/reflectlite" + "unsafe" +) func VisibleFields(t Type) []StructField { - return reflectlite.VisibleFields(toRawType(t)) + fields := reflectlite.VisibleFields(toRawType(t)) + return *(*[]StructField)(unsafe.Pointer(&fields)) } From bf88cdea904f63c3d7061201fad6c2b6c2fdc62c Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 8 Mar 2025 20:12:14 -0800 Subject: [PATCH 421/444] transform: cherry-pick from #4774 --- transform/rtcalls.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/transform/rtcalls.go b/transform/rtcalls.go index 8310fc9f19..3abc1d3952 100644 --- a/transform/rtcalls.go +++ b/transform/rtcalls.go @@ -117,8 +117,9 @@ func OptimizeStringEqual(mod llvm.Module) { // As of this writing, the (reflect.Type).Interface method has not yet been // implemented so this optimization is critical for the encoding/json package. func OptimizeReflectImplements(mod llvm.Module) { - implementsSignature := mod.NamedGlobal("reflect/methods.Implements(reflect.Type) bool") - if implementsSignature.IsNil() { + implementsSignature1 := mod.NamedGlobal("reflect/methods.Implements(reflect.Type) bool") + implementsSignature2 := mod.NamedGlobal("reflect/methods.Implements(internal/reflectlite.Type) bool") + if implementsSignature1.IsNil() && implementsSignature2.IsNil() { return } @@ -132,7 +133,8 @@ func OptimizeReflectImplements(mod llvm.Module) { if attr.IsNil() { continue } - if attr.GetStringValue() == "reflect/methods.Implements(reflect.Type) bool" { + val := attr.GetStringValue() + if val == "reflect/methods.Implements(reflect.Type) bool" || val == "reflect/methods.Implements(internal/reflectlite.Type) bool" { implementsFunc = fn break } From 417aa4312ebc39c6d5647675be19a898787d7451 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 8 Mar 2025 20:35:36 -0800 Subject: [PATCH 422/444] internal/reflectlite, reflect: move StringHeader and SliceHeader back to package reflect --- src/internal/reflectlite/value.go | 13 ------------ src/{internal/reflectlite => reflect}/intw.go | 2 +- .../reflectlite => reflect}/intw_avr.go | 2 +- .../reflectlite => reflect}/intw_test.go | 2 +- src/reflect/value.go | 21 +++++++++++++++++++ 5 files changed, 24 insertions(+), 16 deletions(-) rename src/{internal/reflectlite => reflect}/intw.go (92%) rename src/{internal/reflectlite => reflect}/intw_avr.go (92%) rename src/{internal/reflectlite => reflect}/intw_test.go (97%) diff --git a/src/internal/reflectlite/value.go b/src/internal/reflectlite/value.go index 0cccfe9d87..31015cbd27 100644 --- a/src/internal/reflectlite/value.go +++ b/src/internal/reflectlite/value.go @@ -1686,12 +1686,6 @@ type funcHeader struct { Code unsafe.Pointer } -type SliceHeader struct { - Data uintptr - Len intw - Cap intw -} - // Slice header that matches the underlying structure. Used for when we switch // to a precise GC, which needs to know exactly where pointers live. type sliceHeader struct { @@ -1700,11 +1694,6 @@ type sliceHeader struct { cap uintptr } -type StringHeader struct { - Data uintptr - Len intw -} - // Like sliceHeader, this type is used internally to make sure pointer and // non-pointer fields match those of actual strings. type stringHeader struct { @@ -1716,9 +1705,7 @@ type stringHeader struct { // See https://github.com/tinygo-org/tinygo/pull/4156 // and https://github.com/tinygo-org/tinygo/issues/1284. var ( - _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(SliceHeader{})]byte{} _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(sliceHeader{})]byte{} - _ [unsafe.Sizeof("")]byte = [unsafe.Sizeof(StringHeader{})]byte{} _ [unsafe.Sizeof("")]byte = [unsafe.Sizeof(stringHeader{})]byte{} ) diff --git a/src/internal/reflectlite/intw.go b/src/reflect/intw.go similarity index 92% rename from src/internal/reflectlite/intw.go rename to src/reflect/intw.go index 4dd197cefc..20fbd4341e 100644 --- a/src/internal/reflectlite/intw.go +++ b/src/reflect/intw.go @@ -1,6 +1,6 @@ //go:build !avr -package reflectlite +package reflect // intw is an integer type, used in places where an int is typically required, // except architectures where the size of an int != word size. diff --git a/src/internal/reflectlite/intw_avr.go b/src/reflect/intw_avr.go similarity index 92% rename from src/internal/reflectlite/intw_avr.go rename to src/reflect/intw_avr.go index 24b4377777..8f294eeee2 100644 --- a/src/internal/reflectlite/intw_avr.go +++ b/src/reflect/intw_avr.go @@ -1,6 +1,6 @@ //go:build avr -package reflectlite +package reflect // intw is an integer type, used in places where an int is typically required, // except architectures where the size of an int != word size. diff --git a/src/internal/reflectlite/intw_test.go b/src/reflect/intw_test.go similarity index 97% rename from src/internal/reflectlite/intw_test.go rename to src/reflect/intw_test.go index b235b88f4c..1014a9ae4e 100644 --- a/src/internal/reflectlite/intw_test.go +++ b/src/reflect/intw_test.go @@ -1,6 +1,6 @@ //go:build !avr -package reflectlite_test +package reflect_test import ( "reflect" diff --git a/src/reflect/value.go b/src/reflect/value.go index 26f3657cd0..fe41f2d184 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -226,3 +226,24 @@ func (v Value) Recv() (x Value, ok bool) { func NewAt(typ Type, p unsafe.Pointer) Value { panic("unimplemented: reflect.New()") } + +// Deprecated: Use unsafe.Slice or unsafe.SliceData instead. +type SliceHeader struct { + Data uintptr + Len intw + Cap intw +} + +// Deprecated: Use unsafe.String or unsafe.StringData instead. +type StringHeader struct { + Data uintptr + Len intw +} + +// Verify SliceHeader and StringHeader sizes. +// See https://github.com/tinygo-org/tinygo/pull/4156 +// and https://github.com/tinygo-org/tinygo/issues/1284. +var ( + _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(SliceHeader{})]byte{} + _ [unsafe.Sizeof("")]byte = [unsafe.Sizeof(StringHeader{})]byte{} +) From 05fc49a0cf9f04075d1dfa520f1b4096c5753c78 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 8 Mar 2025 20:52:17 -0800 Subject: [PATCH 423/444] internal/reflectlite: remove old reflect.go --- src/internal/reflectlite/reflect.go | 53 ----------------------------- 1 file changed, 53 deletions(-) delete mode 100644 src/internal/reflectlite/reflect.go diff --git a/src/internal/reflectlite/reflect.go b/src/internal/reflectlite/reflect.go deleted file mode 100644 index df6abd3aa7..0000000000 --- a/src/internal/reflectlite/reflect.go +++ /dev/null @@ -1,53 +0,0 @@ -//go:build never - -package reflectlite - -import "reflect" - -func Swapper(slice interface{}) func(i, j int) { - return reflect.Swapper(slice) -} - -type Kind = reflect.Kind -type Type = reflect.Type -type Value = reflect.Value - -const ( - Invalid Kind = reflect.Invalid - Bool Kind = reflect.Bool - Int Kind = reflect.Int - Int8 Kind = reflect.Int8 - Int16 Kind = reflect.Int16 - Int32 Kind = reflect.Int32 - Int64 Kind = reflect.Int64 - Uint Kind = reflect.Uint - Uint8 Kind = reflect.Uint8 - Uint16 Kind = reflect.Uint16 - Uint32 Kind = reflect.Uint32 - Uint64 Kind = reflect.Uint64 - Uintptr Kind = reflect.Uintptr - Float32 Kind = reflect.Float32 - Float64 Kind = reflect.Float64 - Complex64 Kind = reflect.Complex64 - Complex128 Kind = reflect.Complex128 - Array Kind = reflect.Array - Chan Kind = reflect.Chan - Func Kind = reflect.Func - Interface Kind = reflect.Interface - Map Kind = reflect.Map - Ptr Kind = reflect.Ptr - Slice Kind = reflect.Slice - String Kind = reflect.String - Struct Kind = reflect.Struct - UnsafePointer Kind = reflect.UnsafePointer -) - -func ValueOf(i interface{}) reflect.Value { - return reflect.ValueOf(i) -} - -func TypeOf(i interface{}) reflect.Type { - return reflect.TypeOf(i) -} - -type ValueError = reflect.ValueError From a2be2f33309b2a73b81e5e9fc93f752d84c755e7 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 8 Mar 2025 21:11:08 -0800 Subject: [PATCH 424/444] loader, iter: add shim for go1.22 and earlier --- loader/goroot.go | 4 ++++ src/iter/iter.go | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 src/iter/iter.go diff --git a/loader/goroot.go b/loader/goroot.go index 00a7124d80..3d3dee0d12 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -261,6 +261,10 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { "unique/": false, } + if goMinor <= 22 { + paths["iter]"] = false + } + if goMinor >= 19 { paths["crypto/internal/"] = true paths["crypto/internal/boring/"] = true diff --git a/src/iter/iter.go b/src/iter/iter.go new file mode 100644 index 0000000000..bbeb4e1d12 --- /dev/null +++ b/src/iter/iter.go @@ -0,0 +1,17 @@ +//go:build !go1.23 + +// Delete this file when TinyGo drops support for Go 1.22. + +package iter + +// Seq is an iterator over sequences of individual values. +// When called as seq(yield), seq calls yield(v) for each value v in the sequence, +// stopping early if yield returns false. +// See the [iter] package documentation for more details. +type Seq[V any] func(yield func(V) bool) + +// Seq2 is an iterator over sequences of pairs of values, most commonly key-value pairs. +// When called as seq(yield), seq calls yield(k, v) for each pair (k, v) in the sequence, +// stopping early if yield returns false. +// See the [iter] package documentation for more details. +type Seq2[K, V any] func(yield func(K, V) bool) From fafe80704f629a2cfe95a90c6bc9e2331045240f Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sun, 9 Mar 2025 08:51:28 -0700 Subject: [PATCH 425/444] loader, iter, reflect: use build tags for package iter and iter methods on reflect.Value --- loader/goroot.go | 4 ---- src/iter/iter.go | 17 ----------------- src/reflect/iter.go | 2 ++ 3 files changed, 2 insertions(+), 21 deletions(-) delete mode 100644 src/iter/iter.go diff --git a/loader/goroot.go b/loader/goroot.go index 3d3dee0d12..00a7124d80 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -261,10 +261,6 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { "unique/": false, } - if goMinor <= 22 { - paths["iter]"] = false - } - if goMinor >= 19 { paths["crypto/internal/"] = true paths["crypto/internal/boring/"] = true diff --git a/src/iter/iter.go b/src/iter/iter.go deleted file mode 100644 index bbeb4e1d12..0000000000 --- a/src/iter/iter.go +++ /dev/null @@ -1,17 +0,0 @@ -//go:build !go1.23 - -// Delete this file when TinyGo drops support for Go 1.22. - -package iter - -// Seq is an iterator over sequences of individual values. -// When called as seq(yield), seq calls yield(v) for each value v in the sequence, -// stopping early if yield returns false. -// See the [iter] package documentation for more details. -type Seq[V any] func(yield func(V) bool) - -// Seq2 is an iterator over sequences of pairs of values, most commonly key-value pairs. -// When called as seq(yield), seq calls yield(k, v) for each pair (k, v) in the sequence, -// stopping early if yield returns false. -// See the [iter] package documentation for more details. -type Seq2[K, V any] func(yield func(K, V) bool) diff --git a/src/reflect/iter.go b/src/reflect/iter.go index 90a9469bb5..dd0b8de209 100644 --- a/src/reflect/iter.go +++ b/src/reflect/iter.go @@ -1,3 +1,5 @@ +//go:build go1.23 + // Copyright 2024 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. From b6c3d142db79704ff1e8cf3dd8f661379436f899 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sun, 9 Mar 2025 08:51:51 -0700 Subject: [PATCH 426/444] reflect: copy reflect iter tests from upstream Go --- src/reflect/iter_test.go | 415 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 415 insertions(+) create mode 100644 src/reflect/iter_test.go diff --git a/src/reflect/iter_test.go b/src/reflect/iter_test.go new file mode 100644 index 0000000000..88a6b83a0a --- /dev/null +++ b/src/reflect/iter_test.go @@ -0,0 +1,415 @@ +//go:build go1.23 + +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package reflect_test + +import ( + "iter" + "maps" + "reflect" + . "reflect" + "testing" +) + +type N int8 + +func TestValueSeq(t *testing.T) { + m := map[string]int{ + "1": 1, + "2": 2, + "3": 3, + "4": 4, + } + c := make(chan int, 3) + for i := range 3 { + c <- i + } + close(c) + tests := []struct { + name string + val Value + check func(*testing.T, iter.Seq[Value]) + }{ + {"int", ValueOf(4), func(t *testing.T, s iter.Seq[Value]) { + i := int64(0) + for v := range s { + if v.Int() != i { + t.Fatalf("got %d, want %d", v.Int(), i) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"int8", ValueOf(int8(4)), func(t *testing.T, s iter.Seq[Value]) { + i := int8(0) + for v := range s { + if v.Interface().(int8) != i { + t.Fatalf("got %d, want %d", v.Int(), i) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"uint", ValueOf(uint64(4)), func(t *testing.T, s iter.Seq[Value]) { + i := uint64(0) + for v := range s { + if v.Uint() != i { + t.Fatalf("got %d, want %d", v.Uint(), i) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"uint8", ValueOf(uint8(4)), func(t *testing.T, s iter.Seq[Value]) { + i := uint8(0) + for v := range s { + if v.Interface().(uint8) != i { + t.Fatalf("got %d, want %d", v.Int(), i) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"*[4]int", ValueOf(&[4]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq[Value]) { + i := int64(0) + for v := range s { + if v.Int() != i { + t.Fatalf("got %d, want %d", v.Int(), i) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"[4]int", ValueOf([4]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq[Value]) { + i := int64(0) + for v := range s { + if v.Int() != i { + t.Fatalf("got %d, want %d", v.Int(), i) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"[]int", ValueOf([]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq[Value]) { + i := int64(0) + for v := range s { + if v.Int() != i { + t.Fatalf("got %d, want %d", v.Int(), i) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"string", ValueOf("12语言"), func(t *testing.T, s iter.Seq[Value]) { + i := int64(0) + indexes := []int64{0, 1, 2, 5} + for v := range s { + if v.Int() != indexes[i] { + t.Fatalf("got %d, want %d", v.Int(), indexes[i]) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"map[string]int", ValueOf(m), func(t *testing.T, s iter.Seq[Value]) { + copy := maps.Clone(m) + for v := range s { + if _, ok := copy[v.String()]; !ok { + t.Fatalf("unexpected %v", v.Interface()) + } + delete(copy, v.String()) + } + if len(copy) != 0 { + t.Fatalf("should loop four times") + } + }}, + // {"chan int", ValueOf(c), func(t *testing.T, s iter.Seq[Value]) { + // i := 0 + // m := map[int64]bool{ + // 0: false, + // 1: false, + // 2: false, + // } + // for v := range s { + // if b, ok := m[v.Int()]; !ok || b { + // t.Fatalf("unexpected %v", v.Interface()) + // } + // m[v.Int()] = true + // i++ + // } + // if i != 3 { + // t.Fatalf("should loop three times") + // } + // }}, + // {"func", ValueOf(func(yield func(int) bool) { + // for i := range 4 { + // if !yield(i) { + // return + // } + // } + // }), func(t *testing.T, s iter.Seq[Value]) { + // i := int64(0) + // for v := range s { + // if v.Int() != i { + // t.Fatalf("got %d, want %d", v.Int(), i) + // } + // i++ + // } + // if i != 4 { + // t.Fatalf("should loop four times") + // } + // }}, + // {"method", ValueOf(methodIter{}).MethodByName("Seq"), func(t *testing.T, s iter.Seq[Value]) { + // i := int64(0) + // for v := range s { + // if v.Int() != i { + // t.Fatalf("got %d, want %d", v.Int(), i) + // } + // i++ + // } + // if i != 4 { + // t.Fatalf("should loop four times") + // } + // }}, + {"type N int8", ValueOf(N(4)), func(t *testing.T, s iter.Seq[Value]) { + i := N(0) + for v := range s { + if v.Int() != int64(i) { + t.Fatalf("got %d, want %d", v.Int(), i) + } + i++ + // TODO: iteration should produce the same type + // if v.Type() != reflect.TypeOf(i) { + // t.Fatalf("got %s, want %s", v.Type(), reflect.TypeOf(i)) + // } + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + } + for _, tc := range tests { + seq := tc.val.Seq() + tc.check(t, seq) + } +} + +func TestValueSeq2(t *testing.T) { + m := map[string]int{ + "1": 1, + "2": 2, + "3": 3, + "4": 4, + } + tests := []struct { + name string + val Value + check func(*testing.T, iter.Seq2[Value, Value]) + }{ + {"*[4]int", ValueOf(&[4]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq2[Value, Value]) { + i := int64(0) + for v1, v2 := range s { + if v1.Int() != i { + t.Fatalf("got %d, want %d", v1.Int(), i) + } + i++ + if v2.Int() != i { + t.Fatalf("got %d, want %d", v2.Int(), i) + } + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"[4]int", ValueOf([4]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq2[Value, Value]) { + i := int64(0) + for v1, v2 := range s { + if v1.Int() != i { + t.Fatalf("got %d, want %d", v1.Int(), i) + } + i++ + if v2.Int() != i { + t.Fatalf("got %d, want %d", v2.Int(), i) + } + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"[]int", ValueOf([]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq2[Value, Value]) { + i := int64(0) + for v1, v2 := range s { + if v1.Int() != i { + t.Fatalf("got %d, want %d", v1.Int(), i) + } + i++ + if v2.Int() != i { + t.Fatalf("got %d, want %d", v2.Int(), i) + } + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"string", ValueOf("12语言"), func(t *testing.T, s iter.Seq2[Value, Value]) { + next, stop := iter.Pull2(s) + defer stop() + i := int64(0) + for j, s := range "12语言" { + v1, v2, ok := next() + if !ok { + t.Fatalf("should loop four times") + } + if v1.Int() != int64(j) { + t.Fatalf("got %d, want %d", v1.Int(), j) + } + if v2.Interface() != s { + t.Fatalf("got %v, want %v", v2.Interface(), s) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"map[string]int", ValueOf(m), func(t *testing.T, s iter.Seq2[Value, Value]) { + copy := maps.Clone(m) + for v1, v2 := range s { + v, ok := copy[v1.String()] + if !ok { + t.Fatalf("unexpected %v", v1.String()) + } + if v != v2.Interface() { + t.Fatalf("got %v, want %d", v2.Interface(), v) + } + delete(copy, v1.String()) + } + if len(copy) != 0 { + t.Fatalf("should loop four times") + } + }}, + // {"func", ValueOf(func(f func(int, int) bool) { + // for i := range 4 { + // f(i, i+1) + // } + // }), func(t *testing.T, s iter.Seq2[Value, Value]) { + // i := int64(0) + // for v1, v2 := range s { + // if v1.Int() != i { + // t.Fatalf("got %d, want %d", v1.Int(), i) + // } + // i++ + // if v2.Int() != i { + // t.Fatalf("got %d, want %d", v2.Int(), i) + // } + // } + // if i != 4 { + // t.Fatalf("should loop four times") + // } + // }}, + // {"method", ValueOf(methodIter2{}).MethodByName("Seq2"), func(t *testing.T, s iter.Seq2[Value, Value]) { + // i := int64(0) + // for v1, v2 := range s { + // if v1.Int() != i { + // t.Fatalf("got %d, want %d", v1.Int(), i) + // } + // i++ + // if v2.Int() != i { + // t.Fatalf("got %d, want %d", v2.Int(), i) + // } + // } + // if i != 4 { + // t.Fatalf("should loop four times") + // } + // }}, + {"[4]N", ValueOf([4]N{0, 1, 2, 3}), func(t *testing.T, s iter.Seq2[Value, Value]) { + i := N(0) + for v1, v2 := range s { + if v1.Int() != int64(i) { + t.Fatalf("got %d, want %d", v1.Int(), i) + } + if v2.Int() != int64(i) { + t.Fatalf("got %d, want %d", v2.Int(), i) + } + i++ + if v2.Type() != reflect.TypeOf(i) { + t.Fatalf("got %s, want %s", v2.Type(), reflect.TypeOf(i)) + } + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"[]N", ValueOf([]N{1, 2, 3, 4}), func(t *testing.T, s iter.Seq2[Value, Value]) { + i := N(0) + for v1, v2 := range s { + if v1.Int() != int64(i) { + t.Fatalf("got %d, want %d", v1.Int(), i) + } + i++ + if v2.Int() != int64(i) { + t.Fatalf("got %d, want %d", v2.Int(), i) + } + if v2.Type() != reflect.TypeOf(i) { + t.Fatalf("got %s, want %s", v2.Type(), reflect.TypeOf(i)) + } + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + } + for _, tc := range tests { + seq := tc.val.Seq2() + tc.check(t, seq) + } +} + +// methodIter is a type from which we can derive a method +// value that is an iter.Seq. +type methodIter struct{} + +func (methodIter) Seq(yield func(int) bool) { + for i := range 4 { + if !yield(i) { + return + } + } +} + +// For Type.CanSeq test. +func (methodIter) NonSeq(yield func(int)) {} + +// methodIter2 is a type from which we can derive a method +// value that is an iter.Seq2. +type methodIter2 struct{} + +func (methodIter2) Seq2(yield func(int, int) bool) { + for i := range 4 { + if !yield(i, i+1) { + return + } + } +} + +// For Type.CanSeq2 test. +func (methodIter2) NonSeq2(yield func(int, int)) {} From 64651115c2f9ded9a2bd0d082d035a003c1a55a2 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sun, 9 Mar 2025 09:04:47 -0700 Subject: [PATCH 427/444] reflect: Value.Seq iteration value types should match Implementation of https://github.com/golang/go/issues/71905 194696f1d1f6e5609f96d0fb0192595e7e0f5b90 --- src/reflect/iter.go | 39 ++++++++++++++++++++++++--------------- src/reflect/iter_test.go | 9 +++++---- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/reflect/iter.go b/src/reflect/iter.go index dd0b8de209..4cc2df8fd9 100644 --- a/src/reflect/iter.go +++ b/src/reflect/iter.go @@ -6,15 +6,24 @@ package reflect -import "iter" +import ( + "iter" +) func rangeNum[T int8 | int16 | int32 | int64 | int | uint8 | uint16 | uint32 | uint64 | uint | - uintptr, N int64 | uint64](v N) iter.Seq[Value] { + uintptr, N int64 | uint64](num N, t Type) iter.Seq[Value] { return func(yield func(v Value) bool) { + convert := t.PkgPath() != "" // cannot use range T(v) because no core type. - for i := T(0); i < T(v); i++ { - if !yield(ValueOf(i)) { + for i := T(0); i < T(num); i++ { + tmp := ValueOf(i) + // if the iteration value type is define by + // type T built-in type. + if convert { + tmp = tmp.Convert(t) + } + if !yield(tmp) { return } } @@ -40,27 +49,27 @@ func (v Value) Seq() iter.Seq[Value] { // } switch v.Kind() { case Int: - return rangeNum[int](v.Int()) + return rangeNum[int](v.Int(), v.Type()) case Int8: - return rangeNum[int8](v.Int()) + return rangeNum[int8](v.Int(), v.Type()) case Int16: - return rangeNum[int16](v.Int()) + return rangeNum[int16](v.Int(), v.Type()) case Int32: - return rangeNum[int32](v.Int()) + return rangeNum[int32](v.Int(), v.Type()) case Int64: - return rangeNum[int64](v.Int()) + return rangeNum[int64](v.Int(), v.Type()) case Uint: - return rangeNum[uint](v.Uint()) + return rangeNum[uint](v.Uint(), v.Type()) case Uint8: - return rangeNum[uint8](v.Uint()) + return rangeNum[uint8](v.Uint(), v.Type()) case Uint16: - return rangeNum[uint16](v.Uint()) + return rangeNum[uint16](v.Uint(), v.Type()) case Uint32: - return rangeNum[uint32](v.Uint()) + return rangeNum[uint32](v.Uint(), v.Type()) case Uint64: - return rangeNum[uint64](v.Uint()) + return rangeNum[uint64](v.Uint(), v.Type()) case Uintptr: - return rangeNum[uintptr](v.Uint()) + return rangeNum[uintptr](v.Uint(), v.Type()) case Pointer: if v.Elem().Kind() != Array { break diff --git a/src/reflect/iter_test.go b/src/reflect/iter_test.go index 88a6b83a0a..48c62c477a 100644 --- a/src/reflect/iter_test.go +++ b/src/reflect/iter_test.go @@ -197,10 +197,11 @@ func TestValueSeq(t *testing.T) { t.Fatalf("got %d, want %d", v.Int(), i) } i++ - // TODO: iteration should produce the same type - // if v.Type() != reflect.TypeOf(i) { - // t.Fatalf("got %s, want %s", v.Type(), reflect.TypeOf(i)) - // } + if v.Type() != reflect.TypeOf(i) { + j := ValueOf(i) + t.Logf("ValueOf(j): %s", j.Type()) + t.Fatalf("got %s, want %s", v.Type(), reflect.TypeOf(i)) + } } if i != 4 { t.Fatalf("should loop four times") From c2af1d183ea3b614bba293c82bdd1234f56bac00 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Mon, 10 Mar 2025 12:22:16 -0700 Subject: [PATCH 428/444] reflect: panic on Type.CanSeq[2] instead of returning false PR feedback. --- src/reflect/type.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/reflect/type.go b/src/reflect/type.go index 5116108dc7..a162aa7973 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -373,8 +373,9 @@ func (t *rawType) CanSeq() bool { case Int8, Int16, Int32, Int64, Int, Uint8, Uint16, Uint32, Uint64, Uint, Uintptr, Array, Slice, Chan, String, Map: return true case Func: - return false // TODO: implement canRangeFunc - // return canRangeFunc(&t.) + // TODO: implement canRangeFunc + // return canRangeFunc(t) + panic("unimplemented: (reflect.Type).CanSeq() for functions") case Pointer: return t.Elem().Kind() == Array } @@ -386,8 +387,9 @@ func (t *rawType) CanSeq2() bool { case Array, Slice, String, Map: return true case Func: - return false // TODO: implement canRangeFunc2 - // return canRangeFunc2(&t.t) + // TODO: implement canRangeFunc2 + // return canRangeFunc2(t) + panic("unimplemented: (reflect.Type).CanSeq2() for functions") case Pointer: return t.Elem().Kind() == Array } From 24eed9e376a7f49928e8d3c968c5610550d1890c Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Mon, 17 Mar 2025 07:42:09 -0700 Subject: [PATCH 429/444] reflect: remove strconv.go --- src/reflect/strconv.go | 347 ----------------------------------------- 1 file changed, 347 deletions(-) delete mode 100644 src/reflect/strconv.go diff --git a/src/reflect/strconv.go b/src/reflect/strconv.go deleted file mode 100644 index 2d8131c9df..0000000000 --- a/src/reflect/strconv.go +++ /dev/null @@ -1,347 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package reflect - -import ( - "unicode/utf8" -) - -// errSyntax indicates that a value does not have the right syntax for the target type. -var errSyntax = badSyntax{} - -type badSyntax struct{} - -func (badSyntax) Error() string { - return "invalid syntax" -} - -func unhex(b byte) (v rune, ok bool) { - c := rune(b) - switch { - case '0' <= c && c <= '9': - return c - '0', true - case 'a' <= c && c <= 'f': - return c - 'a' + 10, true - case 'A' <= c && c <= 'F': - return c - 'A' + 10, true - } - return -} - -const ( - lowerhex = "0123456789abcef" -) - -// unquoteChar decodes the first character or byte in the escaped string -// or character literal represented by the string s. -// It returns four values: -// -// 1. value, the decoded Unicode code point or byte value; -// 2. multibyte, a boolean indicating whether the decoded character requires a multibyte UTF-8 representation; -// 3. tail, the remainder of the string after the character; and -// 4. an error that will be nil if the character is syntactically valid. -// -// The second argument, quote, specifies the type of literal being parsed -// and therefore which escaped quote character is permitted. -// If set to a single quote, it permits the sequence \' and disallows unescaped '. -// If set to a double quote, it permits \" and disallows unescaped ". -// If set to zero, it does not permit either escape and allows both quote characters to appear unescaped. -func unquoteChar(s string, quote byte) (value rune, multibyte bool, tail string, err error) { - // easy cases - if len(s) == 0 { - err = errSyntax - return - } - switch c := s[0]; { - case c == quote && (quote == '\'' || quote == '"'): - err = errSyntax - return - case c >= utf8.RuneSelf: - r, size := utf8.DecodeRuneInString(s) - return r, true, s[size:], nil - case c != '\\': - return rune(s[0]), false, s[1:], nil - } - - // hard case: c is backslash - if len(s) <= 1 { - err = errSyntax - return - } - c := s[1] - s = s[2:] - - switch c { - case 'a': - value = '\a' - case 'b': - value = '\b' - case 'f': - value = '\f' - case 'n': - value = '\n' - case 'r': - value = '\r' - case 't': - value = '\t' - case 'v': - value = '\v' - case 'x', 'u', 'U': - n := 0 - switch c { - case 'x': - n = 2 - case 'u': - n = 4 - case 'U': - n = 8 - } - var v rune - if len(s) < n { - err = errSyntax - return - } - for j := 0; j < n; j++ { - x, ok := unhex(s[j]) - if !ok { - err = errSyntax - return - } - v = v<<4 | x - } - s = s[n:] - if c == 'x' { - // single-byte string, possibly not UTF-8 - value = v - break - } - if v > utf8.MaxRune { - err = errSyntax - return - } - value = v - multibyte = true - case '0', '1', '2', '3', '4', '5', '6', '7': - v := rune(c) - '0' - if len(s) < 2 { - err = errSyntax - return - } - for j := 0; j < 2; j++ { // one digit already; two more - x := rune(s[j]) - '0' - if x < 0 || x > 7 { - err = errSyntax - return - } - v = (v << 3) | x - } - s = s[2:] - if v > 255 { - err = errSyntax - return - } - value = v - case '\\': - value = '\\' - case '\'', '"': - if c != quote { - err = errSyntax - return - } - value = rune(c) - default: - err = errSyntax - return - } - tail = s - return -} - -// unquote interprets s as a single-quoted, double-quoted, -// or backquoted Go string literal, returning the string value -// that s quotes. (If s is single-quoted, it would be a Go -// character literal; unquote returns the corresponding -// one-character string.) -func unquote(s string) (string, error) { - n := len(s) - if n < 2 { - return "", errSyntax - } - quote := s[0] - if quote != s[n-1] { - return "", errSyntax - } - s = s[1 : n-1] - - if quote == '`' { - if contains(s, '`') { - return "", errSyntax - } - if contains(s, '\r') { - // -1 because we know there is at least one \r to remove. - buf := make([]byte, 0, len(s)-1) - for i := 0; i < len(s); i++ { - if s[i] != '\r' { - buf = append(buf, s[i]) - } - } - return string(buf), nil - } - return s, nil - } - if quote != '"' && quote != '\'' { - return "", errSyntax - } - if contains(s, '\n') { - return "", errSyntax - } - - // Is it trivial? Avoid allocation. - if !contains(s, '\\') && !contains(s, quote) { - switch quote { - case '"': - if utf8.ValidString(s) { - return s, nil - } - case '\'': - r, size := utf8.DecodeRuneInString(s) - if size == len(s) && (r != utf8.RuneError || size != 1) { - return s, nil - } - } - } - - var runeTmp [utf8.UTFMax]byte - buf := make([]byte, 0, 3*len(s)/2) // Try to avoid more allocations. - for len(s) > 0 { - c, multibyte, ss, err := unquoteChar(s, quote) - if err != nil { - return "", err - } - s = ss - if c < utf8.RuneSelf || !multibyte { - buf = append(buf, byte(c)) - } else { - n := utf8.EncodeRune(runeTmp[:], c) - buf = append(buf, runeTmp[:n]...) - } - if quote == '\'' && len(s) != 0 { - // single-quoted must be single character - return "", errSyntax - } - } - return string(buf), nil -} - -func quote(s string) string { - buf := make([]byte, 0, 3*len(s)/2) - const quote = '"' - - buf = append(buf, quote) - for width := 0; len(s) > 0; s = s[width:] { - r := rune(s[0]) - width = 1 - if r >= utf8.RuneSelf { - r, width = utf8.DecodeRuneInString(s) - } - if width == 1 && r == utf8.RuneError { - buf = append(buf, `\x`...) - buf = append(buf, lowerhex[s[0]>>4]) - buf = append(buf, lowerhex[s[0]&0xF]) - continue - } - buf = appendEscapedRune(buf, r) - } - buf = append(buf, quote) - return string(buf) -} - -func appendEscapedRune(buf []byte, r rune) []byte { - - const quote = '"' - - var runeTmp [utf8.UTFMax]byte - if r == rune(quote) || r == '\\' { // always backslashed - buf = append(buf, '\\') - buf = append(buf, byte(r)) - return buf - } - if isPrint(r) { - n := utf8.EncodeRune(runeTmp[:], r) - buf = append(buf, runeTmp[:n]...) - return buf - } - switch r { - case '\a': - buf = append(buf, `\a`...) - case '\b': - buf = append(buf, `\b`...) - case '\f': - buf = append(buf, `\f`...) - case '\n': - buf = append(buf, `\n`...) - case '\r': - buf = append(buf, `\r`...) - case '\t': - buf = append(buf, `\t`...) - case '\v': - buf = append(buf, `\v`...) - default: - switch { - case r < ' ' || r == 0x7f: - buf = append(buf, `\x`...) - buf = append(buf, lowerhex[byte(r)>>4]) - buf = append(buf, lowerhex[byte(r)&0xF]) - case !utf8.ValidRune(r): - r = 0xFFFD - fallthrough - case r < 0x10000: - buf = append(buf, `\u`...) - for s := 12; s >= 0; s -= 4 { - buf = append(buf, lowerhex[r>>uint(s)&0xF]) - } - default: - buf = append(buf, `\U`...) - for s := 28; s >= 0; s -= 4 { - buf = append(buf, lowerhex[r>>uint(s)&0xF]) - } - } - } - return buf -} - -// This is only used for struct tags. Assume -func isPrint(r rune) bool { - if r <= 0xFF { - if 0x20 <= r && r <= 0x7E { - // All the ASCII is printable from space through DEL-1. - return true - } - if 0xA1 <= r && r <= 0xFF { - // Similarly for ¡ through ÿ... - return r != 0xAD // ...except for the bizarre soft hyphen. - } - return false - } - - // TinyGo: Skip all other unicode processing - return false -} - -// contains reports whether the string contains the byte c. -func contains(s string, c byte) bool { - return indexByteString(s, c) != -1 -} - -// Index finds the index of the first instance of the specified byte in the string. -// If the byte is not found, this returns -1. -func indexByteString(s string, c byte) int { - for i := 0; i < len(s); i++ { - if s[i] == c { - return i - } - } - return -1 -} From d95c1b59826c84f001b733fc4535a6200fd6f1b0 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Mon, 17 Mar 2025 08:03:00 -0700 Subject: [PATCH 430/444] reflect: remove unused go:linkname functions --- src/reflect/value.go | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/reflect/value.go b/src/reflect/value.go index fe41f2d184..026ea02060 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -13,12 +13,6 @@ func Indirect(v Value) Value { return Value{reflectlite.Indirect(v.Value)} } -//go:linkname composeInterface runtime.composeInterface -func composeInterface(unsafe.Pointer, unsafe.Pointer) interface{} - -//go:linkname decomposeInterface runtime.decomposeInterface -func decomposeInterface(i interface{}) (unsafe.Pointer, unsafe.Pointer) - func ValueOf(i interface{}) Value { return Value{reflectlite.ValueOf(i)} } @@ -61,12 +55,6 @@ func (v Value) MapIndex(key Value) Value { return Value{v.Value.MapIndex(key.Value)} } -//go:linkname hashmapNewIterator runtime.hashmapNewIterator -func hashmapNewIterator() unsafe.Pointer - -//go:linkname hashmapNext runtime.hashmapNext -func hashmapNext(m unsafe.Pointer, it unsafe.Pointer, key, value unsafe.Pointer) bool - func (v Value) MapRange() *MapIter { return (*MapIter)(v.Value.MapRange()) } @@ -97,9 +85,6 @@ func (v Value) Convert(t Type) Value { return Value{v.Value.Convert(toRawType(t))} } -//go:linkname slicePanic runtime.slicePanic -func slicePanic() - func MakeSlice(typ Type, len, cap int) Value { return Value{reflectlite.MakeSlice(toRawType(typ), len, cap)} } @@ -158,9 +143,6 @@ func (v Value) FieldByNameFunc(match func(string) bool) Value { return Value{v.Value.FieldByNameFunc(match)} } -//go:linkname hashmapMake runtime.hashmapMake -func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) unsafe.Pointer - type SelectDir int const ( From 6a25fd494e5c25d365e203cc2a2d6ead55ef50c0 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Mon, 17 Mar 2025 08:34:49 -0700 Subject: [PATCH 431/444] reflect, internal/reflectlite: add Value.SetIter{Key,Value} and MapIter.Reset Fixes #4790. Depends on #4787 (merge that first). --- src/internal/reflectlite/value.go | 42 ++++++++++++++++++++----------- src/reflect/all_test.go | 8 +++--- src/reflect/value.go | 12 +++++++++ 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/internal/reflectlite/value.go b/src/internal/reflectlite/value.go index 31015cbd27..22a6628562 100644 --- a/src/internal/reflectlite/value.go +++ b/src/internal/reflectlite/value.go @@ -1102,20 +1102,9 @@ func hashmapNewIterator() unsafe.Pointer func hashmapNext(m unsafe.Pointer, it unsafe.Pointer, key, value unsafe.Pointer) bool func (v Value) MapRange() *MapIter { - if v.Kind() != Map { - panic(&ValueError{Method: "MapRange", Kind: v.Kind()}) - } - - keyType := v.typecode.key() - - keyTypeIsEmptyInterface := keyType.Kind() == Interface && keyType.NumMethod() == 0 - shouldUnpackInterface := !keyTypeIsEmptyInterface && keyType.Kind() != String && !keyType.isBinary() - - return &MapIter{ - m: v, - it: hashmapNewIterator(), - unpackKeyInterface: shouldUnpackInterface, - } + iter := &MapIter{} + iter.Reset(v) + return iter } type MapIter struct { @@ -1142,6 +1131,10 @@ func (it *MapIter) Key() Value { return it.key.Elem() } +func (v Value) SetIterKey(iter *MapIter) { + v.Set(iter.Key()) +} + func (it *MapIter) Value() Value { if !it.valid { panic("reflect.MapIter.Value called on invalid iterator") @@ -1150,6 +1143,10 @@ func (it *MapIter) Value() Value { return it.val.Elem() } +func (v Value) SetIterValue(iter *MapIter) { + v.Set(iter.Value()) +} + func (it *MapIter) Next() bool { it.key = New(it.m.typecode.Key()) it.val = New(it.m.typecode.Elem()) @@ -1158,6 +1155,23 @@ func (it *MapIter) Next() bool { return it.valid } +func (iter *MapIter) Reset(v Value) { + if v.Kind() != Map { + panic(&ValueError{Method: "MapRange", Kind: v.Kind()}) + } + + keyType := v.typecode.key() + + keyTypeIsEmptyInterface := keyType.Kind() == Interface && keyType.NumMethod() == 0 + shouldUnpackInterface := !keyTypeIsEmptyInterface && keyType.Kind() != String && !keyType.isBinary() + + *iter = MapIter{ + m: v, + it: hashmapNewIterator(), + unpackKeyInterface: shouldUnpackInterface, + } +} + func (v Value) Set(x Value) { v.checkAddressable() v.checkRO() diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index 436bc00341..b009c325a8 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -381,8 +381,6 @@ func TestSetValue(t *testing.T) { } } -/* - func TestMapIterSet(t *testing.T) { m := make(map[string]any, len(valueTests)) for _, tt := range valueTests { @@ -430,8 +428,6 @@ func TestMapIterSet(t *testing.T) { } } -*/ - func TestCanIntUintFloatComplex(t *testing.T) { type integer int type uinteger uint @@ -7955,6 +7951,8 @@ func TestConvertibleTo(t *testing.T) { } } +*/ + func TestSetIter(t *testing.T) { data := map[string]int{ "foo": 1, @@ -8044,6 +8042,8 @@ func TestSetIter(t *testing.T) { } } +/* + func TestMethodCallValueCodePtr(t *testing.T) { m := ValueOf(Point{}).Method(1) want := MethodValueCallCodePtr() diff --git a/src/reflect/value.go b/src/reflect/value.go index 026ea02060..5d8c25f2d1 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -65,14 +65,26 @@ func (it *MapIter) Key() Value { return Value{((*reflectlite.MapIter)(it)).Key()} } +func (v Value) SetIterKey(iter *MapIter) { + v.Value.SetIterKey((*reflectlite.MapIter)(iter)) +} + func (it *MapIter) Value() Value { return Value{((*reflectlite.MapIter)(it)).Value()} } +func (v Value) SetIterValue(iter *MapIter) { + v.Value.SetIterValue((*reflectlite.MapIter)(iter)) +} + func (it *MapIter) Next() bool { return ((*reflectlite.MapIter)(it)).Next() } +func (iter *MapIter) Reset(v Value) { + (*reflectlite.MapIter)(iter).Reset(v.Value) +} + func (v Value) Set(x Value) { v.Value.Set(x.Value) } From 5282b4efac70466bf372676ae2f99fa065978490 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 17 Mar 2025 14:26:48 +0100 Subject: [PATCH 432/444] runtime: remove unused file func.go This was used in the past, but we don't use it anymore. --- compiler/llvm.go | 7 ------- interp/memory.go | 13 ------------- src/runtime/func.go | 28 ---------------------------- 3 files changed, 48 deletions(-) delete mode 100644 src/runtime/func.go diff --git a/compiler/llvm.go b/compiler/llvm.go index 59aaee8fdd..139c5a1cd8 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -371,13 +371,6 @@ func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.In return big.NewInt(1) case llvm.StructTypeKind: ptrs := big.NewInt(0) - if typ.StructName() == "runtime.funcValue" { - // Hack: the type runtime.funcValue contains an 'id' field which is - // of type uintptr, but before the LowerFuncValues pass it actually - // contains a pointer (ptrtoint) to a global. This trips up the - // interp package. Therefore, make the id field a pointer for now. - typ = c.ctx.StructType([]llvm.Type{c.dataPtrType, c.dataPtrType}, false) - } for i, subtyp := range typ.StructElementTypes() { subptrs := c.getPointerBitmap(subtyp, pos) if subptrs.BitLen() == 0 { diff --git a/interp/memory.go b/interp/memory.go index ba224f9d59..2812cd01c2 100644 --- a/interp/memory.go +++ b/interp/memory.go @@ -824,19 +824,6 @@ func (v rawValue) rawLLVMValue(mem *memoryView) (llvm.Value, error) { if err != nil { return llvm.Value{}, err } - if !field.IsAGlobalVariable().IsNil() { - elementType := field.GlobalValueType() - if elementType.TypeKind() == llvm.StructTypeKind { - // There are some special pointer types that should be used - // as a ptrtoint, so that they can be used in certain - // optimizations. - name := elementType.StructName() - if name == "runtime.funcValueWithSignature" { - uintptrType := ctx.IntType(int(mem.r.pointerSize) * 8) - field = llvm.ConstPtrToInt(field, uintptrType) - } - } - } structFields = append(structFields, field) i += mem.r.pointerSize continue diff --git a/src/runtime/func.go b/src/runtime/func.go deleted file mode 100644 index 879424b5d7..0000000000 --- a/src/runtime/func.go +++ /dev/null @@ -1,28 +0,0 @@ -package runtime - -// This file implements some data types that may be useful for some -// implementations of func values. - -import ( - "unsafe" -) - -// funcValue is the underlying type of func values, depending on which func -// value representation was used. -type funcValue struct { - context unsafe.Pointer // function context, for closures and bound methods - id uintptr // ptrtoint of *funcValueWithSignature before lowering, opaque index (non-0) after lowering -} - -// funcValueWithSignature is used before the func lowering pass. -type funcValueWithSignature struct { - funcPtr uintptr // ptrtoint of the actual function pointer - signature *uint8 // external *i8 with a name identifying the function signature -} - -// getFuncPtr is a dummy function that may be used if the func lowering pass is -// not used. It is generally too slow but may be a useful fallback to debug the -// func lowering pass. -func getFuncPtr(val funcValue, signature *uint8) uintptr { - return (*funcValueWithSignature)(unsafe.Pointer(val.id)).funcPtr -} From a4cbe3326ec37313bfd92c01c3dbf313cae4e2b8 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 5 Aug 2024 16:12:48 +0200 Subject: [PATCH 433/444] runtime: only allocate heap memory when needed For example, with -gc=none and -gc=leaking, no heap needs to be allocated when initializing the runtime. And some GCs (like -gc=custom) are responsible for allocating the heap themselves. --- src/runtime/gc_blocks.go | 1 + src/runtime/gc_custom.go | 2 ++ src/runtime/gc_leaking.go | 2 ++ src/runtime/gc_none.go | 2 ++ src/runtime/runtime_unix.go | 7 +++++-- 5 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/runtime/gc_blocks.go b/src/runtime/gc_blocks.go index d58bfd92a2..6d32863649 100644 --- a/src/runtime/gc_blocks.go +++ b/src/runtime/gc_blocks.go @@ -37,6 +37,7 @@ import ( ) const gcDebug = false +const needsStaticHeap = true // Some globals + constants for the entire GC. diff --git a/src/runtime/gc_custom.go b/src/runtime/gc_custom.go index a34b7dce69..0125f1688b 100644 --- a/src/runtime/gc_custom.go +++ b/src/runtime/gc_custom.go @@ -36,6 +36,8 @@ import ( "unsafe" ) +const needsStaticHeap = false + // initHeap is called when the heap is first initialized at program start. func initHeap() diff --git a/src/runtime/gc_leaking.go b/src/runtime/gc_leaking.go index 9e84dc2a53..bdc7efe810 100644 --- a/src/runtime/gc_leaking.go +++ b/src/runtime/gc_leaking.go @@ -11,6 +11,8 @@ import ( "unsafe" ) +const needsStaticHeap = true + // Ever-incrementing pointer: no memory is freed. var heapptr uintptr diff --git a/src/runtime/gc_none.go b/src/runtime/gc_none.go index 43d3c4904b..173c1f6439 100644 --- a/src/runtime/gc_none.go +++ b/src/runtime/gc_none.go @@ -10,6 +10,8 @@ import ( "unsafe" ) +const needsStaticHeap = false + var gcTotalAlloc uint64 // for runtime.MemStats var gcMallocs uint64 var gcFrees uint64 diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index 08e3e74269..6add9157d3 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -79,7 +79,10 @@ var stackTop uintptr // //export main func main(argc int32, argv *unsafe.Pointer) int { - preinit() + if needsStaticHeap { + // Allocate area for the heap if the GC needs it. + allocateHeap() + } // Store argc and argv for later use. main_argc = argc @@ -298,7 +301,7 @@ var heapMaxSize uintptr var heapStart, heapEnd uintptr -func preinit() { +func allocateHeap() { // Allocate a large chunk of virtual memory. Because it is virtual, it won't // really be allocated in RAM. Memory will only be allocated when it is // first touched. From 3a7c25f9878723beb927225523905596a7bf5af9 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 13 Aug 2023 23:32:45 +0200 Subject: [PATCH 434/444] all: add the Boehm-Demers-Weiser GC on Linux This adds support for the well-known Boehm GC. It's significantly faster than our own naive GC and could be used as an alternative on bigger systems. In the future, this GC might also be supported on WebAssembly with some extra work. Right now it's Linux only (though Windows/MacOS shouldn't be too difficult to add). --- .github/workflows/nix.yml | 4 +- .gitmodules | 3 + GNUmakefile | 5 ++ builder/bdwgc.go | 71 +++++++++++++++ builder/build.go | 29 ++++-- builder/library.go | 25 ++++-- builder/musl.go | 6 ++ compileopts/config.go | 82 ++++++++++------- compileopts/options.go | 2 +- compileopts/options_test.go | 2 +- compileopts/target.go | 4 +- lib/bdwgc | 1 + src/runtime/gc_blocks.go | 6 ++ src/runtime/gc_boehm.go | 172 ++++++++++++++++++++++++++++++++++++ src/runtime/gc_globals.go | 2 +- src/runtime/gc_stack_raw.go | 5 +- 16 files changed, 364 insertions(+), 55 deletions(-) create mode 100644 builder/bdwgc.go create mode 160000 lib/bdwgc create mode 100644 src/runtime/gc_boehm.go diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 2f24df54a8..b19538c4d9 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -22,9 +22,9 @@ jobs: run: sudo apt-get remove llvm-18 - name: Checkout uses: actions/checkout@v4 - - name: Pull musl + - name: Pull musl, bdwgc run: | - git submodule update --init lib/musl + git submodule update --init lib/musl lib/bdwgc - name: Restore LLVM source cache uses: actions/cache/restore@v4 id: cache-llvm-source diff --git a/.gitmodules b/.gitmodules index 91bd14a7d7..97689eff82 100644 --- a/.gitmodules +++ b/.gitmodules @@ -39,3 +39,6 @@ [submodule "lib/wasi-cli"] path = lib/wasi-cli url = https://github.com/WebAssembly/wasi-cli +[submodule "lib/bdwgc"] + path = lib/bdwgc + url = https://github.com/ivmai/bdwgc.git diff --git a/GNUmakefile b/GNUmakefile index bcbc21b263..8a25e1df99 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -940,6 +940,7 @@ wasmtest: build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN)),,binaryen) @mkdir -p build/release/tinygo/bin + @mkdir -p build/release/tinygo/lib/bdwgc @mkdir -p build/release/tinygo/lib/clang/include @mkdir -p build/release/tinygo/lib/CMSIS/CMSIS @mkdir -p build/release/tinygo/lib/macos-minimal-sdk @@ -961,6 +962,7 @@ build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN ifneq ($(USE_SYSTEM_BINARYEN),1) @cp -p build/wasm-opt$(EXE) build/release/tinygo/bin endif + @cp -rp lib/bdwgc/* build/release/tinygo/lib/bdwgc @cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include @cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS @cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS @@ -974,6 +976,7 @@ endif @cp -rp lib/musl/crt/crt1.c build/release/tinygo/lib/musl/crt @cp -rp lib/musl/COPYRIGHT build/release/tinygo/lib/musl @cp -rp lib/musl/include build/release/tinygo/lib/musl + @cp -rp lib/musl/src/ctype build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/env build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/errno build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/exit build/release/tinygo/lib/musl/src @@ -988,8 +991,10 @@ endif @cp -rp lib/musl/src/math build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/misc build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/multibyte build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/sched build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/signal build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/stdio build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/stdlib build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/string build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/thread build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/time build/release/tinygo/lib/musl/src diff --git a/builder/bdwgc.go b/builder/bdwgc.go new file mode 100644 index 0000000000..c7c797636e --- /dev/null +++ b/builder/bdwgc.go @@ -0,0 +1,71 @@ +package builder + +// The well-known conservative Boehm-Demers-Weiser GC. +// This file provides a way to compile this GC for use with TinyGo. + +import ( + "path/filepath" + + "github.com/tinygo-org/tinygo/goenv" +) + +var BoehmGC = Library{ + name: "bdwgc", + cflags: func(target, headerPath string) []string { + libdir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/bdwgc") + return []string{ + // use a modern environment + "-DUSE_MMAP", // mmap is available + "-DUSE_MUNMAP", // return memory to the OS using munmap + "-DGC_BUILTIN_ATOMIC", // use compiler intrinsics for atomic operations + "-DNO_EXECUTE_PERMISSION", // don't make the heap executable + + // specific flags for TinyGo + "-DALL_INTERIOR_POINTERS", // scan interior pointers (needed for Go) + "-DIGNORE_DYNAMIC_LOADING", // we don't support dynamic loading at the moment + "-DNO_GETCONTEXT", // musl doesn't support getcontext() + + // Special flag to work around the lack of __data_start in ld.lld. + // TODO: try to fix this in LLVM/lld directly so we don't have to + // work around it anymore. + "-DGC_DONT_REGISTER_MAIN_STATIC_DATA", + + // Do not scan the stack. We have our own mechanism to do this. + "-DSTACK_NOT_SCANNED", + + // Assertions can be enabled while debugging GC issues. + //"-DGC_ASSERTIONS", + + // Threading is not yet supported, so these are disabled. + //"-DGC_THREADS", + //"-DTHREAD_LOCAL_ALLOC", + + "-I" + libdir + "/include", + } + }, + sourceDir: func() string { + return filepath.Join(goenv.Get("TINYGOROOT"), "lib/bdwgc") + }, + librarySources: func(target string) ([]string, error) { + return []string{ + "allchblk.c", + "alloc.c", + "blacklst.c", + "dbg_mlc.c", + "dyn_load.c", + "finalize.c", + "headers.c", + "mach_dep.c", + "malloc.c", + "mark.c", + "mark_rts.c", + "misc.c", + "new_hblk.c", + "obj_map.c", + "os_dep.c", + "pthread_stop_world.c", + "pthread_support.c", + "reclaim.c", + }, nil + }, +} diff --git a/builder/build.go b/builder/build.go index c2c712456e..9d73a03664 100644 --- a/builder/build.go +++ b/builder/build.go @@ -147,20 +147,22 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe // the libc needs them. root := goenv.Get("TINYGOROOT") var libcDependencies []*compileJob + var libcJob *compileJob switch config.Target.Libc { case "darwin-libSystem": job := makeDarwinLibSystemJob(config, tmpdir) libcDependencies = append(libcDependencies, job) case "musl": - job, unlock, err := libMusl.load(config, tmpdir) + var unlock func() + libcJob, unlock, err = libMusl.load(config, tmpdir, nil) if err != nil { return BuildResult{}, err } defer unlock() - libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(job.result), "crt1.o"))) - libcDependencies = append(libcDependencies, job) + libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(libcJob.result), "crt1.o"))) + libcDependencies = append(libcDependencies, libcJob) case "picolibc": - libcJob, unlock, err := libPicolibc.load(config, tmpdir) + libcJob, unlock, err := libPicolibc.load(config, tmpdir, nil) if err != nil { return BuildResult{}, err } @@ -173,14 +175,14 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } libcDependencies = append(libcDependencies, dummyCompileJob(path)) case "wasmbuiltins": - libcJob, unlock, err := libWasmBuiltins.load(config, tmpdir) + libcJob, unlock, err := libWasmBuiltins.load(config, tmpdir, nil) if err != nil { return BuildResult{}, err } defer unlock() libcDependencies = append(libcDependencies, libcJob) case "mingw-w64": - job, unlock, err := libMinGW.load(config, tmpdir) + job, unlock, err := libMinGW.load(config, tmpdir, nil) if err != nil { return BuildResult{}, err } @@ -701,7 +703,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe // Add compiler-rt dependency if needed. Usually this is a simple load from // a cache. if config.Target.RTLib == "compiler-rt" { - job, unlock, err := libCompilerRT.load(config, tmpdir) + job, unlock, err := libCompilerRT.load(config, tmpdir, nil) if err != nil { return result, err } @@ -709,6 +711,19 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe linkerDependencies = append(linkerDependencies, job) } + // The Boehm collector is stored in a separate C library. + if config.GC() == "boehm" { + if libcJob == nil { + return BuildResult{}, fmt.Errorf("boehm GC isn't supported with libc %s", config.Target.Libc) + } + job, unlock, err := BoehmGC.load(config, tmpdir, libcJob) + if err != nil { + return BuildResult{}, err + } + defer unlock() + linkerDependencies = append(linkerDependencies, job) + } + // Add jobs to compile extra files. These files are in C or assembly and // contain things like the interrupt vector table and low level operations // such as stack switching. diff --git a/builder/library.go b/builder/library.go index c79f7ce3f3..1b6afe2fcd 100644 --- a/builder/library.go +++ b/builder/library.go @@ -43,7 +43,11 @@ type Library struct { // output archive file, it is expected to be removed after use. // As a side effect, this call creates the library header files if they didn't // exist yet. -func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJob, abortLock func(), err error) { +// The provided libc job (if not null) will cause this libc to be added as a +// dependency for all C compiler jobs, and adds libc headers for the given +// target config. In other words, pass this libc if the library needs a libc to +// compile. +func (l *Library) load(config *compileopts.Config, tmpdir string, libc *compileJob) (job *compileJob, abortLock func(), err error) { outdir, precompiled := config.LibcPath(l.name) archiveFilePath := filepath.Join(outdir, "lib.a") if precompiled { @@ -181,6 +185,9 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ args = append(args, "-mfpu=vfpv2") } } + if libc != nil { + args = append(args, config.LibcCFlags()...) + } var once sync.Once @@ -233,7 +240,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ objpath := filepath.Join(dir, cleanpath+".o") os.MkdirAll(filepath.Dir(objpath), 0o777) objs = append(objs, objpath) - job.dependencies = append(job.dependencies, &compileJob{ + objfile := &compileJob{ description: "compile " + srcpath, run: func(*compileJob) error { var compileArgs []string @@ -248,7 +255,11 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ } return nil }, - }) + } + if libc != nil { + objfile.dependencies = append(objfile.dependencies, libc) + } + job.dependencies = append(job.dependencies, objfile) } // Create crt1.o job, if needed. @@ -257,7 +268,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ // won't make much of a difference in speed). if l.crt1Source != "" { srcpath := filepath.Join(sourceDir, l.crt1Source) - job.dependencies = append(job.dependencies, &compileJob{ + crt1Job := &compileJob{ description: "compile " + srcpath, run: func(*compileJob) error { var compileArgs []string @@ -277,7 +288,11 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ } return os.Rename(tmpfile.Name(), filepath.Join(outdir, "crt1.o")) }, - }) + } + if libc != nil { + crt1Job.dependencies = append(crt1Job.dependencies, libc) + } + job.dependencies = append(job.dependencies, crt1Job) } ok = true diff --git a/builder/musl.go b/builder/musl.go index 3c79c7c43a..dc03be46f7 100644 --- a/builder/musl.go +++ b/builder/musl.go @@ -113,26 +113,32 @@ var libMusl = Library{ librarySources: func(target string) ([]string, error) { arch := compileopts.MuslArchitecture(target) globs := []string{ + "ctype/*.c", "env/*.c", "errno/*.c", "exit/*.c", "fcntl/*.c", "internal/defsysinfo.c", + "internal/intscan.c", "internal/libc.c", + "internal/shgetc.c", "internal/syscall_ret.c", "internal/vdso.c", "legacy/*.c", "locale/*.c", "linux/*.c", + "locale/*.c", "malloc/*.c", "malloc/mallocng/*.c", "mman/*.c", "math/*.c", "misc/*.c", "multibyte/*.c", + "sched/*.c", "signal/" + arch + "/*.s", "signal/*.c", "stdio/*.c", + "stdlib/*.c", "string/*.c", "thread/" + arch + "/*.s", "thread/*.c", diff --git a/compileopts/config.go b/compileopts/config.go index ee5c34537c..a9eb235ad1 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -261,6 +261,10 @@ func (c *Config) LibcPath(name string) (path string, precompiled bool) { if c.Target.SoftFloat { archname += "-softfloat" } + if name == "bdwgc" { + // Boehm GC is compiled against a particular libc. + archname += "-" + c.Target.Libc + } // Try to load a precompiled library. precompiledDir := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", archname, name) @@ -315,83 +319,93 @@ func (c *Config) CFlags(libclang bool) []string { "-resource-dir="+resourceDir, ) } + cflags = append(cflags, c.LibcCFlags()...) + // Always emit debug information. It is optionally stripped at link time. + cflags = append(cflags, "-gdwarf-4") + // Use the same optimization level as TinyGo. + cflags = append(cflags, "-O"+c.Options.Opt) + // Set the LLVM target triple. + cflags = append(cflags, "--target="+c.Triple()) + // Set the -mcpu (or similar) flag. + if c.Target.CPU != "" { + if c.GOARCH() == "amd64" || c.GOARCH() == "386" { + // x86 prefers the -march flag (-mcpu is deprecated there). + cflags = append(cflags, "-march="+c.Target.CPU) + } else if strings.HasPrefix(c.Triple(), "avr") { + // AVR MCUs use -mmcu instead of -mcpu. + cflags = append(cflags, "-mmcu="+c.Target.CPU) + } else { + // The rest just uses -mcpu. + cflags = append(cflags, "-mcpu="+c.Target.CPU) + } + } + // Set the -mabi flag, if needed. + if c.ABI() != "" { + cflags = append(cflags, "-mabi="+c.ABI()) + } + return cflags +} + +// LibcCFlags returns the C compiler flags for the configured libc. +// It only uses flags that are part of the libc path (triple, cpu, abi, libc +// name) so it can safely be used to compile another C library. +func (c *Config) LibcCFlags() []string { switch c.Target.Libc { case "darwin-libSystem": root := goenv.Get("TINYGOROOT") - cflags = append(cflags, + return []string{ "-nostdlibinc", "-isystem", filepath.Join(root, "lib/macos-minimal-sdk/src/usr/include"), - ) + } case "picolibc": root := goenv.Get("TINYGOROOT") picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc") path, _ := c.LibcPath("picolibc") - cflags = append(cflags, + return []string{ "-nostdlibinc", "-isystem", filepath.Join(path, "include"), "-isystem", filepath.Join(picolibcDir, "include"), "-isystem", filepath.Join(picolibcDir, "tinystdio"), "-D__PICOLIBC_ERRNO_FUNCTION=__errno_location", - ) + } case "musl": root := goenv.Get("TINYGOROOT") path, _ := c.LibcPath("musl") arch := MuslArchitecture(c.Triple()) - cflags = append(cflags, + return []string{ "-nostdlibinc", "-isystem", filepath.Join(path, "include"), "-isystem", filepath.Join(root, "lib", "musl", "arch", arch), "-isystem", filepath.Join(root, "lib", "musl", "arch", "generic"), "-isystem", filepath.Join(root, "lib", "musl", "include"), - ) + } case "wasi-libc": root := goenv.Get("TINYGOROOT") - cflags = append(cflags, + return []string{ "-nostdlibinc", - "-isystem", root+"/lib/wasi-libc/sysroot/include") + "-isystem", root + "/lib/wasi-libc/sysroot/include", + } case "wasmbuiltins": // nothing to add (library is purely for builtins) + return nil case "mingw-w64": root := goenv.Get("TINYGOROOT") path, _ := c.LibcPath("mingw-w64") - cflags = append(cflags, + return []string{ "-nostdlibinc", "-isystem", filepath.Join(path, "include"), "-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "crt"), "-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"), "-D_UCRT", - ) + } case "": // No libc specified, nothing to add. + return nil default: // Incorrect configuration. This could be handled in a better way, but // usually this will be found by developers (not by TinyGo users). panic("unknown libc: " + c.Target.Libc) } - // Always emit debug information. It is optionally stripped at link time. - cflags = append(cflags, "-gdwarf-4") - // Use the same optimization level as TinyGo. - cflags = append(cflags, "-O"+c.Options.Opt) - // Set the LLVM target triple. - cflags = append(cflags, "--target="+c.Triple()) - // Set the -mcpu (or similar) flag. - if c.Target.CPU != "" { - if c.GOARCH() == "amd64" || c.GOARCH() == "386" { - // x86 prefers the -march flag (-mcpu is deprecated there). - cflags = append(cflags, "-march="+c.Target.CPU) - } else if strings.HasPrefix(c.Triple(), "avr") { - // AVR MCUs use -mmcu instead of -mcpu. - cflags = append(cflags, "-mmcu="+c.Target.CPU) - } else { - // The rest just uses -mcpu. - cflags = append(cflags, "-mcpu="+c.Target.CPU) - } - } - // Set the -mabi flag, if needed. - if c.ABI() != "" { - cflags = append(cflags, "-mabi="+c.ABI()) - } - return cflags } // LDFlags returns the flags to pass to the linker. A few more flags are needed diff --git a/compileopts/options.go b/compileopts/options.go index e698cb3cb7..ed248c8020 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -9,7 +9,7 @@ import ( var ( validBuildModeOptions = []string{"default", "c-shared", "wasi-legacy"} - validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise"} + validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise", "boehm"} validSchedulerOptions = []string{"none", "tasks", "asyncify"} validSerialOptions = []string{"none", "uart", "usb", "rtt"} validPrintSizeOptions = []string{"none", "short", "full", "html"} diff --git a/compileopts/options_test.go b/compileopts/options_test.go index ee63c4c46d..280ff9b467 100644 --- a/compileopts/options_test.go +++ b/compileopts/options_test.go @@ -9,7 +9,7 @@ import ( func TestVerifyOptions(t *testing.T) { - expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, conservative, custom, precise`) + expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, conservative, custom, precise, boehm`) expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, asyncify`) expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full, html`) expectedPanicStrategyError := errors.New(`invalid panic option 'incorrect': valid values are print, trap`) diff --git a/compileopts/target.go b/compileopts/target.go index 64cbc2daa9..6917a944bd 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -247,7 +247,6 @@ func defaultTarget(options *Options) (*TargetSpec, error) { GOOS: options.GOOS, GOARCH: options.GOARCH, BuildTags: []string{options.GOOS, options.GOARCH}, - GC: "precise", Scheduler: "tasks", Linker: "cc", DefaultStackSize: 1024 * 64, // 64kB @@ -366,6 +365,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) { llvmvendor := "unknown" switch options.GOOS { case "darwin": + spec.GC = "precise" platformVersion := "10.12.0" if options.GOARCH == "arm64" { platformVersion = "11.0.0" // first macosx platform with arm64 support @@ -388,6 +388,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) { "src/runtime/runtime_unix.c", "src/runtime/signal.c") case "linux": + spec.GC = "boehm" spec.Linker = "ld.lld" spec.RTLib = "compiler-rt" spec.Libc = "musl" @@ -411,6 +412,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) { "src/runtime/runtime_unix.c", "src/runtime/signal.c") case "windows": + spec.GC = "precise" spec.Linker = "ld.lld" spec.Libc = "mingw-w64" // Note: using a medium code model, low image base and no ASLR diff --git a/lib/bdwgc b/lib/bdwgc new file mode 160000 index 0000000000..d1ff06cc50 --- /dev/null +++ b/lib/bdwgc @@ -0,0 +1 @@ +Subproject commit d1ff06cc503a74dca0150d5e988f2c93158b0cdf diff --git a/src/runtime/gc_blocks.go b/src/runtime/gc_blocks.go index 6d32863649..62e38dcd7a 100644 --- a/src/runtime/gc_blocks.go +++ b/src/runtime/gc_blocks.go @@ -534,6 +534,12 @@ func markRoots(start, end uintptr) { } } +func markCurrentGoroutineStack(sp uintptr) { + // This could be optimized by only marking the stack area that's currently + // in use. + markRoot(0, sp) +} + // stackOverflow is a flag which is set when the GC scans too deep while marking. // After it is set, all marked allocations must be re-scanned. var stackOverflow bool diff --git a/src/runtime/gc_boehm.go b/src/runtime/gc_boehm.go new file mode 100644 index 0000000000..0955f3c224 --- /dev/null +++ b/src/runtime/gc_boehm.go @@ -0,0 +1,172 @@ +//go:build gc.boehm + +package runtime + +import ( + "internal/gclayout" + "internal/reflectlite" + "internal/task" + "unsafe" +) + +const needsStaticHeap = false + +// zeroSizedAlloc is just a sentinel that gets returned when allocating 0 bytes. +var zeroSizedAlloc uint8 + +var gcLock task.PMutex + +func initHeap() { + libgc_init() + + libgc_set_push_other_roots(gcCallbackPtr) +} + +var gcCallbackPtr = reflectlite.ValueOf(gcCallback).UnsafePointer() + +func gcCallback() { + // Mark the system stack and (if we're on a goroutine stack) also the + // current goroutine stack. + markStack() + + findGlobals(func(start, end uintptr) { + libgc_push_all(start, end) + }) +} + +func markRoots(start, end uintptr) { + libgc_push_all(start, end) +} + +func markCurrentGoroutineStack(sp uintptr) { + // Only mark the area of the stack that is currently in use. + // (This doesn't work for other goroutines, but at least it doesn't keep + // more pointers alive than needed on the current stack). + base := libgc_base(sp) + if base == 0 { // && asserts + runtimePanic("goroutine stack not in a heap allocation?") + } + stackBottom := base + libgc_size(base) + libgc_push_all_stack(sp, stackBottom) +} + +//go:noinline +func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { + if size == 0 { + return unsafe.Pointer(&zeroSizedAlloc) + } + + gcLock.Lock() + var ptr unsafe.Pointer + if layout == gclayout.NoPtrs { + // This object is entirely pointer free, for example make([]int, ...). + // Make sure the GC knows this so it doesn't scan the object + // unnecessarily to improve performance. + ptr = libgc_malloc_atomic(size) + // Memory returned from libgc_malloc_atomic has not been zeroed so we + // have to do that manually. + memzero(ptr, size) + } else { + // TODO: bdwgc supports typed allocations, which could be useful to + // implement a mostly-precise GC. + ptr = libgc_malloc(size) + // Memory returned from libgc_malloc has already been zeroed, so nothing + // to do here. + } + gcLock.Unlock() + if ptr == nil { + runtimePanic("gc: out of memory") + } + + return ptr +} + +func free(ptr unsafe.Pointer) { + libgc_free(ptr) +} + +func GC() { + libgc_gcollect() +} + +// This should be stack-allocated, but we don't currently have a good way of +// ensuring that happens. +var gcMemStats libgc_prof_stats + +func ReadMemStats(m *MemStats) { + gcLock.Lock() + + libgc_get_prof_stats(&gcMemStats, unsafe.Sizeof(gcMemStats)) + + // Fill in MemStats as well as we can, given the information that bdwgc + // provides to us. + m.HeapIdle = uint64(gcMemStats.free_bytes_full - gcMemStats.unmapped_bytes) + m.HeapInuse = uint64(gcMemStats.heapsize_full - gcMemStats.unmapped_bytes) + m.HeapReleased = uint64(gcMemStats.unmapped_bytes) + m.HeapSys = uint64(m.HeapInuse + m.HeapIdle) + m.GCSys = 0 // not provided by bdwgc + m.TotalAlloc = uint64(gcMemStats.allocd_bytes_before_gc + gcMemStats.bytes_allocd_since_gc) + m.Mallocs = 0 // not provided by bdwgc + m.Frees = 0 // not provided by bdwgc + m.Sys = uint64(gcMemStats.obtained_from_os_bytes) + + gcLock.Unlock() +} + +func setHeapEnd(newHeapEnd uintptr) { + runtimePanic("gc: did not expect setHeapEnd call") +} + +func SetFinalizer(obj interface{}, finalizer interface{}) { + // Unimplemented. + // The GC *does* support finalization, so this could be added relatively + // easily I think. +} + +//export GC_init +func libgc_init() + +//export GC_malloc +func libgc_malloc(uintptr) unsafe.Pointer + +//export GC_malloc_atomic +func libgc_malloc_atomic(uintptr) unsafe.Pointer + +//export GC_free +func libgc_free(unsafe.Pointer) + +//export GC_base +func libgc_base(ptr uintptr) uintptr + +//export GC_size +func libgc_size(ptr uintptr) uintptr + +//export GC_push_all +func libgc_push_all(bottom, top uintptr) + +//export GC_push_all_stack +func libgc_push_all_stack(bottom, top uintptr) + +//export GC_gcollect +func libgc_gcollect() + +//export GC_get_prof_stats +func libgc_get_prof_stats(*libgc_prof_stats, uintptr) uintptr + +//export GC_set_push_other_roots +func libgc_set_push_other_roots(unsafe.Pointer) + +type libgc_prof_stats struct { + heapsize_full uintptr + free_bytes_full uintptr + unmapped_bytes uintptr + bytes_allocd_since_gc uintptr + allocd_bytes_before_gc uintptr + non_gc_bytes uintptr + gc_no uintptr + markers_m1 uintptr + bytes_reclaimed_since_gc uintptr + reclaimed_bytes_before_gc uintptr + expl_freed_bytes_since_gc uintptr + obtained_from_os_bytes uintptr +} diff --git a/src/runtime/gc_globals.go b/src/runtime/gc_globals.go index f27911ec51..3e8f857618 100644 --- a/src/runtime/gc_globals.go +++ b/src/runtime/gc_globals.go @@ -1,4 +1,4 @@ -//go:build (gc.conservative || gc.precise) && (baremetal || tinygo.wasm) +//go:build baremetal || tinygo.wasm package runtime diff --git a/src/runtime/gc_stack_raw.go b/src/runtime/gc_stack_raw.go index 5ee18622db..01ec0208c2 100644 --- a/src/runtime/gc_stack_raw.go +++ b/src/runtime/gc_stack_raw.go @@ -1,4 +1,4 @@ -//go:build (gc.conservative || gc.precise) && !tinygo.wasm +//go:build (gc.conservative || gc.precise || gc.boehm) && !tinygo.wasm package runtime @@ -33,7 +33,6 @@ func scanstack(sp uintptr) { markRoots(sp, stackTop) } else { // This is a goroutine stack. - // It is an allocation, so scan it as if it were a value in a global. - markRoot(0, sp) + markCurrentGoroutineStack(sp) } } From 7755780a498b11865c3517ddb3c34577b4a7b9f5 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 15 Mar 2025 22:00:57 -0700 Subject: [PATCH 435/444] GNUmakefile, internal/wasm-tools: update to go.bytecodealliance.org@v0.6.0 --- GNUmakefile | 7 ++++--- internal/wasm-tools/go.mod | 20 ++++++++++-------- internal/wasm-tools/go.sum | 40 ++++++++++++++++++++---------------- internal/wasm-tools/tools.go | 5 +++-- 4 files changed, 41 insertions(+), 31 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 8a25e1df99..4e630c6a3f 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -267,16 +267,17 @@ lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a: cd lib/wasi-libc && $(MAKE) -j4 EXTRA_CFLAGS="-O2 -g -DNDEBUG -mnontrapping-fptoint -msign-ext" MALLOC_IMPL=none CC="$(CLANG)" AR=$(LLVM_AR) NM=$(LLVM_NM) # Generate WASI syscall bindings -WASM_TOOLS_MODULE=github.com/bytecodealliance/wasm-tools-go +WASM_TOOLS_MODULE=go.bytecodealliance.org .PHONY: wasi-syscall wasi-syscall: wasi-cm + rm -rf ./src/internal/wasi/* go run -modfile ./internal/wasm-tools/go.mod $(WASM_TOOLS_MODULE)/cmd/wit-bindgen-go generate --versioned -o ./src/internal -p internal --cm internal/cm ./lib/wasi-cli/wit # Copy package cm into src/internal/cm .PHONY: wasi-cm wasi-cm: - # rm -rf ./src/internal/cm - rsync -rv --delete --exclude '*_test.go' $(shell go list -modfile ./internal/wasm-tools/go.mod -m -f {{.Dir}} $(WASM_TOOLS_MODULE))/cm ./src/internal/ + rm -rf ./src/internal/cm/* + rsync -rv --delete --exclude '*_test.go' $(shell go list -modfile ./internal/wasm-tools/go.mod -m -f {{.Dir}} $(WASM_TOOLS_MODULE)/cm)/ ./src/internal/cm # Check for Node.js used during WASM tests. NODEJS_VERSION := $(word 1,$(subst ., ,$(shell node -v | cut -c 2-))) diff --git a/internal/wasm-tools/go.mod b/internal/wasm-tools/go.mod index 3404ab78c7..47ad4889c7 100644 --- a/internal/wasm-tools/go.mod +++ b/internal/wasm-tools/go.mod @@ -1,18 +1,22 @@ -module github.com/tinygo-org/tinygo/internal/tools +module github.com/tinygo-org/tinygo/internal/wasm-tools -go 1.22.4 +go 1.23.0 -require github.com/bytecodealliance/wasm-tools-go v0.3.1 +require ( + go.bytecodealliance.org v0.6.0 + go.bytecodealliance.org/cm v0.2.0 +) require ( github.com/coreos/go-semver v0.3.1 // indirect github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect - github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/regclient/regclient v0.7.1 // indirect + github.com/regclient/regclient v0.8.2 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/tetratelabs/wazero v1.9.0 // indirect github.com/ulikunitz/xz v0.5.12 // indirect - github.com/urfave/cli/v3 v3.0.0-alpha9.2 // indirect - golang.org/x/mod v0.21.0 // indirect - golang.org/x/sys v0.26.0 // indirect + github.com/urfave/cli/v3 v3.0.0-beta1 // indirect + golang.org/x/mod v0.24.0 // indirect + golang.org/x/sys v0.31.0 // indirect ) diff --git a/internal/wasm-tools/go.sum b/internal/wasm-tools/go.sum index 2f4bef21b6..45b74397d1 100644 --- a/internal/wasm-tools/go.sum +++ b/internal/wasm-tools/go.sum @@ -1,5 +1,3 @@ -github.com/bytecodealliance/wasm-tools-go v0.3.1 h1:9Q9PjSzkbiVmkUvZ7nYCfJ02mcQDBalxycA3s8g7kR4= -github.com/bytecodealliance/wasm-tools-go v0.3.1/go.mod h1:vNAQ8DAEp6xvvk+TUHah5DslLEa76f4H6e737OeaxuY= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -7,16 +5,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/olareg/olareg v0.1.0 h1:1dXBOgPrig5N7zoXyIZVQqU0QBo6sD9pbL6UYjY75CA= -github.com/olareg/olareg v0.1.0/go.mod h1:RBuU7JW7SoIIxZKzLRhq8sVtQeAHzCAtRrXEBx2KlM4= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/olareg/olareg v0.1.1 h1:Ui7q93zjcoF+U9U71sgqgZWByDoZOpqHitUXEu2xV+g= +github.com/olareg/olareg v0.1.1/go.mod h1:w8NP4SWrHHtxsFaUiv1lnCnYPm4sN1seCd2h7FK/dc0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/regclient/regclient v0.7.1 h1:qEsJrTmZd98fZKjueAbrZCSNGU+ifnr6xjlSAs3WOPs= -github.com/regclient/regclient v0.7.1/go.mod h1:+w/BFtJuw0h0nzIw/z2+1FuA2/dVXBzDq4rYmziJpMc= +github.com/regclient/regclient v0.8.2 h1:23BQ3jWgKYHHIXUhp/S9laVJDHDoOQaQCzXMJ4undVE= +github.com/regclient/regclient v0.8.2/go.mod h1:uGyetv0o6VLyRDjtfeBqp/QBwRLJ3Hcn07/+8QbhNcM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -25,19 +23,25 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= +github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/urfave/cli/v3 v3.0.0-alpha9.2 h1:CL8llQj3dGRLVQQzHxS+ZYRLanOuhyK1fXgLKD+qV+Y= -github.com/urfave/cli/v3 v3.0.0-alpha9.2/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg= +github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= +go.bytecodealliance.org v0.6.0 h1:9ziqj963aEKqOiu5cl87Hb78KImNP4zEABl+OWwBwxk= +go.bytecodealliance.org v0.6.0/go.mod h1:j0lprXhqeVSE8kYN2EjcN9gm+fxUyNWtl//WA+3ypFg= +go.bytecodealliance.org/cm v0.2.0 h1:HMkj1x1LZWU/Ghu2TtUk3VTyS7gyDHLyIfIut0Cpc5M= +go.bytecodealliance.org/cm v0.2.0/go.mod h1:JD5vtVNZv7sBoQQkvBvAAVKJPhR/bqBH7yYXTItMfZI= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/wasm-tools/tools.go b/internal/wasm-tools/tools.go index 0876ea7331..baf54a4a33 100644 --- a/internal/wasm-tools/tools.go +++ b/internal/wasm-tools/tools.go @@ -5,7 +5,8 @@ package tools import ( - _ "github.com/bytecodealliance/wasm-tools-go/cmd/wit-bindgen-go" + _ "go.bytecodealliance.org/cm" + _ "go.bytecodealliance.org/cmd/wit-bindgen-go" ) -//go:generate go install github.com/bytecodealliance/wasm-tools-go/cmd/wit-bindgen-go +//go:generate go install go.bytecodealliance.org/cmd/wit-bindgen-go From 429aea8e74ae06b3fec6e20022eb64b851f3fa59 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 15 Mar 2025 22:01:26 -0700 Subject: [PATCH 436/444] internal/cm: update to go.bytecodealliance.org/cm@v0.2.0 --- src/internal/cm/CHANGELOG.md | 23 ++++ src/internal/cm/LICENSE | 220 ++++++++++++++++++++++++++++++++++ src/internal/cm/README.md | 15 +++ src/internal/cm/RELEASE.md | 34 ++++++ src/internal/cm/case.go | 51 ++++++++ src/internal/cm/docs.go | 2 +- src/internal/cm/empty.s | 3 + src/internal/cm/error.go | 40 +++++++ src/internal/cm/error.wasm.go | 13 ++ src/internal/cm/future.go | 15 +++ src/internal/cm/go.mod | 3 + src/internal/cm/list.go | 60 +++++++++- src/internal/cm/stream.go | 15 +++ src/internal/cm/variant.go | 5 +- 14 files changed, 494 insertions(+), 5 deletions(-) create mode 100644 src/internal/cm/CHANGELOG.md create mode 100644 src/internal/cm/LICENSE create mode 100644 src/internal/cm/README.md create mode 100644 src/internal/cm/RELEASE.md create mode 100644 src/internal/cm/case.go create mode 100644 src/internal/cm/empty.s create mode 100644 src/internal/cm/error.go create mode 100644 src/internal/cm/error.wasm.go create mode 100644 src/internal/cm/future.go create mode 100644 src/internal/cm/go.mod create mode 100644 src/internal/cm/stream.go diff --git a/src/internal/cm/CHANGELOG.md b/src/internal/cm/CHANGELOG.md new file mode 100644 index 0000000000..35efaf2f57 --- /dev/null +++ b/src/internal/cm/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [v0.2.0] — 2025-03-15 + +### Added + +- Initial support for Component Model [async](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Async.md) types `stream`, `future`, and `error-context`. +- Initial support for JSON serialization of WIT `list`, `enum`, and `record` types. +- Added `cm.CaseUnmarshaler` helper for text and JSON unmarshaling of `enum` and `variant` types. + +### Changed + +- Breaking: package `cm`: removed `bool` from `Discriminant` type constraint. It was not used by code generation. + +## [v0.1.0] — 2024-12-14 + +Initial version, extracted into module [`go.bytecodealliance.org/cm`](https://pkg.go.dev/go.bytecodealliance.org/cm). + +[Unreleased]: +[v0.2.0]: +[v0.1.0]: diff --git a/src/internal/cm/LICENSE b/src/internal/cm/LICENSE new file mode 100644 index 0000000000..f9d81955f4 --- /dev/null +++ b/src/internal/cm/LICENSE @@ -0,0 +1,220 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + + +--- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + diff --git a/src/internal/cm/README.md b/src/internal/cm/README.md new file mode 100644 index 0000000000..d6c5fd4fd5 --- /dev/null +++ b/src/internal/cm/README.md @@ -0,0 +1,15 @@ +# go.bytecodealliance.org/cm + +[![pkg.go.dev](https://img.shields.io/badge/docs-pkg.go.dev-blue.svg)](https://pkg.go.dev/go.bytecodealliance.org/cm) [![build status](https://img.shields.io/github/actions/workflow/status/bytecodealliance/go-modules/test.yaml?branch=main)](https://github.com/bytecodealliance/go-modules/actions) + +## About + +Package `cm` contains helper types and functions used by generated packages, such as `option`, `result`, `variant`, `list`, and `resource`. These are intended for use by generated [Component Model](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#type-definitions) bindings, where the caller converts to a Go equivalent. It attempts to map WIT semantics to their equivalent in Go where possible. + +### Note on Memory Safety + +Package `cm` and generated bindings from `wit-bindgen-go` may have compatibility issues with the Go garbage collector, as they directly represent `variant` and `result` types as tagged unions where a pointer shape may be occupied by a non-pointer value. The GC may detect and throw an error if it detects a non-pointer value in an area it expects to see a pointer. This is an area of active development. + +## License + +This project is licensed under the Apache 2.0 license with the LLVM exception. See [LICENSE](../LICENSE) for more details. diff --git a/src/internal/cm/RELEASE.md b/src/internal/cm/RELEASE.md new file mode 100644 index 0000000000..5a6283a606 --- /dev/null +++ b/src/internal/cm/RELEASE.md @@ -0,0 +1,34 @@ +# Release + +This document describes the steps to release a new version of module `go.bytecodealliance.org/cm`. + +## 1. Update [CHANGELOG.md](./CHANGELOG.md) + +1. Add the latest changes to [CHANGELOG.md](./CHANGELOG.md). +1. Rename the Unreleased section to reflect the new version number. + 1. Update the links to new version tag in the footer of CHANGELOG.md +1. Add today’s date (YYYY-MM-DD) after an em dash (—). +1. Submit a [GitHub Pull Request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests) with these updates. + +## 2. Create a new release + +Once the PR is merged, tag the new version in Git and push the tag to GitHub. + +**Note:** the tag **must** start with the prefix `cm/` in order to correctly tag this module. + +For example, to tag version `cm/v0.2.0`: + +```console +git tag cm/v0.2.0 +git push origin cm/v0.2.0 +``` + +## 3. Update the root module + +Once the tag is pushed, you can update the root module to depend on the newly created version of package `cm` by running the following: + +```console +go get -u go.bytecodealliance.org/cm@latest +``` + +Then follow the instructions in [RELEASE.md](../RELEASE.md) to release a new version of the root module. diff --git a/src/internal/cm/case.go b/src/internal/cm/case.go new file mode 100644 index 0000000000..65ade4949a --- /dev/null +++ b/src/internal/cm/case.go @@ -0,0 +1,51 @@ +package cm + +import "errors" + +// CaseUnmarshaler returns an function that can unmarshal text into +// [variant] or [enum] case T. +// +// [enum]: https://component-model.bytecodealliance.org/design/wit.html#enums +// [variant]: https://component-model.bytecodealliance.org/design/wit.html#variants +func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, text []byte) error { + if len(cases) <= linearScanThreshold { + return func(v *T, text []byte) error { + if len(text) == 0 { + return errEmpty + } + s := string(text) + for i := 0; i < len(cases); i++ { + if cases[i] == s { + *v = T(i) + return nil + } + } + return errNoMatchingCase + } + } + + m := make(map[string]T, len(cases)) + for i, v := range cases { + m[v] = T(i) + } + + return func(v *T, text []byte) error { + if len(text) == 0 { + return errEmpty + } + s := string(text) + c, ok := m[s] + if !ok { + return errNoMatchingCase + } + *v = c + return nil + } +} + +const linearScanThreshold = 16 + +var ( + errEmpty = errors.New("empty text") + errNoMatchingCase = errors.New("no matching case") +) diff --git a/src/internal/cm/docs.go b/src/internal/cm/docs.go index 5fc48fb759..5522cf9424 100644 --- a/src/internal/cm/docs.go +++ b/src/internal/cm/docs.go @@ -1,4 +1,4 @@ -// Package cm contains types and functions for interfacing with the WebAssembly Component Model. +// Package cm provides types and functions for interfacing with the WebAssembly Component Model. // // The types in this package (such as [List], [Option], [Result], and [Variant]) are designed to match the memory layout // of [Component Model] types as specified in the [Canonical ABI]. diff --git a/src/internal/cm/empty.s b/src/internal/cm/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/cm/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/cm/error.go b/src/internal/cm/error.go new file mode 100644 index 0000000000..857d5ce7c1 --- /dev/null +++ b/src/internal/cm/error.go @@ -0,0 +1,40 @@ +package cm + +import "unsafe" + +// ErrorContext represents the Component Model [error-context] type, +// an immutable, non-deterministic, host-defined value meant to aid in debugging. +// +// [error-context]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#error-context-type +type ErrorContext struct { + _ HostLayout + errorContext +} + +type errorContext uint32 + +// Error implements the [error] interface. It returns the debug message associated with err. +func (err errorContext) Error() string { + return err.DebugMessage() +} + +// String implements [fmt.Stringer]. +func (err errorContext) String() string { + return err.DebugMessage() +} + +// DebugMessage represents the Canonical ABI [error-context.debug-message] function. +// +// [error-context.debug-message]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#error-contextdebug-message +func (err errorContext) DebugMessage() string { + var s string + wasmimport_errorContextDebugMessage(err, unsafe.Pointer(&s)) + return s +} + +// Drop represents the Canonical ABI [error-context.drop] function. +// +// [error-context.drop]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#error-contextdrop +func (err errorContext) Drop() { + wasmimport_errorContextDrop(err) +} diff --git a/src/internal/cm/error.wasm.go b/src/internal/cm/error.wasm.go new file mode 100644 index 0000000000..112eb6d815 --- /dev/null +++ b/src/internal/cm/error.wasm.go @@ -0,0 +1,13 @@ +package cm + +import "unsafe" + +// msg uses unsafe.Pointer for compatibility with go1.23 and lower. +// +//go:wasmimport canon error-context.debug-message +//go:noescape +func wasmimport_errorContextDebugMessage(err errorContext, msg unsafe.Pointer) + +//go:wasmimport canon error-context.drop +//go:noescape +func wasmimport_errorContextDrop(err errorContext) diff --git a/src/internal/cm/future.go b/src/internal/cm/future.go new file mode 100644 index 0000000000..e82183f655 --- /dev/null +++ b/src/internal/cm/future.go @@ -0,0 +1,15 @@ +package cm + +// Future represents the Component Model [future] type. +// A future is a special case of stream. In non-error cases, +// a future delivers exactly one value before being automatically closed. +// +// [future]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#asynchronous-value-types +type Future[T any] struct { + _ HostLayout + future[T] +} + +type future[T any] uint32 + +// TODO: implement methods on type future diff --git a/src/internal/cm/go.mod b/src/internal/cm/go.mod new file mode 100644 index 0000000000..f8e24af713 --- /dev/null +++ b/src/internal/cm/go.mod @@ -0,0 +1,3 @@ +module go.bytecodealliance.org/cm + +go 1.23.0 diff --git a/src/internal/cm/list.go b/src/internal/cm/list.go index 5c896d044b..0a171dbd37 100644 --- a/src/internal/cm/list.go +++ b/src/internal/cm/list.go @@ -1,6 +1,10 @@ package cm -import "unsafe" +import ( + "bytes" + "encoding/json" + "unsafe" +) // List represents a Component Model list. // The binary representation of list is similar to a Go slice minus the cap field. @@ -58,3 +62,57 @@ func (l list[T]) Data() *T { func (l list[T]) Len() uintptr { return l.len } + +// MarshalJSON implements json.Marshaler. +func (l list[T]) MarshalJSON() ([]byte, error) { + if l.len == 0 { + return []byte("[]"), nil + } + + s := l.Slice() + var zero T + if unsafe.Sizeof(zero) == 1 { + // The default Go json.Encoder will marshal []byte as base64. + // We override that behavior so all int types have the same serialization format. + // []uint8{1,2,3} -> [1,2,3] + // []uint32{1,2,3} -> [1,2,3] + return json.Marshal(sliceOf(s)) + } + return json.Marshal(s) +} + +type slice[T any] []entry[T] + +func sliceOf[S ~[]E, E any](s S) slice[E] { + return *(*slice[E])(unsafe.Pointer(&s)) +} + +type entry[T any] [1]T + +func (v entry[T]) MarshalJSON() ([]byte, error) { + return json.Marshal(v[0]) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (l *list[T]) UnmarshalJSON(data []byte) error { + if bytes.Equal(data, nullLiteral) { + return nil + } + + var s []T + err := json.Unmarshal(data, &s) + if err != nil { + return err + } + + l.data = unsafe.SliceData([]T(s)) + l.len = uintptr(len(s)) + + return nil +} + +// nullLiteral is the JSON representation of a null literal. +// By convention, to approximate the behavior of Unmarshal itself, +// Unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op. +// See https://pkg.go.dev/encoding/json#Unmarshaler for more information. +var nullLiteral = []byte("null") diff --git a/src/internal/cm/stream.go b/src/internal/cm/stream.go new file mode 100644 index 0000000000..80ef062e97 --- /dev/null +++ b/src/internal/cm/stream.go @@ -0,0 +1,15 @@ +package cm + +// Stream represents the Component Model [stream] type. +// A stream is a special case of stream. In non-error cases, +// a stream delivers exactly one value before being automatically closed. +// +// [stream]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#asynchronous-value-types +type Stream[T any] struct { + _ HostLayout + stream[T] +} + +type stream[T any] uint32 + +// TODO: implement methods on type stream diff --git a/src/internal/cm/variant.go b/src/internal/cm/variant.go index 24703641df..d0def34bb3 100644 --- a/src/internal/cm/variant.go +++ b/src/internal/cm/variant.go @@ -3,10 +3,9 @@ package cm import "unsafe" // Discriminant is the set of types that can represent the tag or discriminator of a variant. -// Use bool for 2-case variant types, result, or option types, uint8 where there are 256 or -// fewer cases, uint16 for up to 65,536 cases, or uint32 for anything greater. +// Use uint8 where there are 256 or fewer cases, uint16 for up to 65,536 cases, or uint32 for anything greater. type Discriminant interface { - bool | uint8 | uint16 | uint32 + uint8 | uint16 | uint32 } // Variant represents a loosely-typed Component Model variant. From 211425c3e9967b9a74b95ba8fb9f7ce4be38c4e7 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 15 Mar 2025 22:02:04 -0700 Subject: [PATCH 437/444] internal/wasi: regenerate WASI 0.2 bindings Use go.bytecodealliance.org/cmd/wit-bindgen-go@v0.6.0 --- .../wasi/cli/v0.2.0/command/command.wit | 2099 ----------------- .../wasi/cli/v0.2.0/command/command.wit.go | 0 .../wasi/cli/v0.2.0/environment/empty.s | 0 .../v0.2.0/environment/environment.wasm.go | 0 .../cli/v0.2.0/environment/environment.wit.go | 0 src/internal/wasi/cli/v0.2.0/exit/empty.s | 0 .../wasi/cli/v0.2.0/exit/exit.wasm.go | 0 src/internal/wasi/cli/v0.2.0/exit/exit.wit.go | 2 +- src/internal/wasi/cli/v0.2.0/run/empty.s | 0 .../wasi/cli/v0.2.0/run/run.exports.go | 0 src/internal/wasi/cli/v0.2.0/run/run.wasm.go | 2 +- src/internal/wasi/cli/v0.2.0/run/run.wit.go | 0 src/internal/wasi/cli/v0.2.0/stderr/empty.s | 0 .../wasi/cli/v0.2.0/stderr/stderr.wasm.go | 0 .../wasi/cli/v0.2.0/stderr/stderr.wit.go | 0 src/internal/wasi/cli/v0.2.0/stdin/empty.s | 0 .../wasi/cli/v0.2.0/stdin/stdin.wasm.go | 0 .../wasi/cli/v0.2.0/stdin/stdin.wit.go | 0 src/internal/wasi/cli/v0.2.0/stdout/empty.s | 0 .../wasi/cli/v0.2.0/stdout/stdout.wasm.go | 0 .../wasi/cli/v0.2.0/stdout/stdout.wit.go | 0 .../wasi/cli/v0.2.0/terminal-input/empty.s | 0 ...alinput.wasm.go => terminal-input.wasm.go} | 0 .../terminal-input/terminal-input.wit.go | 0 .../wasi/cli/v0.2.0/terminal-output/empty.s | 0 ...output.wasm.go => terminal-output.wasm.go} | 0 .../terminal-output/terminal-output.wit.go | 0 .../wasi/cli/v0.2.0/terminal-stderr/empty.s | 0 ...stderr.wasm.go => terminal-stderr.wasm.go} | 0 .../terminal-stderr/terminal-stderr.wit.go | 0 .../wasi/cli/v0.2.0/terminal-stdin/empty.s | 0 ...alstdin.wasm.go => terminal-stdin.wasm.go} | 0 .../terminal-stdin/terminal-stdin.wit.go | 0 .../wasi/cli/v0.2.0/terminal-stdout/empty.s | 0 ...stdout.wasm.go => terminal-stdout.wasm.go} | 0 .../terminal-stdout/terminal-stdout.wit.go | 0 .../clocks/v0.2.0/monotonic-clock/empty.s | 0 ...cclock.wasm.go => monotonic-clock.wasm.go} | 0 .../monotonic-clock/monotonic-clock.wit.go | 0 .../wasi/clocks/v0.2.0/wall-clock/empty.s | 0 .../{wallclock.wasm.go => wall-clock.wasm.go} | 0 .../v0.2.0/wall-clock/wall-clock.wit.go | 6 +- .../wasi/filesystem/v0.2.0/preopens/empty.s | 0 .../v0.2.0/preopens/preopens.wasm.go | 0 .../v0.2.0/preopens/preopens.wit.go | 0 .../wasi/filesystem/v0.2.0/types/abi.go | 2 +- .../wasi/filesystem/v0.2.0/types/empty.s | 0 .../filesystem/v0.2.0/types/types.wasm.go | 0 .../wasi/filesystem/v0.2.0/types/types.wit.go | 83 +- src/internal/wasi/io/v0.2.0/error/empty.s | 0 .../error/{ioerror.wasm.go => error.wasm.go} | 0 .../wasi/io/v0.2.0/error/error.wit.go | 0 src/internal/wasi/io/v0.2.0/poll/empty.s | 0 src/internal/wasi/io/v0.2.0/poll/poll.wasm.go | 0 src/internal/wasi/io/v0.2.0/poll/poll.wit.go | 2 +- src/internal/wasi/io/v0.2.0/streams/abi.go | 12 - src/internal/wasi/io/v0.2.0/streams/empty.s | 0 .../wasi/io/v0.2.0/streams/streams.wasm.go | 0 .../wasi/io/v0.2.0/streams/streams.wit.go | 30 +- .../wasi/random/v0.2.0/insecure-seed/empty.s | 0 ...cureseed.wasm.go => insecure-seed.wasm.go} | 0 .../v0.2.0/insecure-seed/insecure-seed.wit.go | 0 .../wasi/random/v0.2.0/insecure/empty.s | 0 .../random/v0.2.0/insecure/insecure.wasm.go | 0 .../random/v0.2.0/insecure/insecure.wit.go | 0 .../wasi/random/v0.2.0/random/empty.s | 0 .../wasi/random/v0.2.0/random/random.wasm.go | 0 .../wasi/random/v0.2.0/random/random.wit.go | 0 .../sockets/v0.2.0/instance-network/empty.s | 0 ...twork.wasm.go => instance-network.wasm.go} | 0 .../instance-network/instance-network.wit.go | 0 .../wasi/sockets/v0.2.0/ip-name-lookup/abi.go | 0 .../sockets/v0.2.0/ip-name-lookup/empty.s | 0 ...elookup.wasm.go => ip-name-lookup.wasm.go} | 0 .../ip-name-lookup/ip-name-lookup.wit.go | 0 .../wasi/sockets/v0.2.0/network/abi.go | 0 .../wasi/sockets/v0.2.0/network/empty.s | 0 .../sockets/v0.2.0/network/network.wasm.go | 0 .../sockets/v0.2.0/network/network.wit.go | 58 +- .../sockets/v0.2.0/tcp-create-socket/empty.s | 0 ...cket.wasm.go => tcp-create-socket.wasm.go} | 0 .../tcp-create-socket.wit.go | 0 src/internal/wasi/sockets/v0.2.0/tcp/abi.go | 6 +- src/internal/wasi/sockets/v0.2.0/tcp/empty.s | 0 .../wasi/sockets/v0.2.0/tcp/tcp.wasm.go | 2 +- .../wasi/sockets/v0.2.0/tcp/tcp.wit.go | 23 +- .../sockets/v0.2.0/udp-create-socket/empty.s | 0 ...cket.wasm.go => udp-create-socket.wasm.go} | 0 .../udp-create-socket.wit.go | 0 src/internal/wasi/sockets/v0.2.0/udp/abi.go | 6 +- src/internal/wasi/sockets/v0.2.0/udp/empty.s | 0 .../wasi/sockets/v0.2.0/udp/udp.wasm.go | 0 .../wasi/sockets/v0.2.0/udp/udp.wit.go | 16 +- 93 files changed, 158 insertions(+), 2191 deletions(-) delete mode 100644 src/internal/wasi/cli/v0.2.0/command/command.wit mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/command/command.wit.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/environment/empty.s mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/environment/environment.wit.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/exit/empty.s mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/exit/exit.wit.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/run/empty.s mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/run/run.exports.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/run/run.wasm.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/run/run.wit.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/stderr/empty.s mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/stdin/empty.s mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/stdout/empty.s mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/terminal-input/empty.s rename src/internal/wasi/cli/v0.2.0/terminal-input/{terminalinput.wasm.go => terminal-input.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/terminal-output/empty.s rename src/internal/wasi/cli/v0.2.0/terminal-output/{terminaloutput.wasm.go => terminal-output.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/terminal-stderr/empty.s rename src/internal/wasi/cli/v0.2.0/terminal-stderr/{terminalstderr.wasm.go => terminal-stderr.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/terminal-stdin/empty.s rename src/internal/wasi/cli/v0.2.0/terminal-stdin/{terminalstdin.wasm.go => terminal-stdin.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/terminal-stdout/empty.s rename src/internal/wasi/cli/v0.2.0/terminal-stdout/{terminalstdout.wasm.go => terminal-stdout.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go mode change 100644 => 100755 src/internal/wasi/clocks/v0.2.0/monotonic-clock/empty.s rename src/internal/wasi/clocks/v0.2.0/monotonic-clock/{monotonicclock.wasm.go => monotonic-clock.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go mode change 100644 => 100755 src/internal/wasi/clocks/v0.2.0/wall-clock/empty.s rename src/internal/wasi/clocks/v0.2.0/wall-clock/{wallclock.wasm.go => wall-clock.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go mode change 100644 => 100755 src/internal/wasi/filesystem/v0.2.0/preopens/empty.s mode change 100644 => 100755 src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go mode change 100644 => 100755 src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go mode change 100644 => 100755 src/internal/wasi/filesystem/v0.2.0/types/abi.go mode change 100644 => 100755 src/internal/wasi/filesystem/v0.2.0/types/empty.s mode change 100644 => 100755 src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go mode change 100644 => 100755 src/internal/wasi/filesystem/v0.2.0/types/types.wit.go mode change 100644 => 100755 src/internal/wasi/io/v0.2.0/error/empty.s rename src/internal/wasi/io/v0.2.0/error/{ioerror.wasm.go => error.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/io/v0.2.0/error/error.wit.go mode change 100644 => 100755 src/internal/wasi/io/v0.2.0/poll/empty.s mode change 100644 => 100755 src/internal/wasi/io/v0.2.0/poll/poll.wasm.go mode change 100644 => 100755 src/internal/wasi/io/v0.2.0/poll/poll.wit.go delete mode 100644 src/internal/wasi/io/v0.2.0/streams/abi.go mode change 100644 => 100755 src/internal/wasi/io/v0.2.0/streams/empty.s mode change 100644 => 100755 src/internal/wasi/io/v0.2.0/streams/streams.wasm.go mode change 100644 => 100755 src/internal/wasi/io/v0.2.0/streams/streams.wit.go mode change 100644 => 100755 src/internal/wasi/random/v0.2.0/insecure-seed/empty.s rename src/internal/wasi/random/v0.2.0/insecure-seed/{insecureseed.wasm.go => insecure-seed.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go mode change 100644 => 100755 src/internal/wasi/random/v0.2.0/insecure/empty.s mode change 100644 => 100755 src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go mode change 100644 => 100755 src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go mode change 100644 => 100755 src/internal/wasi/random/v0.2.0/random/empty.s mode change 100644 => 100755 src/internal/wasi/random/v0.2.0/random/random.wasm.go mode change 100644 => 100755 src/internal/wasi/random/v0.2.0/random/random.wit.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/instance-network/empty.s rename src/internal/wasi/sockets/v0.2.0/instance-network/{instancenetwork.wasm.go => instance-network.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/empty.s rename src/internal/wasi/sockets/v0.2.0/ip-name-lookup/{ipnamelookup.wasm.go => ip-name-lookup.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/network/abi.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/network/empty.s mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/network/network.wasm.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/network/network.wit.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/tcp-create-socket/empty.s rename src/internal/wasi/sockets/v0.2.0/tcp-create-socket/{tcpcreatesocket.wasm.go => tcp-create-socket.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/tcp/abi.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/tcp/empty.s mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/udp-create-socket/empty.s rename src/internal/wasi/sockets/v0.2.0/udp-create-socket/{udpcreatesocket.wasm.go => udp-create-socket.wasm.go} (100%) mode change 100644 => 100755 mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/udp/abi.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/udp/empty.s mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go mode change 100644 => 100755 src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go diff --git a/src/internal/wasi/cli/v0.2.0/command/command.wit b/src/internal/wasi/cli/v0.2.0/command/command.wit deleted file mode 100644 index 7b2af2ae9e..0000000000 --- a/src/internal/wasi/cli/v0.2.0/command/command.wit +++ /dev/null @@ -1,2099 +0,0 @@ -package wasi:cli@0.2.0; - -interface environment { - /// Get the POSIX-style environment variables. - /// - /// Each environment variable is provided as a pair of string variable names - /// and string value. - /// - /// Morally, these are a value import, but until value imports are available - /// in the component model, this import function should return the same - /// values each time it is called. - get-environment: func() -> list>; - - /// Get the POSIX-style arguments to the program. - get-arguments: func() -> list; - - /// Return a path that programs should use as their initial current working - /// directory, interpreting `.` as shorthand for this. - initial-cwd: func() -> option; -} - -interface exit { - /// Exit the current instance and any linked instances. - exit: func(status: result); -} - -interface run { - /// Run the program. - run: func() -> result; -} - -interface stdin { - use wasi:io/streams@0.2.0.{input-stream}; - get-stdin: func() -> input-stream; -} - -interface stdout { - use wasi:io/streams@0.2.0.{output-stream}; - get-stdout: func() -> output-stream; -} - -interface stderr { - use wasi:io/streams@0.2.0.{output-stream}; - get-stderr: func() -> output-stream; -} - -/// Terminal input. -/// -/// In the future, this may include functions for disabling echoing, -/// disabling input buffering so that keyboard events are sent through -/// immediately, querying supported features, and so on. -interface terminal-input { - /// The input side of a terminal. - resource terminal-input; -} - -/// Terminal output. -/// -/// In the future, this may include functions for querying the terminal -/// size, being notified of terminal size changes, querying supported -/// features, and so on. -interface terminal-output { - /// The output side of a terminal. - resource terminal-output; -} - -/// An interface providing an optional `terminal-input` for stdin as a -/// link-time authority. -interface terminal-stdin { - use terminal-input.{terminal-input}; - - /// If stdin is connected to a terminal, return a `terminal-input` handle - /// allowing further interaction with it. - get-terminal-stdin: func() -> option; -} - -/// An interface providing an optional `terminal-output` for stdout as a -/// link-time authority. -interface terminal-stdout { - use terminal-output.{terminal-output}; - - /// If stdout is connected to a terminal, return a `terminal-output` handle - /// allowing further interaction with it. - get-terminal-stdout: func() -> option; -} - -/// An interface providing an optional `terminal-output` for stderr as a -/// link-time authority. -interface terminal-stderr { - use terminal-output.{terminal-output}; - - /// If stderr is connected to a terminal, return a `terminal-output` handle - /// allowing further interaction with it. - get-terminal-stderr: func() -> option; -} - -world command { - import environment; - import exit; - import wasi:io/error@0.2.0; - import wasi:io/poll@0.2.0; - import wasi:io/streams@0.2.0; - import stdin; - import stdout; - import stderr; - import terminal-input; - import terminal-output; - import terminal-stdin; - import terminal-stdout; - import terminal-stderr; - import wasi:clocks/monotonic-clock@0.2.0; - import wasi:clocks/wall-clock@0.2.0; - import wasi:filesystem/types@0.2.0; - import wasi:filesystem/preopens@0.2.0; - import wasi:sockets/network@0.2.0; - import wasi:sockets/instance-network@0.2.0; - import wasi:sockets/udp@0.2.0; - import wasi:sockets/udp-create-socket@0.2.0; - import wasi:sockets/tcp@0.2.0; - import wasi:sockets/tcp-create-socket@0.2.0; - import wasi:sockets/ip-name-lookup@0.2.0; - import wasi:random/random@0.2.0; - import wasi:random/insecure@0.2.0; - import wasi:random/insecure-seed@0.2.0; - export run; -} - -package wasi:filesystem@0.2.0 { - /// WASI filesystem is a filesystem API primarily intended to let users run WASI - /// programs that access their files on their existing filesystems, without - /// significant overhead. - /// - /// It is intended to be roughly portable between Unix-family platforms and - /// Windows, though it does not hide many of the major differences. - /// - /// Paths are passed as interface-type `string`s, meaning they must consist of - /// a sequence of Unicode Scalar Values (USVs). Some filesystems may contain - /// paths which are not accessible by this API. - /// - /// The directory separator in WASI is always the forward-slash (`/`). - /// - /// All paths in WASI are relative paths, and are interpreted relative to a - /// `descriptor` referring to a base directory. If a `path` argument to any WASI - /// function starts with `/`, or if any step of resolving a `path`, including - /// `..` and symbolic link steps, reaches a directory outside of the base - /// directory, or reaches a symlink to an absolute or rooted path in the - /// underlying filesystem, the function fails with `error-code::not-permitted`. - /// - /// For more information about WASI path resolution and sandboxing, see - /// [WASI filesystem path resolution]. - /// - /// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md - interface types { - use wasi:io/streams@0.2.0.{input-stream}; - use wasi:io/streams@0.2.0.{output-stream}; - use wasi:io/streams@0.2.0.{error}; - use wasi:clocks/wall-clock@0.2.0.{datetime}; - - /// File size or length of a region within a file. - type filesize = u64; - - /// The type of a filesystem object referenced by a descriptor. - /// - /// Note: This was called `filetype` in earlier versions of WASI. - enum descriptor-type { - /// The type of the descriptor or file is unknown or is different from - /// any of the other types specified. - unknown, - /// The descriptor refers to a block device inode. - block-device, - /// The descriptor refers to a character device inode. - character-device, - /// The descriptor refers to a directory inode. - directory, - /// The descriptor refers to a named pipe. - fifo, - /// The file refers to a symbolic link inode. - symbolic-link, - /// The descriptor refers to a regular file inode. - regular-file, - /// The descriptor refers to a socket. - socket - } - - /// Descriptor flags. - /// - /// Note: This was called `fdflags` in earlier versions of WASI. - flags descriptor-flags { - /// Read mode: Data can be read. - read, - /// Write mode: Data can be written to. - write, - /// Request that writes be performed according to synchronized I/O file - /// integrity completion. The data stored in the file and the file's - /// metadata are synchronized. This is similar to `O_SYNC` in POSIX. - /// - /// The precise semantics of this operation have not yet been defined for - /// WASI. At this time, it should be interpreted as a request, and not a - /// requirement. - file-integrity-sync, - /// Request that writes be performed according to synchronized I/O data - /// integrity completion. Only the data stored in the file is - /// synchronized. This is similar to `O_DSYNC` in POSIX. - /// - /// The precise semantics of this operation have not yet been defined for - /// WASI. At this time, it should be interpreted as a request, and not a - /// requirement. - data-integrity-sync, - /// Requests that reads be performed at the same level of integrety - /// requested for writes. This is similar to `O_RSYNC` in POSIX. - /// - /// The precise semantics of this operation have not yet been defined for - /// WASI. At this time, it should be interpreted as a request, and not a - /// requirement. - requested-write-sync, - /// Mutating directories mode: Directory contents may be mutated. - /// - /// When this flag is unset on a descriptor, operations using the - /// descriptor which would create, rename, delete, modify the data or - /// metadata of filesystem objects, or obtain another handle which - /// would permit any of those, shall fail with `error-code::read-only` if - /// they would otherwise succeed. - /// - /// This may only be set on directories. - mutate-directory, - } - - /// Flags determining the method of how paths are resolved. - flags path-flags { - /// As long as the resolved path corresponds to a symbolic link, it is - /// expanded. - symlink-follow, - } - - /// Open flags used by `open-at`. - flags open-flags { - /// Create file if it does not exist, similar to `O_CREAT` in POSIX. - create, - /// Fail if not a directory, similar to `O_DIRECTORY` in POSIX. - directory, - /// Fail if file already exists, similar to `O_EXCL` in POSIX. - exclusive, - /// Truncate file to size 0, similar to `O_TRUNC` in POSIX. - truncate, - } - - /// Number of hard links to an inode. - type link-count = u64; - - /// File attributes. - /// - /// Note: This was called `filestat` in earlier versions of WASI. - record descriptor-stat { - /// File type. - %type: descriptor-type, - /// Number of hard links to the file. - link-count: link-count, - /// For regular files, the file size in bytes. For symbolic links, the - /// length in bytes of the pathname contained in the symbolic link. - size: filesize, - /// Last data access timestamp. - /// - /// If the `option` is none, the platform doesn't maintain an access - /// timestamp for this file. - data-access-timestamp: option, - /// Last data modification timestamp. - /// - /// If the `option` is none, the platform doesn't maintain a - /// modification timestamp for this file. - data-modification-timestamp: option, - /// Last file status-change timestamp. - /// - /// If the `option` is none, the platform doesn't maintain a - /// status-change timestamp for this file. - status-change-timestamp: option, - } - - /// When setting a timestamp, this gives the value to set it to. - variant new-timestamp { - /// Leave the timestamp set to its previous value. - no-change, - /// Set the timestamp to the current time of the system clock associated - /// with the filesystem. - now, - /// Set the timestamp to the given value. - timestamp(datetime), - } - - /// A directory entry. - record directory-entry { - /// The type of the file referred to by this directory entry. - %type: descriptor-type, - /// The name of the object. - name: string, - } - - /// Error codes returned by functions, similar to `errno` in POSIX. - /// Not all of these error codes are returned by the functions provided by this - /// API; some are used in higher-level library layers, and others are provided - /// merely for alignment with POSIX. - enum error-code { - /// Permission denied, similar to `EACCES` in POSIX. - access, - /// Resource unavailable, or operation would block, similar to `EAGAIN` and `EWOULDBLOCK` - /// in POSIX. - would-block, - /// Connection already in progress, similar to `EALREADY` in POSIX. - already, - /// Bad descriptor, similar to `EBADF` in POSIX. - bad-descriptor, - /// Device or resource busy, similar to `EBUSY` in POSIX. - busy, - /// Resource deadlock would occur, similar to `EDEADLK` in POSIX. - deadlock, - /// Storage quota exceeded, similar to `EDQUOT` in POSIX. - quota, - /// File exists, similar to `EEXIST` in POSIX. - exist, - /// File too large, similar to `EFBIG` in POSIX. - file-too-large, - /// Illegal byte sequence, similar to `EILSEQ` in POSIX. - illegal-byte-sequence, - /// Operation in progress, similar to `EINPROGRESS` in POSIX. - in-progress, - /// Interrupted function, similar to `EINTR` in POSIX. - interrupted, - /// Invalid argument, similar to `EINVAL` in POSIX. - invalid, - /// I/O error, similar to `EIO` in POSIX. - io, - /// Is a directory, similar to `EISDIR` in POSIX. - is-directory, - /// Too many levels of symbolic links, similar to `ELOOP` in POSIX. - loop, - /// Too many links, similar to `EMLINK` in POSIX. - too-many-links, - /// Message too large, similar to `EMSGSIZE` in POSIX. - message-size, - /// Filename too long, similar to `ENAMETOOLONG` in POSIX. - name-too-long, - /// No such device, similar to `ENODEV` in POSIX. - no-device, - /// No such file or directory, similar to `ENOENT` in POSIX. - no-entry, - /// No locks available, similar to `ENOLCK` in POSIX. - no-lock, - /// Not enough space, similar to `ENOMEM` in POSIX. - insufficient-memory, - /// No space left on device, similar to `ENOSPC` in POSIX. - insufficient-space, - /// Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX. - not-directory, - /// Directory not empty, similar to `ENOTEMPTY` in POSIX. - not-empty, - /// State not recoverable, similar to `ENOTRECOVERABLE` in POSIX. - not-recoverable, - /// Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX. - unsupported, - /// Inappropriate I/O control operation, similar to `ENOTTY` in POSIX. - no-tty, - /// No such device or address, similar to `ENXIO` in POSIX. - no-such-device, - /// Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX. - overflow, - /// Operation not permitted, similar to `EPERM` in POSIX. - not-permitted, - /// Broken pipe, similar to `EPIPE` in POSIX. - pipe, - /// Read-only file system, similar to `EROFS` in POSIX. - read-only, - /// Invalid seek, similar to `ESPIPE` in POSIX. - invalid-seek, - /// Text file busy, similar to `ETXTBSY` in POSIX. - text-file-busy, - /// Cross-device link, similar to `EXDEV` in POSIX. - cross-device - } - - /// File or memory access pattern advisory information. - enum advice { - /// The application has no advice to give on its behavior with respect - /// to the specified data. - normal, - /// The application expects to access the specified data sequentially - /// from lower offsets to higher offsets. - sequential, - /// The application expects to access the specified data in a random - /// order. - random, - /// The application expects to access the specified data in the near - /// future. - will-need, - /// The application expects that it will not access the specified data - /// in the near future. - dont-need, - /// The application expects to access the specified data once and then - /// not reuse it thereafter. - no-reuse - } - - /// A 128-bit hash value, split into parts because wasm doesn't have a - /// 128-bit integer type. - record metadata-hash-value { - /// 64 bits of a 128-bit hash value. - lower: u64, - /// Another 64 bits of a 128-bit hash value. - upper: u64, - } - - /// A descriptor is a reference to a filesystem object, which may be a file, - /// directory, named pipe, special file, or other object on which filesystem - /// calls may be made. - resource descriptor { - - /// Provide file advisory information on a descriptor. - /// - /// This is similar to `posix_fadvise` in POSIX. - advise: func(offset: filesize, length: filesize, advice: advice) -> result<_, error-code>; - - /// Return a stream for appending to a file, if available. - /// - /// May fail with an error-code describing why the file cannot be appended. - /// - /// Note: This allows using `write-stream`, which is similar to `write` with - /// `O_APPEND` in in POSIX. - append-via-stream: func() -> result; - - /// Create a directory. - /// - /// Note: This is similar to `mkdirat` in POSIX. - create-directory-at: func(path: string) -> result<_, error-code>; - - /// Get flags associated with a descriptor. - /// - /// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. - /// - /// Note: This returns the value that was the `fs_flags` value returned - /// from `fdstat_get` in earlier versions of WASI. - get-flags: func() -> result; - - /// Get the dynamic type of a descriptor. - /// - /// Note: This returns the same value as the `type` field of the `fd-stat` - /// returned by `stat`, `stat-at` and similar. - /// - /// Note: This returns similar flags to the `st_mode & S_IFMT` value provided - /// by `fstat` in POSIX. - /// - /// Note: This returns the value that was the `fs_filetype` value returned - /// from `fdstat_get` in earlier versions of WASI. - get-type: func() -> result; - - /// Test whether two descriptors refer to the same filesystem object. - /// - /// In POSIX, this corresponds to testing whether the two descriptors have the - /// same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers. - /// wasi-filesystem does not expose device and inode numbers, so this function - /// may be used instead. - is-same-object: func(other: borrow) -> bool; - - /// Create a hard link. - /// - /// Note: This is similar to `linkat` in POSIX. - link-at: func(old-path-flags: path-flags, old-path: string, new-descriptor: borrow, new-path: string) -> result<_, error-code>; - - /// Return a hash of the metadata associated with a filesystem object referred - /// to by a descriptor. - /// - /// This returns a hash of the last-modification timestamp and file size, and - /// may also include the inode number, device number, birth timestamp, and - /// other metadata fields that may change when the file is modified or - /// replaced. It may also include a secret value chosen by the - /// implementation and not otherwise exposed. - /// - /// Implementations are encourated to provide the following properties: - /// - /// - If the file is not modified or replaced, the computed hash value should - /// usually not change. - /// - If the object is modified or replaced, the computed hash value should - /// usually change. - /// - The inputs to the hash should not be easily computable from the - /// computed hash. - /// - /// However, none of these is required. - metadata-hash: func() -> result; - - /// Return a hash of the metadata associated with a filesystem object referred - /// to by a directory descriptor and a relative path. - /// - /// This performs the same hash computation as `metadata-hash`. - metadata-hash-at: func(path-flags: path-flags, path: string) -> result; - - /// Open a file or directory. - /// - /// The returned descriptor is not guaranteed to be the lowest-numbered - /// descriptor not currently open/ it is randomized to prevent applications - /// from depending on making assumptions about indexes, since this is - /// error-prone in multi-threaded contexts. The returned descriptor is - /// guaranteed to be less than 2**31. - /// - /// If `flags` contains `descriptor-flags::mutate-directory`, and the base - /// descriptor doesn't have `descriptor-flags::mutate-directory` set, - /// `open-at` fails with `error-code::read-only`. - /// - /// If `flags` contains `write` or `mutate-directory`, or `open-flags` - /// contains `truncate` or `create`, and the base descriptor doesn't have - /// `descriptor-flags::mutate-directory` set, `open-at` fails with - /// `error-code::read-only`. - /// - /// Note: This is similar to `openat` in POSIX. - open-at: func(path-flags: path-flags, path: string, open-flags: open-flags, %flags: descriptor-flags) -> result; - - /// Read from a descriptor, without using and updating the descriptor's offset. - /// - /// This function returns a list of bytes containing the data that was - /// read, along with a bool which, when true, indicates that the end of the - /// file was reached. The returned list will contain up to `length` bytes; it - /// may return fewer than requested, if the end of the file is reached or - /// if the I/O operation is interrupted. - /// - /// In the future, this may change to return a `stream`. - /// - /// Note: This is similar to `pread` in POSIX. - read: func(length: filesize, offset: filesize) -> result, bool>, error-code>; - - /// Read directory entries from a directory. - /// - /// On filesystems where directories contain entries referring to themselves - /// and their parents, often named `.` and `..` respectively, these entries - /// are omitted. - /// - /// This always returns a new stream which starts at the beginning of the - /// directory. Multiple streams may be active on the same directory, and they - /// do not interfere with each other. - read-directory: func() -> result; - - /// Return a stream for reading from a file, if available. - /// - /// May fail with an error-code describing why the file cannot be read. - /// - /// Multiple read, write, and append streams may be active on the same open - /// file and they do not interfere with each other. - /// - /// Note: This allows using `read-stream`, which is similar to `read` in POSIX. - read-via-stream: func(offset: filesize) -> result; - - /// Read the contents of a symbolic link. - /// - /// If the contents contain an absolute or rooted path in the underlying - /// filesystem, this function fails with `error-code::not-permitted`. - /// - /// Note: This is similar to `readlinkat` in POSIX. - readlink-at: func(path: string) -> result; - - /// Remove a directory. - /// - /// Return `error-code::not-empty` if the directory is not empty. - /// - /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. - remove-directory-at: func(path: string) -> result<_, error-code>; - - /// Rename a filesystem object. - /// - /// Note: This is similar to `renameat` in POSIX. - rename-at: func(old-path: string, new-descriptor: borrow, new-path: string) -> result<_, error-code>; - - /// Adjust the size of an open file. If this increases the file's size, the - /// extra bytes are filled with zeros. - /// - /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. - set-size: func(size: filesize) -> result<_, error-code>; - - /// Adjust the timestamps of an open file or directory. - /// - /// Note: This is similar to `futimens` in POSIX. - /// - /// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. - set-times: func(data-access-timestamp: new-timestamp, data-modification-timestamp: new-timestamp) -> result<_, error-code>; - - /// Adjust the timestamps of a file or directory. - /// - /// Note: This is similar to `utimensat` in POSIX. - /// - /// Note: This was called `path_filestat_set_times` in earlier versions of - /// WASI. - set-times-at: func(path-flags: path-flags, path: string, data-access-timestamp: new-timestamp, data-modification-timestamp: new-timestamp) -> result<_, error-code>; - - /// Return the attributes of an open file or directory. - /// - /// Note: This is similar to `fstat` in POSIX, except that it does not return - /// device and inode information. For testing whether two descriptors refer to - /// the same underlying filesystem object, use `is-same-object`. To obtain - /// additional data that can be used do determine whether a file has been - /// modified, use `metadata-hash`. - /// - /// Note: This was called `fd_filestat_get` in earlier versions of WASI. - stat: func() -> result; - - /// Return the attributes of a file or directory. - /// - /// Note: This is similar to `fstatat` in POSIX, except that it does not - /// return device and inode information. See the `stat` description for a - /// discussion of alternatives. - /// - /// Note: This was called `path_filestat_get` in earlier versions of WASI. - stat-at: func(path-flags: path-flags, path: string) -> result; - - /// Create a symbolic link (also known as a "symlink"). - /// - /// If `old-path` starts with `/`, the function fails with - /// `error-code::not-permitted`. - /// - /// Note: This is similar to `symlinkat` in POSIX. - symlink-at: func(old-path: string, new-path: string) -> result<_, error-code>; - - /// Synchronize the data and metadata of a file to disk. - /// - /// This function succeeds with no effect if the file descriptor is not - /// opened for writing. - /// - /// Note: This is similar to `fsync` in POSIX. - sync: func() -> result<_, error-code>; - - /// Synchronize the data of a file to disk. - /// - /// This function succeeds with no effect if the file descriptor is not - /// opened for writing. - /// - /// Note: This is similar to `fdatasync` in POSIX. - sync-data: func() -> result<_, error-code>; - - /// Unlink a filesystem object that is not a directory. - /// - /// Return `error-code::is-directory` if the path refers to a directory. - /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. - unlink-file-at: func(path: string) -> result<_, error-code>; - - /// Write to a descriptor, without using and updating the descriptor's offset. - /// - /// It is valid to write past the end of a file; the file is extended to the - /// extent of the write, with bytes between the previous end and the start of - /// the write set to zero. - /// - /// In the future, this may change to take a `stream`. - /// - /// Note: This is similar to `pwrite` in POSIX. - write: func(buffer: list, offset: filesize) -> result; - - /// Return a stream for writing to a file, if available. - /// - /// May fail with an error-code describing why the file cannot be written. - /// - /// Note: This allows using `write-stream`, which is similar to `write` in - /// POSIX. - write-via-stream: func(offset: filesize) -> result; - } - - /// A stream of directory entries. - resource directory-entry-stream { - - /// Read a single directory entry from a `directory-entry-stream`. - read-directory-entry: func() -> result, error-code>; - } - - /// Attempts to extract a filesystem-related `error-code` from the stream - /// `error` provided. - /// - /// Stream operations which return `stream-error::last-operation-failed` - /// have a payload with more information about the operation that failed. - /// This payload can be passed through to this function to see if there's - /// filesystem-related information about the error to return. - /// - /// Note that this function is fallible because not all stream-related - /// errors are filesystem-related errors. - filesystem-error-code: func(err: borrow) -> option; - } - - interface preopens { - use types.{descriptor}; - - /// Return the set of preopened directories, and their path. - get-directories: func() -> list>; - } -} - -package wasi:sockets@0.2.0 { - interface network { - /// An opaque resource that represents access to (a subset of) the network. - /// This enables context-based security for networking. - /// There is no need for this to map 1:1 to a physical network interface. - resource network; - - /// Error codes. - /// - /// In theory, every API can return any error code. - /// In practice, API's typically only return the errors documented per API - /// combined with a couple of errors that are always possible: - /// - `unknown` - /// - `access-denied` - /// - `not-supported` - /// - `out-of-memory` - /// - `concurrency-conflict` - /// - /// See each individual API for what the POSIX equivalents are. They sometimes differ - /// per API. - enum error-code { - /// Unknown error - unknown, - /// Access denied. - /// - /// POSIX equivalent: EACCES, EPERM - access-denied, - /// The operation is not supported. - /// - /// POSIX equivalent: EOPNOTSUPP - not-supported, - /// One of the arguments is invalid. - /// - /// POSIX equivalent: EINVAL - invalid-argument, - /// Not enough memory to complete the operation. - /// - /// POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY - out-of-memory, - /// The operation timed out before it could finish completely. - timeout, - /// This operation is incompatible with another asynchronous operation that is already - /// in progress. - /// - /// POSIX equivalent: EALREADY - concurrency-conflict, - /// Trying to finish an asynchronous operation that: - /// - has not been started yet, or: - /// - was already finished by a previous `finish-*` call. - /// - /// Note: this is scheduled to be removed when `future`s are natively supported. - not-in-progress, - /// The operation has been aborted because it could not be completed immediately. - /// - /// Note: this is scheduled to be removed when `future`s are natively supported. - would-block, - /// The operation is not valid in the socket's current state. - invalid-state, - /// A new socket resource could not be created because of a system limit. - new-socket-limit, - /// A bind operation failed because the provided address is not an address that the - /// `network` can bind to. - address-not-bindable, - /// A bind operation failed because the provided address is already in use or because - /// there are no ephemeral ports available. - address-in-use, - /// The remote address is not reachable - remote-unreachable, - /// The TCP connection was forcefully rejected - connection-refused, - /// The TCP connection was reset. - connection-reset, - /// A TCP connection was aborted. - connection-aborted, - /// The size of a datagram sent to a UDP socket exceeded the maximum - /// supported size. - datagram-too-large, - /// Name does not exist or has no suitable associated IP addresses. - name-unresolvable, - /// A temporary failure in name resolution occurred. - temporary-resolver-failure, - /// A permanent failure in name resolution occurred. - permanent-resolver-failure - } - enum ip-address-family { - /// Similar to `AF_INET` in POSIX. - ipv4, - /// Similar to `AF_INET6` in POSIX. - ipv6 - } - type ipv4-address = tuple; - type ipv6-address = tuple; - variant ip-address { - ipv4(ipv4-address), - ipv6(ipv6-address), - } - record ipv4-socket-address { - /// sin_port - port: u16, - /// sin_addr - address: ipv4-address, - } - record ipv6-socket-address { - /// sin6_port - port: u16, - /// sin6_flowinfo - flow-info: u32, - /// sin6_addr - address: ipv6-address, - /// sin6_scope_id - scope-id: u32, - } - variant ip-socket-address { - ipv4(ipv4-socket-address), - ipv6(ipv6-socket-address), - } - } - - /// This interface provides a value-export of the default network handle.. - interface instance-network { - use network.{network}; - - /// Get a handle to the default network. - instance-network: func() -> network; - } - - interface ip-name-lookup { - use wasi:io/poll@0.2.0.{pollable}; - use network.{network}; - use network.{error-code}; - use network.{ip-address}; - resource resolve-address-stream { - - /// Returns the next address from the resolver. - /// - /// This function should be called multiple times. On each call, it will - /// return the next address in connection order preference. If all - /// addresses have been exhausted, this function returns `none`. - /// - /// This function never returns IPv4-mapped IPv6 addresses. - /// - /// # Typical errors - /// - `name-unresolvable`: Name does not exist or has no suitable associated - /// IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) - /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. - /// (EAI_AGAIN) - /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. - /// (EAI_FAIL) - /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) - resolve-next-address: func() -> result, error-code>; - - /// Create a `pollable` which will resolve once the stream is ready for I/O. - /// - /// Note: this function is here for WASI Preview2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - subscribe: func() -> pollable; - } - - /// Resolve an internet host name to a list of IP addresses. - /// - /// Unicode domain names are automatically converted to ASCII using IDNA encoding. - /// If the input is an IP address string, the address is parsed and returned - /// as-is without making any external requests. - /// - /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. - /// - /// This function never blocks. It either immediately fails or immediately - /// returns successfully with a `resolve-address-stream` that can be used - /// to (asynchronously) fetch the results. - /// - /// # Typical errors - /// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address. - /// - /// # References: - /// - - /// - - /// - - /// - - resolve-addresses: func(network: borrow, name: string) -> result; - } - - interface tcp { - use wasi:io/streams@0.2.0.{input-stream}; - use wasi:io/streams@0.2.0.{output-stream}; - use wasi:io/poll@0.2.0.{pollable}; - use wasi:clocks/monotonic-clock@0.2.0.{duration}; - use network.{network}; - use network.{error-code}; - use network.{ip-socket-address}; - use network.{ip-address-family}; - enum shutdown-type { - /// Similar to `SHUT_RD` in POSIX. - receive, - /// Similar to `SHUT_WR` in POSIX. - send, - /// Similar to `SHUT_RDWR` in POSIX. - both - } - - /// A TCP socket resource. - /// - /// The socket can be in one of the following states: - /// - `unbound` - /// - `bind-in-progress` - /// - `bound` (See note below) - /// - `listen-in-progress` - /// - `listening` - /// - `connect-in-progress` - /// - `connected` - /// - `closed` - /// See - /// for a more information. - /// - /// Note: Except where explicitly mentioned, whenever this documentation uses - /// the term "bound" without backticks it actually means: in the `bound` state *or - /// higher*. - /// (i.e. `bound`, `listen-in-progress`, `listening`, `connect-in-progress` or `connected`) - /// - /// In addition to the general error codes documented on the - /// `network::error-code` type, TCP socket methods may always return - /// `error(invalid-state)` when in the `closed` state. - resource tcp-socket { - - /// Accept a new client socket. - /// - /// The returned socket is bound and in the `connected` state. The following properties - /// are inherited from the listener socket: - /// - `address-family` - /// - `keep-alive-enabled` - /// - `keep-alive-idle-time` - /// - `keep-alive-interval` - /// - `keep-alive-count` - /// - `hop-limit` - /// - `receive-buffer-size` - /// - `send-buffer-size` - /// - /// On success, this function returns the newly accepted client socket along with - /// a pair of streams that can be used to read & write to the connection. - /// - /// # Typical errors - /// - `invalid-state`: Socket is not in the `listening` state. (EINVAL) - /// - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) - /// - `connection-aborted`: An incoming connection was pending, but was terminated - /// by the client before this listener could accept it. (ECONNABORTED) - /// - `new-socket-limit`: The new socket resource could not be created because of - /// a system limit. (EMFILE, ENFILE) - /// - /// # References - /// - - /// - - /// - - /// - - accept: func() -> result, error-code>; - - /// Whether this is a IPv4 or IPv6 socket. - /// - /// Equivalent to the SO_DOMAIN socket option. - address-family: func() -> ip-address-family; - finish-bind: func() -> result<_, error-code>; - finish-connect: func() -> result, error-code>; - finish-listen: func() -> result<_, error-code>; - - /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The TTL value must be 1 or higher. - hop-limit: func() -> result; - - /// Whether the socket is in the `listening` state. - /// - /// Equivalent to the SO_ACCEPTCONN socket option. - is-listening: func() -> bool; - - /// The maximum amount of keepalive packets TCP should send before aborting the connection. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// Any other value will never cause an error, but it might be silently clamped and/or - /// rounded. - /// I.e. after setting a value, reading the same setting back may return a different - /// value. - /// - /// Equivalent to the TCP_KEEPCNT socket option. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - keep-alive-count: func() -> result; - - /// Enables or disables keepalive. - /// - /// The keepalive behavior can be adjusted using: - /// - `keep-alive-idle-time` - /// - `keep-alive-interval` - /// - `keep-alive-count` - /// These properties can be configured while `keep-alive-enabled` is false, but only - /// come into effect when `keep-alive-enabled` is true. - /// - /// Equivalent to the SO_KEEPALIVE socket option. - keep-alive-enabled: func() -> result; - - /// Amount of time the connection has to be idle before TCP starts sending keepalive - /// packets. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// Any other value will never cause an error, but it might be silently clamped and/or - /// rounded. - /// I.e. after setting a value, reading the same setting back may return a different - /// value. - /// - /// Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS) - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - keep-alive-idle-time: func() -> result; - - /// The time between keepalive packets. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// Any other value will never cause an error, but it might be silently clamped and/or - /// rounded. - /// I.e. after setting a value, reading the same setting back may return a different - /// value. - /// - /// Equivalent to the TCP_KEEPINTVL socket option. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - keep-alive-interval: func() -> result; - - /// Get the bound local address. - /// - /// POSIX mentions: - /// > If the socket has not been bound to a local name, the value - /// > stored in the object pointed to by `address` is unspecified. - /// - /// WASI is stricter and requires `local-address` to return `invalid-state` when the - /// socket hasn't been bound yet. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not bound to any local address. - /// - /// # References - /// - - /// - - /// - - /// - - local-address: func() -> result; - - /// The kernel buffer space reserved for sends/receives on this socket. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// Any other value will never cause an error, but it might be silently clamped and/or - /// rounded. - /// I.e. after setting a value, reading the same setting back may return a different - /// value. - /// - /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - receive-buffer-size: func() -> result; - - /// Get the remote address. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - remote-address: func() -> result; - send-buffer-size: func() -> result; - set-hop-limit: func(value: u8) -> result<_, error-code>; - set-keep-alive-count: func(value: u32) -> result<_, error-code>; - set-keep-alive-enabled: func(value: bool) -> result<_, error-code>; - set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>; - set-keep-alive-interval: func(value: duration) -> result<_, error-code>; - - /// Hints the desired listen queue size. Implementations are free to ignore this. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// Any other value will never cause an error, but it might be silently clamped and/or - /// rounded. - /// - /// # Typical errors - /// - `not-supported`: (set) The platform does not support changing the backlog - /// size after the initial listen. - /// - `invalid-argument`: (set) The provided value was 0. - /// - `invalid-state`: (set) The socket is in the `connect-in-progress` or - /// `connected` state. - set-listen-backlog-size: func(value: u64) -> result<_, error-code>; - set-receive-buffer-size: func(value: u64) -> result<_, error-code>; - set-send-buffer-size: func(value: u64) -> result<_, error-code>; - - /// Initiate a graceful shutdown. - /// - /// - `receive`: The socket is not expecting to receive any data from - /// the peer. The `input-stream` associated with this socket will be - /// closed. Any data still in the receive queue at time of calling - /// this method will be discarded. - /// - `send`: The socket has no more data to send to the peer. The `output-stream` - /// associated with this socket will be closed and a FIN packet will be sent. - /// - `both`: Same effect as `receive` & `send` combined. - /// - /// This function is idempotent. Shutting a down a direction more than once - /// has no effect and returns `ok`. - /// - /// The shutdown function does not close (drop) the socket. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code>; - - /// Bind the socket to a specific network on the provided IP address and port. - /// - /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the - /// implementation to decide which - /// network interface(s) to bind to. - /// If the TCP/UDP port is zero, the socket will be bound to a random free port. - /// - /// Bind can be attempted multiple times on the same socket, even with - /// different arguments on each iteration. But never concurrently and - /// only as long as the previous bind failed. Once a bind succeeds, the - /// binding can't be changed anymore. - /// - /// # Typical errors - /// - `invalid-argument`: The `local-address` has the wrong address family. - /// (EAFNOSUPPORT, EFAULT on Windows) - /// - `invalid-argument`: `local-address` is not a unicast address. (EINVAL) - /// - `invalid-argument`: `local-address` is an IPv4-mapped IPv6 address. - /// (EINVAL) - /// - `invalid-state`: The socket is already bound. (EINVAL) - /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS - /// on Windows) - /// - `address-in-use`: Address is already in use. (EADDRINUSE) - /// - `address-not-bindable`: `local-address` is not an address that the `network` - /// can bind to. (EADDRNOTAVAIL) - /// - `not-in-progress`: A `bind` operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. - /// (EWOULDBLOCK, EAGAIN) - /// - /// # Implementors note - /// When binding to a non-zero port, this bind operation shouldn't be affected by - /// the TIME_WAIT - /// state of a recently closed socket on the same local address. In practice this - /// means that the SO_REUSEADDR - /// socket option should be set implicitly on all platforms, except on Windows where - /// this is the default behavior - /// and SO_REUSEADDR performs something different entirely. - /// - /// Unlike in POSIX, in WASI the bind operation is async. This enables - /// interactive WASI hosts to inject permission prompts. Runtimes that - /// don't want to make use of this ability can simply call the native - /// `bind` as part of either `start-bind` or `finish-bind`. - /// - /// # References - /// - - /// - - /// - - /// - - start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; - - /// Connect to a remote endpoint. - /// - /// On success: - /// - the socket is transitioned into the `connection` state. - /// - a pair of streams is returned that can be used to read & write to the connection - /// - /// After a failed connection attempt, the socket will be in the `closed` - /// state and the only valid action left is to `drop` the socket. A single - /// socket can not be used to connect more than once. - /// - /// # Typical errors - /// - `invalid-argument`: The `remote-address` has the wrong address family. - /// (EAFNOSUPPORT) - /// - `invalid-argument`: `remote-address` is not a unicast address. (EINVAL, - /// ENETUNREACH on Linux, EAFNOSUPPORT on MacOS) - /// - `invalid-argument`: `remote-address` is an IPv4-mapped IPv6 address. - /// (EINVAL, EADDRNOTAVAIL on Illumos) - /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY - /// (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) - /// - `invalid-argument`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL - /// on Windows) - /// - `invalid-argument`: The socket is already attached to a different network. - /// The `network` passed to `connect` must be identical to the one passed to `bind`. - /// - `invalid-state`: The socket is already in the `connected` state. - /// (EISCONN) - /// - `invalid-state`: The socket is already in the `listening` state. - /// (EOPNOTSUPP, EINVAL on Windows) - /// - `timeout`: Connection timed out. (ETIMEDOUT) - /// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) - /// - `connection-reset`: The connection was reset. (ECONNRESET) - /// - `connection-aborted`: The connection was aborted. (ECONNABORTED) - /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, - /// EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) - /// - `address-in-use`: Tried to perform an implicit bind, but there were - /// no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) - /// - `not-in-progress`: A connect operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. - /// (EWOULDBLOCK, EAGAIN) - /// - /// # Implementors note - /// The POSIX equivalent of `start-connect` is the regular `connect` syscall. - /// Because all WASI sockets are non-blocking this is expected to return - /// EINPROGRESS, which should be translated to `ok()` in WASI. - /// - /// The POSIX equivalent of `finish-connect` is a `poll` for event `POLLOUT` - /// with a timeout of 0 on the socket descriptor. Followed by a check for - /// the `SO_ERROR` socket option, in case the poll signaled readiness. - /// - /// # References - /// - - /// - - /// - - /// - - start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code>; - - /// Start listening for new connections. - /// - /// Transitions the socket into the `listening` state. - /// - /// Unlike POSIX, the socket must already be explicitly bound. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not bound to any local address. (EDESTADDRREQ) - /// - `invalid-state`: The socket is already in the `connected` state. - /// (EISCONN, EINVAL on BSD) - /// - `invalid-state`: The socket is already in the `listening` state. - /// - `address-in-use`: Tried to perform an implicit bind, but there were - /// no ephemeral ports available. (EADDRINUSE) - /// - `not-in-progress`: A listen operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. - /// (EWOULDBLOCK, EAGAIN) - /// - /// # Implementors note - /// Unlike in POSIX, in WASI the listen operation is async. This enables - /// interactive WASI hosts to inject permission prompts. Runtimes that - /// don't want to make use of this ability can simply call the native - /// `listen` as part of either `start-listen` or `finish-listen`. - /// - /// # References - /// - - /// - - /// - - /// - - start-listen: func() -> result<_, error-code>; - - /// Create a `pollable` which can be used to poll for, or block on, - /// completion of any of the asynchronous operations of this socket. - /// - /// When `finish-bind`, `finish-listen`, `finish-connect` or `accept` - /// return `error(would-block)`, this pollable can be used to wait for - /// their success or failure, after which the method can be retried. - /// - /// The pollable is not limited to the async operation that happens to be - /// in progress at the time of calling `subscribe` (if any). Theoretically, - /// `subscribe` only has to be called once per socket and can then be - /// (re)used for the remainder of the socket's lifetime. - /// - /// See - /// for a more information. - /// - /// Note: this function is here for WASI Preview2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - subscribe: func() -> pollable; - } - } - - interface tcp-create-socket { - use network.{network}; - use network.{error-code}; - use network.{ip-address-family}; - use tcp.{tcp-socket}; - - /// Create a new TCP socket. - /// - /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. - /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. - /// - /// This function does not require a network capability handle. This is considered - /// to be safe because - /// at time of creation, the socket is not bound to any `network` yet. Up to the moment - /// `bind`/`connect` - /// is called, the socket is effectively an in-memory configuration object, unable - /// to communicate with the outside world. - /// - /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous - /// operations. - /// - /// # Typical errors - /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) - /// - `new-socket-limit`: The new socket resource could not be created because of - /// a system limit. (EMFILE, ENFILE) - /// - /// # References - /// - - /// - - /// - - /// - - create-tcp-socket: func(address-family: ip-address-family) -> result; - } - - interface udp { - use wasi:io/poll@0.2.0.{pollable}; - use network.{network}; - use network.{error-code}; - use network.{ip-socket-address}; - use network.{ip-address-family}; - - /// A received datagram. - record incoming-datagram { - /// The payload. - /// - /// Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. - data: list, - /// The source address. - /// - /// This field is guaranteed to match the remote address the stream was initialized - /// with, if any. - /// - /// Equivalent to the `src_addr` out parameter of `recvfrom`. - remote-address: ip-socket-address, - } - - /// A datagram to be sent out. - record outgoing-datagram { - /// The payload. - data: list, - /// The destination address. - /// - /// The requirements on this field depend on how the stream was initialized: - /// - with a remote address: this field must be None or match the stream's remote - /// address exactly. - /// - without a remote address: this field is required. - /// - /// If this value is None, the send operation is equivalent to `send` in POSIX. Otherwise - /// it is equivalent to `sendto`. - remote-address: option, - } - - /// A UDP socket handle. - resource udp-socket { - - /// Whether this is a IPv4 or IPv6 socket. - /// - /// Equivalent to the SO_DOMAIN socket option. - address-family: func() -> ip-address-family; - finish-bind: func() -> result<_, error-code>; - - /// Get the current bound address. - /// - /// POSIX mentions: - /// > If the socket has not been bound to a local name, the value - /// > stored in the object pointed to by `address` is unspecified. - /// - /// WASI is stricter and requires `local-address` to return `invalid-state` when the - /// socket hasn't been bound yet. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not bound to any local address. - /// - /// # References - /// - - /// - - /// - - /// - - local-address: func() -> result; - - /// The kernel buffer space reserved for sends/receives on this socket. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// Any other value will never cause an error, but it might be silently clamped and/or - /// rounded. - /// I.e. after setting a value, reading the same setting back may return a different - /// value. - /// - /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - receive-buffer-size: func() -> result; - - /// Get the address the socket is currently streaming to. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not streaming to a specific remote address. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - remote-address: func() -> result; - send-buffer-size: func() -> result; - set-receive-buffer-size: func(value: u64) -> result<_, error-code>; - set-send-buffer-size: func(value: u64) -> result<_, error-code>; - set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; - - /// Bind the socket to a specific network on the provided IP address and port. - /// - /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the - /// implementation to decide which - /// network interface(s) to bind to. - /// If the port is zero, the socket will be bound to a random free port. - /// - /// # Typical errors - /// - `invalid-argument`: The `local-address` has the wrong address family. - /// (EAFNOSUPPORT, EFAULT on Windows) - /// - `invalid-state`: The socket is already bound. (EINVAL) - /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS - /// on Windows) - /// - `address-in-use`: Address is already in use. (EADDRINUSE) - /// - `address-not-bindable`: `local-address` is not an address that the `network` - /// can bind to. (EADDRNOTAVAIL) - /// - `not-in-progress`: A `bind` operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. - /// (EWOULDBLOCK, EAGAIN) - /// - /// # Implementors note - /// Unlike in POSIX, in WASI the bind operation is async. This enables - /// interactive WASI hosts to inject permission prompts. Runtimes that - /// don't want to make use of this ability can simply call the native - /// `bind` as part of either `start-bind` or `finish-bind`. - /// - /// # References - /// - - /// - - /// - - /// - - start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; - - /// Set up inbound & outbound communication channels, optionally to a specific peer. - /// - /// This function only changes the local socket configuration and does not generate - /// any network traffic. - /// On success, the `remote-address` of the socket is updated. The `local-address` - /// may be updated as well, - /// based on the best network path to `remote-address`. - /// - /// When a `remote-address` is provided, the returned streams are limited to communicating - /// with that specific peer: - /// - `send` can only be used to send to this destination. - /// - `receive` will only return datagrams sent from the provided `remote-address`. - /// - /// This method may be called multiple times on the same socket to change its association, - /// but - /// only the most recently returned pair of streams will be operational. Implementations - /// may trap if - /// the streams returned by a previous invocation haven't been dropped yet before - /// calling `stream` again. - /// - /// The POSIX equivalent in pseudo-code is: - /// ```text - /// if (was previously connected) { - /// connect(s, AF_UNSPEC) - /// } - /// if (remote_address is Some) { - /// connect(s, remote_address) - /// } - /// ``` - /// - /// Unlike in POSIX, the socket must already be explicitly bound. - /// - /// # Typical errors - /// - `invalid-argument`: The `remote-address` has the wrong address family. - /// (EAFNOSUPPORT) - /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY - /// (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, - /// EADDRNOTAVAIL) - /// - `invalid-state`: The socket is not bound. - /// - `address-in-use`: Tried to perform an implicit bind, but there were - /// no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) - /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, - /// ENETRESET, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) - /// - `connection-refused`: The connection was refused. (ECONNREFUSED) - /// - /// # References - /// - - /// - - /// - - /// - - %stream: func(remote-address: option) -> result, error-code>; - - /// Create a `pollable` which will resolve once the socket is ready for I/O. - /// - /// Note: this function is here for WASI Preview2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - subscribe: func() -> pollable; - - /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The TTL value must be 1 or higher. - unicast-hop-limit: func() -> result; - } - resource incoming-datagram-stream { - - /// Receive messages on the socket. - /// - /// This function attempts to receive up to `max-results` datagrams on the socket - /// without blocking. - /// The returned list may contain fewer elements than requested, but never more. - /// - /// This function returns successfully with an empty list when either: - /// - `max-results` is 0, or: - /// - `max-results` is greater than 0, but no results are immediately available. - /// This function never returns `error(would-block)`. - /// - /// # Typical errors - /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET - /// on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) - /// - `connection-refused`: The connection was refused. (ECONNREFUSED) - /// - /// # References - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - receive: func(max-results: u64) -> result, error-code>; - - /// Create a `pollable` which will resolve once the stream is ready to receive again. - /// - /// Note: this function is here for WASI Preview2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - subscribe: func() -> pollable; - } - resource outgoing-datagram-stream { - - /// Check readiness for sending. This function never blocks. - /// - /// Returns the number of datagrams permitted for the next call to `send`, - /// or an error. Calling `send` with more datagrams than this function has - /// permitted will trap. - /// - /// When this function returns ok(0), the `subscribe` pollable will - /// become ready when this function will report at least ok(1), or an - /// error. - /// - /// Never returns `would-block`. - check-send: func() -> result; - - /// Send messages on the socket. - /// - /// This function attempts to send all provided `datagrams` on the socket without - /// blocking and - /// returns how many messages were actually sent (or queued for sending). This function - /// never - /// returns `error(would-block)`. If none of the datagrams were able to be sent, `ok(0)` - /// is returned. - /// - /// This function semantically behaves the same as iterating the `datagrams` list - /// and sequentially - /// sending each individual datagram until either the end of the list has been reached - /// or the first error occurred. - /// If at least one datagram has been sent successfully, this function never returns - /// an error. - /// - /// If the input list is empty, the function returns `ok(0)`. - /// - /// Each call to `send` must be permitted by a preceding `check-send`. Implementations - /// must trap if - /// either `check-send` was not called or `datagrams` contains more items than `check-send` - /// permitted. - /// - /// # Typical errors - /// - `invalid-argument`: The `remote-address` has the wrong address family. - /// (EAFNOSUPPORT) - /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY - /// (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, - /// EADDRNOTAVAIL) - /// - `invalid-argument`: The socket is in "connected" mode and `remote-address` - /// is `some` value that does not match the address passed to `stream`. (EISCONN) - /// - `invalid-argument`: The socket is not "connected" and no value for `remote-address` - /// was provided. (EDESTADDRREQ) - /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, - /// ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) - /// - `connection-refused`: The connection was refused. (ECONNREFUSED) - /// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) - /// - /// # References - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - send: func(datagrams: list) -> result; - - /// Create a `pollable` which will resolve once the stream is ready to send again. - /// - /// Note: this function is here for WASI Preview2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - subscribe: func() -> pollable; - } - } - - interface udp-create-socket { - use network.{network}; - use network.{error-code}; - use network.{ip-address-family}; - use udp.{udp-socket}; - - /// Create a new UDP socket. - /// - /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. - /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. - /// - /// This function does not require a network capability handle. This is considered - /// to be safe because - /// at time of creation, the socket is not bound to any `network` yet. Up to the moment - /// `bind` is called, - /// the socket is effectively an in-memory configuration object, unable to communicate - /// with the outside world. - /// - /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous - /// operations. - /// - /// # Typical errors - /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) - /// - `new-socket-limit`: The new socket resource could not be created because of - /// a system limit. (EMFILE, ENFILE) - /// - /// # References: - /// - - /// - - /// - - /// - - create-udp-socket: func(address-family: ip-address-family) -> result; - } -} - -package wasi:clocks@0.2.0 { - /// WASI Monotonic Clock is a clock API intended to let users measure elapsed - /// time. - /// - /// It is intended to be portable at least between Unix-family platforms and - /// Windows. - /// - /// A monotonic clock is a clock which has an unspecified initial value, and - /// successive reads of the clock will produce non-decreasing values. - /// - /// It is intended for measuring elapsed time. - interface monotonic-clock { - use wasi:io/poll@0.2.0.{pollable}; - - /// An instant in time, in nanoseconds. An instant is relative to an - /// unspecified initial value, and can only be compared to instances from - /// the same monotonic-clock. - type instant = u64; - - /// A duration of time, in nanoseconds. - type duration = u64; - - /// Read the current value of the clock. - /// - /// The clock is monotonic, therefore calling this function repeatedly will - /// produce a sequence of non-decreasing values. - now: func() -> instant; - - /// Query the resolution of the clock. Returns the duration of time - /// corresponding to a clock tick. - resolution: func() -> duration; - - /// Create a `pollable` which will resolve once the specified instant - /// occured. - subscribe-instant: func(when: instant) -> pollable; - - /// Create a `pollable` which will resolve once the given duration has - /// elapsed, starting at the time at which this function was called. - /// occured. - subscribe-duration: func(when: duration) -> pollable; - } - - /// WASI Wall Clock is a clock API intended to let users query the current - /// time. The name "wall" makes an analogy to a "clock on the wall", which - /// is not necessarily monotonic as it may be reset. - /// - /// It is intended to be portable at least between Unix-family platforms and - /// Windows. - /// - /// A wall clock is a clock which measures the date and time according to - /// some external reference. - /// - /// External references may be reset, so this clock is not necessarily - /// monotonic, making it unsuitable for measuring elapsed time. - /// - /// It is intended for reporting the current date and time for humans. - interface wall-clock { - /// A time and date in seconds plus nanoseconds. - record datetime { - seconds: u64, - nanoseconds: u32, - } - - /// Read the current value of the clock. - /// - /// This clock is not monotonic, therefore calling this function repeatedly - /// will not necessarily produce a sequence of non-decreasing values. - /// - /// The returned timestamps represent the number of seconds since - /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], - /// also known as [Unix Time]. - /// - /// The nanoseconds field of the output is always less than 1000000000. - /// - /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 - /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time - now: func() -> datetime; - - /// Query the resolution of the clock. - /// - /// The nanoseconds field of the output is always less than 1000000000. - resolution: func() -> datetime; - } -} - -package wasi:io@0.2.0 { - interface error { - /// A resource which represents some error information. - /// - /// The only method provided by this resource is `to-debug-string`, - /// which provides some human-readable information about the error. - /// - /// In the `wasi:io` package, this resource is returned through the - /// `wasi:io/streams/stream-error` type. - /// - /// To provide more specific error information, other interfaces may - /// provide functions to further "downcast" this error into more specific - /// error information. For example, `error`s returned in streams derived - /// from filesystem types to be described using the filesystem's own - /// error-code type, using the function - /// `wasi:filesystem/types/filesystem-error-code`, which takes a parameter - /// `borrow` and returns - /// `option`. - /// - /// The set of functions which can "downcast" an `error` into a more - /// concrete type is open. - resource error { - - /// Returns a string that is suitable to assist humans in debugging - /// this error. - /// - /// WARNING: The returned string should not be consumed mechanically! - /// It may change across platforms, hosts, or other implementation - /// details. Parsing this string is a major platform-compatibility - /// hazard. - to-debug-string: func() -> string; - } - } - - /// A poll API intended to let users wait for I/O events on multiple handles - /// at once. - interface poll { - /// `pollable` represents a single I/O event which may be ready, or not. - resource pollable { - - /// `block` returns immediately if the pollable is ready, and otherwise - /// blocks until ready. - /// - /// This function is equivalent to calling `poll.poll` on a list - /// containing only this pollable. - block: func(); - - /// Return the readiness of a pollable. This function never blocks. - /// - /// Returns `true` when the pollable is ready, and `false` otherwise. - ready: func() -> bool; - } - - /// Poll for completion on a set of pollables. - /// - /// This function takes a list of pollables, which identify I/O sources of - /// interest, and waits until one or more of the events is ready for I/O. - /// - /// The result `list` contains one or more indices of handles in the - /// argument list that is ready for I/O. - /// - /// If the list contains more elements than can be indexed with a `u32` - /// value, this function traps. - /// - /// A timeout can be implemented by adding a pollable from the - /// wasi-clocks API to the list. - /// - /// This function does not return a `result`; polling in itself does not - /// do any I/O so it doesn't fail. If any of the I/O sources identified by - /// the pollables has an error, it is indicated by marking the source as - /// being reaedy for I/O. - poll: func(in: list>) -> list; - } - - /// WASI I/O is an I/O abstraction API which is currently focused on providing - /// stream types. - /// - /// In the future, the component model is expected to add built-in stream types; - /// when it does, they are expected to subsume this API. - interface streams { - use error.{error}; - use poll.{pollable}; - - /// An error for input-stream and output-stream operations. - variant stream-error { - /// The last operation (a write or flush) failed before completion. - /// - /// More information is available in the `error` payload. - last-operation-failed(error), - /// The stream is closed: no more input will be accepted by the - /// stream. A closed output-stream will return this error on all - /// future operations. - closed, - } - - /// An input bytestream. - /// - /// `input-stream`s are *non-blocking* to the extent practical on underlying - /// platforms. I/O operations always return promptly; if fewer bytes are - /// promptly available than requested, they return the number of bytes promptly - /// available, which could even be zero. To wait for data to be available, - /// use the `subscribe` function to obtain a `pollable` which can be polled - /// for using `wasi:io/poll`. - resource input-stream { - - /// Read bytes from a stream, after blocking until at least one byte can - /// be read. Except for blocking, behavior is identical to `read`. - blocking-read: func(len: u64) -> result, stream-error>; - - /// Skip bytes from a stream, after blocking until at least one byte - /// can be skipped. Except for blocking behavior, identical to `skip`. - blocking-skip: func(len: u64) -> result; - - /// Perform a non-blocking read from the stream. - /// - /// When the source of a `read` is binary data, the bytes from the source - /// are returned verbatim. When the source of a `read` is known to the - /// implementation to be text, bytes containing the UTF-8 encoding of the - /// text are returned. - /// - /// This function returns a list of bytes containing the read data, - /// when successful. The returned list will contain up to `len` bytes; - /// it may return fewer than requested, but not more. The list is - /// empty when no bytes are available for reading at this time. The - /// pollable given by `subscribe` will be ready when more bytes are - /// available. - /// - /// This function fails with a `stream-error` when the operation - /// encounters an error, giving `last-operation-failed`, or when the - /// stream is closed, giving `closed`. - /// - /// When the caller gives a `len` of 0, it represents a request to - /// read 0 bytes. If the stream is still open, this call should - /// succeed and return an empty list, or otherwise fail with `closed`. - /// - /// The `len` parameter is a `u64`, which could represent a list of u8 which - /// is not possible to allocate in wasm32, or not desirable to allocate as - /// as a return value by the callee. The callee may return a list of bytes - /// less than `len` in size while more bytes are available for reading. - read: func(len: u64) -> result, stream-error>; - - /// Skip bytes from a stream. Returns number of bytes skipped. - /// - /// Behaves identical to `read`, except instead of returning a list - /// of bytes, returns the number of bytes consumed from the stream. - skip: func(len: u64) -> result; - - /// Create a `pollable` which will resolve once either the specified stream - /// has bytes available to read or the other end of the stream has been - /// closed. - /// The created `pollable` is a child resource of the `input-stream`. - /// Implementations may trap if the `input-stream` is dropped before - /// all derived `pollable`s created with this function are dropped. - subscribe: func() -> pollable; - } - - /// An output bytestream. - /// - /// `output-stream`s are *non-blocking* to the extent practical on - /// underlying platforms. Except where specified otherwise, I/O operations also - /// always return promptly, after the number of bytes that can be written - /// promptly, which could even be zero. To wait for the stream to be ready to - /// accept data, the `subscribe` function to obtain a `pollable` which can be - /// polled for using `wasi:io/poll`. - resource output-stream { - - /// Request to flush buffered output, and block until flush completes - /// and stream is ready for writing again. - blocking-flush: func() -> result<_, stream-error>; - - /// Read from one stream and write to another, with blocking. - /// - /// This is similar to `splice`, except that it blocks until the - /// `output-stream` is ready for writing, and the `input-stream` - /// is ready for reading, before performing the `splice`. - blocking-splice: func(src: borrow, len: u64) -> result; - - /// Perform a write of up to 4096 bytes, and then flush the stream. Block - /// until all of these operations are complete, or an error occurs. - /// - /// This is a convenience wrapper around the use of `check-write`, - /// `subscribe`, `write`, and `flush`, and is implemented with the - /// following pseudo-code: - /// - /// ```text - /// let pollable = this.subscribe(); - /// while !contents.is_empty() { - /// // Wait for the stream to become writable - /// pollable.block(); - /// let Ok(n) = this.check-write(); // eliding error handling - /// let len = min(n, contents.len()); - /// let (chunk, rest) = contents.split_at(len); - /// this.write(chunk ); // eliding error handling - /// contents = rest; - /// } - /// this.flush(); - /// // Wait for completion of `flush` - /// pollable.block(); - /// // Check for any errors that arose during `flush` - /// let _ = this.check-write(); // eliding error handling - /// ``` - blocking-write-and-flush: func(contents: list) -> result<_, stream-error>; - - /// Perform a write of up to 4096 zeroes, and then flush the stream. - /// Block until all of these operations are complete, or an error - /// occurs. - /// - /// This is a convenience wrapper around the use of `check-write`, - /// `subscribe`, `write-zeroes`, and `flush`, and is implemented with - /// the following pseudo-code: - /// - /// ```text - /// let pollable = this.subscribe(); - /// while num_zeroes != 0 { - /// // Wait for the stream to become writable - /// pollable.block(); - /// let Ok(n) = this.check-write(); // eliding error handling - /// let len = min(n, num_zeroes); - /// this.write-zeroes(len); // eliding error handling - /// num_zeroes -= len; - /// } - /// this.flush(); - /// // Wait for completion of `flush` - /// pollable.block(); - /// // Check for any errors that arose during `flush` - /// let _ = this.check-write(); // eliding error handling - /// ``` - blocking-write-zeroes-and-flush: func(len: u64) -> result<_, stream-error>; - - /// Check readiness for writing. This function never blocks. - /// - /// Returns the number of bytes permitted for the next call to `write`, - /// or an error. Calling `write` with more bytes than this function has - /// permitted will trap. - /// - /// When this function returns 0 bytes, the `subscribe` pollable will - /// become ready when this function will report at least 1 byte, or an - /// error. - check-write: func() -> result; - - /// Request to flush buffered output. This function never blocks. - /// - /// This tells the output-stream that the caller intends any buffered - /// output to be flushed. the output which is expected to be flushed - /// is all that has been passed to `write` prior to this call. - /// - /// Upon calling this function, the `output-stream` will not accept any - /// writes (`check-write` will return `ok(0)`) until the flush has - /// completed. The `subscribe` pollable will become ready when the - /// flush has completed and the stream can accept more writes. - flush: func() -> result<_, stream-error>; - - /// Read from one stream and write to another. - /// - /// The behavior of splice is equivelant to: - /// 1. calling `check-write` on the `output-stream` - /// 2. calling `read` on the `input-stream` with the smaller of the - /// `check-write` permitted length and the `len` provided to `splice` - /// 3. calling `write` on the `output-stream` with that read data. - /// - /// Any error reported by the call to `check-write`, `read`, or - /// `write` ends the splice and reports that error. - /// - /// This function returns the number of bytes transferred; it may be less - /// than `len`. - splice: func(src: borrow, len: u64) -> result; - - /// Create a `pollable` which will resolve once the output-stream - /// is ready for more writing, or an error has occured. When this - /// pollable is ready, `check-write` will return `ok(n)` with n>0, or an - /// error. - /// - /// If the stream is closed, this pollable is always ready immediately. - /// - /// The created `pollable` is a child resource of the `output-stream`. - /// Implementations may trap if the `output-stream` is dropped before - /// all derived `pollable`s created with this function are dropped. - subscribe: func() -> pollable; - - /// Perform a write. This function never blocks. - /// - /// When the destination of a `write` is binary data, the bytes from - /// `contents` are written verbatim. When the destination of a `write` is - /// known to the implementation to be text, the bytes of `contents` are - /// transcoded from UTF-8 into the encoding of the destination and then - /// written. - /// - /// Precondition: check-write gave permit of Ok(n) and contents has a - /// length of less than or equal to n. Otherwise, this function will trap. - /// - /// returns Err(closed) without writing if the stream has closed since - /// the last call to check-write provided a permit. - write: func(contents: list) -> result<_, stream-error>; - - /// Write zeroes to a stream. - /// - /// This should be used precisely like `write` with the exact same - /// preconditions (must use check-write first), but instead of - /// passing a list of bytes, you simply pass the number of zero-bytes - /// that should be written. - write-zeroes: func(len: u64) -> result<_, stream-error>; - } - } -} - -package wasi:random@0.2.0 { - /// The insecure-seed interface for seeding hash-map DoS resistance. - /// - /// It is intended to be portable at least between Unix-family platforms and - /// Windows. - interface insecure-seed { - /// Return a 128-bit value that may contain a pseudo-random value. - /// - /// The returned value is not required to be computed from a CSPRNG, and may - /// even be entirely deterministic. Host implementations are encouraged to - /// provide pseudo-random values to any program exposed to - /// attacker-controlled content, to enable DoS protection built into many - /// languages' hash-map implementations. - /// - /// This function is intended to only be called once, by a source language - /// to initialize Denial Of Service (DoS) protection in its hash-map - /// implementation. - /// - /// # Expected future evolution - /// - /// This will likely be changed to a value import, to prevent it from being - /// called multiple times and potentially used for purposes other than DoS - /// protection. - insecure-seed: func() -> tuple; - } - - /// The insecure interface for insecure pseudo-random numbers. - /// - /// It is intended to be portable at least between Unix-family platforms and - /// Windows. - interface insecure { - /// Return `len` insecure pseudo-random bytes. - /// - /// This function is not cryptographically secure. Do not use it for - /// anything related to security. - /// - /// There are no requirements on the values of the returned bytes, however - /// implementations are encouraged to return evenly distributed values with - /// a long period. - get-insecure-random-bytes: func(len: u64) -> list; - - /// Return an insecure pseudo-random `u64` value. - /// - /// This function returns the same type of pseudo-random data as - /// `get-insecure-random-bytes`, represented as a `u64`. - get-insecure-random-u64: func() -> u64; - } - - /// WASI Random is a random data API. - /// - /// It is intended to be portable at least between Unix-family platforms and - /// Windows. - interface random { - /// Return `len` cryptographically-secure random or pseudo-random bytes. - /// - /// This function must produce data at least as cryptographically secure and - /// fast as an adequately seeded cryptographically-secure pseudo-random - /// number generator (CSPRNG). It must not block, from the perspective of - /// the calling program, under any circumstances, including on the first - /// request and on requests for numbers of bytes. The returned data must - /// always be unpredictable. - /// - /// This function must always return fresh data. Deterministic environments - /// must omit this function, rather than implementing it with deterministic - /// data. - get-random-bytes: func(len: u64) -> list; - - /// Return a cryptographically-secure random or pseudo-random `u64` value. - /// - /// This function returns the same type of data as `get-random-bytes`, - /// represented as a `u64`. - get-random-u64: func() -> u64; - } -} diff --git a/src/internal/wasi/cli/v0.2.0/command/command.wit.go b/src/internal/wasi/cli/v0.2.0/command/command.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/environment/empty.s b/src/internal/wasi/cli/v0.2.0/environment/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go b/src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go b/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/exit/empty.s b/src/internal/wasi/cli/v0.2.0/exit/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go b/src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go b/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go old mode 100644 new mode 100755 index f3f172e68b..caeeb269db --- a/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go +++ b/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go @@ -15,7 +15,7 @@ import ( // //go:nosplit func Exit(status cm.BoolResult) { - status0 := cm.BoolToU32(status) + status0 := (uint32)(cm.BoolToU32(status)) wasmimport_Exit((uint32)(status0)) return } diff --git a/src/internal/wasi/cli/v0.2.0/run/empty.s b/src/internal/wasi/cli/v0.2.0/run/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/run/run.exports.go b/src/internal/wasi/cli/v0.2.0/run/run.exports.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/run/run.wasm.go b/src/internal/wasi/cli/v0.2.0/run/run.wasm.go old mode 100644 new mode 100755 index d7e20574e7..12a14e763e --- a/src/internal/wasi/cli/v0.2.0/run/run.wasm.go +++ b/src/internal/wasi/cli/v0.2.0/run/run.wasm.go @@ -12,6 +12,6 @@ import ( //export wasi:cli/run@0.2.0#run func wasmexport_Run() (result0 uint32) { result := Exports.Run() - result0 = cm.BoolToU32(result) + result0 = (uint32)(cm.BoolToU32(result)) return } diff --git a/src/internal/wasi/cli/v0.2.0/run/run.wit.go b/src/internal/wasi/cli/v0.2.0/run/run.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/stderr/empty.s b/src/internal/wasi/cli/v0.2.0/stderr/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/stdin/empty.s b/src/internal/wasi/cli/v0.2.0/stdin/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/stdout/empty.s b/src/internal/wasi/cli/v0.2.0/stdout/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-input/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-input/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-input/terminalinput.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/cli/v0.2.0/terminal-input/terminalinput.wasm.go rename to src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wasm.go diff --git a/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-output/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-output/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-output/terminaloutput.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/cli/v0.2.0/terminal-output/terminaloutput.wasm.go rename to src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wasm.go diff --git a/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stderr/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-stderr/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminalstderr.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/cli/v0.2.0/terminal-stderr/terminalstderr.wasm.go rename to src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wasm.go diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdin/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-stdin/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminalstdin.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/cli/v0.2.0/terminal-stdin/terminalstdin.wasm.go rename to src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wasm.go diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdout/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-stdout/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminalstdout.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/cli/v0.2.0/terminal-stdout/terminalstdout.wasm.go rename to src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wasm.go diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/empty.s b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonicclock.wasm.go b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonicclock.wasm.go rename to src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wasm.go diff --git a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/clocks/v0.2.0/wall-clock/empty.s b/src/internal/wasi/clocks/v0.2.0/wall-clock/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/clocks/v0.2.0/wall-clock/wallclock.wasm.go b/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/clocks/v0.2.0/wall-clock/wallclock.wasm.go rename to src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wasm.go diff --git a/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go b/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go old mode 100644 new mode 100755 index 7473c3e2c5..acf22248b8 --- a/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go +++ b/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go @@ -31,9 +31,9 @@ import ( // nanoseconds: u32, // } type DateTime struct { - _ cm.HostLayout - Seconds uint64 - Nanoseconds uint32 + _ cm.HostLayout `json:"-"` + Seconds uint64 `json:"seconds"` + Nanoseconds uint32 `json:"nanoseconds"` } // Now represents the imported function "now". diff --git a/src/internal/wasi/filesystem/v0.2.0/preopens/empty.s b/src/internal/wasi/filesystem/v0.2.0/preopens/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/filesystem/v0.2.0/types/abi.go b/src/internal/wasi/filesystem/v0.2.0/types/abi.go old mode 100644 new mode 100755 index 40ee28aa8a..30f39af5fb --- a/src/internal/wasi/filesystem/v0.2.0/types/abi.go +++ b/src/internal/wasi/filesystem/v0.2.0/types/abi.go @@ -30,7 +30,7 @@ func lower_NewTimestamp(v NewTimestamp) (f0 uint32, f1 uint64, f2 uint32) { f0 = (uint32)(v.Tag()) switch f0 { case 2: // timestamp - v1, v2 := lower_DateTime(*v.Timestamp()) + v1, v2 := lower_DateTime(*cm.Case[DateTime](&v, 2)) f1 = (uint64)(v1) f2 = (uint32)(v2) } diff --git a/src/internal/wasi/filesystem/v0.2.0/types/empty.s b/src/internal/wasi/filesystem/v0.2.0/types/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go b/src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go b/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go old mode 100644 new mode 100755 index cac7de9580..46c94b0040 --- a/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go +++ b/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go @@ -106,7 +106,7 @@ const ( DescriptorTypeSocket ) -var stringsDescriptorType = [8]string{ +var _DescriptorTypeStrings = [8]string{ "unknown", "block-device", "character-device", @@ -119,9 +119,22 @@ var stringsDescriptorType = [8]string{ // String implements [fmt.Stringer], returning the enum case name of e. func (e DescriptorType) String() string { - return stringsDescriptorType[e] + return _DescriptorTypeStrings[e] } +// MarshalText implements [encoding.TextMarshaler]. +func (e DescriptorType) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *DescriptorType) UnmarshalText(text []byte) error { + return _DescriptorTypeUnmarshalCase(e, text) +} + +var _DescriptorTypeUnmarshalCase = cm.CaseUnmarshaler[DescriptorType](_DescriptorTypeStrings[:]) + // DescriptorFlags represents the flags "wasi:filesystem/types@0.2.0#descriptor-flags". // // Descriptor flags. @@ -246,34 +259,34 @@ type LinkCount uint64 // status-change-timestamp: option, // } type DescriptorStat struct { - _ cm.HostLayout + _ cm.HostLayout `json:"-"` // File type. - Type DescriptorType + Type DescriptorType `json:"type"` // Number of hard links to the file. - LinkCount LinkCount + LinkCount LinkCount `json:"link-count"` // For regular files, the file size in bytes. For symbolic links, the // length in bytes of the pathname contained in the symbolic link. - Size FileSize + Size FileSize `json:"size"` // Last data access timestamp. // // If the `option` is none, the platform doesn't maintain an access // timestamp for this file. - DataAccessTimestamp cm.Option[DateTime] + DataAccessTimestamp cm.Option[DateTime] `json:"data-access-timestamp"` // Last data modification timestamp. // // If the `option` is none, the platform doesn't maintain a // modification timestamp for this file. - DataModificationTimestamp cm.Option[DateTime] + DataModificationTimestamp cm.Option[DateTime] `json:"data-modification-timestamp"` // Last file status-change timestamp. // // If the `option` is none, the platform doesn't maintain a // status-change timestamp for this file. - StatusChangeTimestamp cm.Option[DateTime] + StatusChangeTimestamp cm.Option[DateTime] `json:"status-change-timestamp"` } // NewTimestamp represents the variant "wasi:filesystem/types@0.2.0#new-timestamp". @@ -326,7 +339,7 @@ func (self *NewTimestamp) Timestamp() *DateTime { return cm.Case[DateTime](self, 2) } -var stringsNewTimestamp = [3]string{ +var _NewTimestampStrings = [3]string{ "no-change", "now", "timestamp", @@ -334,7 +347,7 @@ var stringsNewTimestamp = [3]string{ // String implements [fmt.Stringer], returning the variant case name of v. func (v NewTimestamp) String() string { - return stringsNewTimestamp[v.Tag()] + return _NewTimestampStrings[v.Tag()] } // DirectoryEntry represents the record "wasi:filesystem/types@0.2.0#directory-entry". @@ -346,12 +359,12 @@ func (v NewTimestamp) String() string { // name: string, // } type DirectoryEntry struct { - _ cm.HostLayout + _ cm.HostLayout `json:"-"` // The type of the file referred to by this directory entry. - Type DescriptorType + Type DescriptorType `json:"type"` // The name of the object. - Name string + Name string `json:"name"` } // ErrorCode represents the enum "wasi:filesystem/types@0.2.0#error-code". @@ -516,7 +529,7 @@ const ( ErrorCodeCrossDevice ) -var stringsErrorCode = [37]string{ +var _ErrorCodeStrings = [37]string{ "access", "would-block", "already", @@ -558,9 +571,22 @@ var stringsErrorCode = [37]string{ // String implements [fmt.Stringer], returning the enum case name of e. func (e ErrorCode) String() string { - return stringsErrorCode[e] + return _ErrorCodeStrings[e] +} + +// MarshalText implements [encoding.TextMarshaler]. +func (e ErrorCode) MarshalText() ([]byte, error) { + return []byte(e.String()), nil } +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *ErrorCode) UnmarshalText(text []byte) error { + return _ErrorCodeUnmarshalCase(e, text) +} + +var _ErrorCodeUnmarshalCase = cm.CaseUnmarshaler[ErrorCode](_ErrorCodeStrings[:]) + // Advice represents the enum "wasi:filesystem/types@0.2.0#advice". // // File or memory access pattern advisory information. @@ -601,7 +627,7 @@ const ( AdviceNoReuse ) -var stringsAdvice = [6]string{ +var _AdviceStrings = [6]string{ "normal", "sequential", "random", @@ -612,9 +638,22 @@ var stringsAdvice = [6]string{ // String implements [fmt.Stringer], returning the enum case name of e. func (e Advice) String() string { - return stringsAdvice[e] + return _AdviceStrings[e] +} + +// MarshalText implements [encoding.TextMarshaler]. +func (e Advice) MarshalText() ([]byte, error) { + return []byte(e.String()), nil } +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *Advice) UnmarshalText(text []byte) error { + return _AdviceUnmarshalCase(e, text) +} + +var _AdviceUnmarshalCase = cm.CaseUnmarshaler[Advice](_AdviceStrings[:]) + // MetadataHashValue represents the record "wasi:filesystem/types@0.2.0#metadata-hash-value". // // A 128-bit hash value, split into parts because wasm doesn't have a @@ -625,12 +664,12 @@ func (e Advice) String() string { // upper: u64, // } type MetadataHashValue struct { - _ cm.HostLayout + _ cm.HostLayout `json:"-"` // 64 bits of a 128-bit hash value. - Lower uint64 + Lower uint64 `json:"lower"` // Another 64 bits of a 128-bit hash value. - Upper uint64 + Upper uint64 `json:"upper"` } // Descriptor represents the imported resource "wasi:filesystem/types@0.2.0#descriptor". @@ -761,7 +800,7 @@ func (self Descriptor) IsSameObject(other Descriptor) (result bool) { self0 := cm.Reinterpret[uint32](self) other0 := cm.Reinterpret[uint32](other) result0 := wasmimport_DescriptorIsSameObject((uint32)(self0), (uint32)(other0)) - result = cm.U32ToBool((uint32)(result0)) + result = (bool)(cm.U32ToBool((uint32)(result0))) return } diff --git a/src/internal/wasi/io/v0.2.0/error/empty.s b/src/internal/wasi/io/v0.2.0/error/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/io/v0.2.0/error/ioerror.wasm.go b/src/internal/wasi/io/v0.2.0/error/error.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/io/v0.2.0/error/ioerror.wasm.go rename to src/internal/wasi/io/v0.2.0/error/error.wasm.go diff --git a/src/internal/wasi/io/v0.2.0/error/error.wit.go b/src/internal/wasi/io/v0.2.0/error/error.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/io/v0.2.0/poll/empty.s b/src/internal/wasi/io/v0.2.0/poll/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/io/v0.2.0/poll/poll.wasm.go b/src/internal/wasi/io/v0.2.0/poll/poll.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/io/v0.2.0/poll/poll.wit.go b/src/internal/wasi/io/v0.2.0/poll/poll.wit.go old mode 100644 new mode 100755 index a3d5163944..4261f161a2 --- a/src/internal/wasi/io/v0.2.0/poll/poll.wit.go +++ b/src/internal/wasi/io/v0.2.0/poll/poll.wit.go @@ -57,7 +57,7 @@ func (self Pollable) Block() { func (self Pollable) Ready() (result bool) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_PollableReady((uint32)(self0)) - result = cm.U32ToBool((uint32)(result0)) + result = (bool)(cm.U32ToBool((uint32)(result0))) return } diff --git a/src/internal/wasi/io/v0.2.0/streams/abi.go b/src/internal/wasi/io/v0.2.0/streams/abi.go deleted file mode 100644 index 5f1500288a..0000000000 --- a/src/internal/wasi/io/v0.2.0/streams/abi.go +++ /dev/null @@ -1,12 +0,0 @@ -// Code generated by wit-bindgen-go. DO NOT EDIT. - -package streams - -import ( - "unsafe" -) - -// StreamErrorShape is used for storage in variant or result types. -type StreamErrorShape struct { - shape [unsafe.Sizeof(StreamError{})]byte -} diff --git a/src/internal/wasi/io/v0.2.0/streams/empty.s b/src/internal/wasi/io/v0.2.0/streams/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/io/v0.2.0/streams/streams.wasm.go b/src/internal/wasi/io/v0.2.0/streams/streams.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/io/v0.2.0/streams/streams.wit.go b/src/internal/wasi/io/v0.2.0/streams/streams.wit.go old mode 100644 new mode 100755 index a4dcc970e2..e67a512c66 --- a/src/internal/wasi/io/v0.2.0/streams/streams.wit.go +++ b/src/internal/wasi/io/v0.2.0/streams/streams.wit.go @@ -64,14 +64,14 @@ func (self *StreamError) Closed() bool { return self.Tag() == 1 } -var stringsStreamError = [2]string{ +var _StreamErrorStrings = [2]string{ "last-operation-failed", "closed", } // String implements [fmt.Stringer], returning the variant case name of v. func (v StreamError) String() string { - return stringsStreamError[v.Tag()] + return _StreamErrorStrings[v.Tag()] } // InputStream represents the imported resource "wasi:io/streams@0.2.0#input-stream". @@ -273,13 +273,13 @@ func (self OutputStream) BlockingSplice(src InputStream, len_ uint64) (result cm // // let pollable = this.subscribe(); // while !contents.is_empty() { -// // Wait for the stream to become writable -// pollable.block(); -// let Ok(n) = this.check-write(); // eliding error handling -// let len = min(n, contents.len()); -// let (chunk, rest) = contents.split_at(len); -// this.write(chunk ); // eliding error handling -// contents = rest; +// // Wait for the stream to become writable +// pollable.block(); +// let Ok(n) = this.check-write(); // eliding error handling +// let len = min(n, contents.len()); +// let (chunk, rest) = contents.split_at(len); +// this.write(chunk ); // eliding error handling +// contents = rest; // } // this.flush(); // // Wait for completion of `flush` @@ -309,12 +309,12 @@ func (self OutputStream) BlockingWriteAndFlush(contents cm.List[uint8]) (result // // let pollable = this.subscribe(); // while num_zeroes != 0 { -// // Wait for the stream to become writable -// pollable.block(); -// let Ok(n) = this.check-write(); // eliding error handling -// let len = min(n, num_zeroes); -// this.write-zeroes(len); // eliding error handling -// num_zeroes -= len; +// // Wait for the stream to become writable +// pollable.block(); +// let Ok(n) = this.check-write(); // eliding error handling +// let len = min(n, num_zeroes); +// this.write-zeroes(len); // eliding error handling +// num_zeroes -= len; // } // this.flush(); // // Wait for completion of `flush` diff --git a/src/internal/wasi/random/v0.2.0/insecure-seed/empty.s b/src/internal/wasi/random/v0.2.0/insecure-seed/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/random/v0.2.0/insecure-seed/insecureseed.wasm.go b/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/random/v0.2.0/insecure-seed/insecureseed.wasm.go rename to src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wasm.go diff --git a/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go b/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/random/v0.2.0/insecure/empty.s b/src/internal/wasi/random/v0.2.0/insecure/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go b/src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go b/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/random/v0.2.0/random/empty.s b/src/internal/wasi/random/v0.2.0/random/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/random/v0.2.0/random/random.wasm.go b/src/internal/wasi/random/v0.2.0/random/random.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/random/v0.2.0/random/random.wit.go b/src/internal/wasi/random/v0.2.0/random/random.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/instance-network/empty.s b/src/internal/wasi/sockets/v0.2.0/instance-network/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/instance-network/instancenetwork.wasm.go b/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/sockets/v0.2.0/instance-network/instancenetwork.wasm.go rename to src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wasm.go diff --git a/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go b/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/empty.s b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ipnamelookup.wasm.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ipnamelookup.wasm.go rename to src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wasm.go diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/network/abi.go b/src/internal/wasi/sockets/v0.2.0/network/abi.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/network/empty.s b/src/internal/wasi/sockets/v0.2.0/network/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/network/network.wasm.go b/src/internal/wasi/sockets/v0.2.0/network/network.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/network/network.wit.go b/src/internal/wasi/sockets/v0.2.0/network/network.wit.go old mode 100644 new mode 100755 index 93a8e82002..987c4c4c35 --- a/src/internal/wasi/sockets/v0.2.0/network/network.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/network/network.wit.go @@ -153,7 +153,7 @@ const ( ErrorCodePermanentResolverFailure ) -var stringsErrorCode = [21]string{ +var _ErrorCodeStrings = [21]string{ "unknown", "access-denied", "not-supported", @@ -179,9 +179,22 @@ var stringsErrorCode = [21]string{ // String implements [fmt.Stringer], returning the enum case name of e. func (e ErrorCode) String() string { - return stringsErrorCode[e] + return _ErrorCodeStrings[e] } +// MarshalText implements [encoding.TextMarshaler]. +func (e ErrorCode) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *ErrorCode) UnmarshalText(text []byte) error { + return _ErrorCodeUnmarshalCase(e, text) +} + +var _ErrorCodeUnmarshalCase = cm.CaseUnmarshaler[ErrorCode](_ErrorCodeStrings[:]) + // IPAddressFamily represents the enum "wasi:sockets/network@0.2.0#ip-address-family". // // enum ip-address-family { @@ -198,16 +211,29 @@ const ( IPAddressFamilyIPv6 ) -var stringsIPAddressFamily = [2]string{ +var _IPAddressFamilyStrings = [2]string{ "ipv4", "ipv6", } // String implements [fmt.Stringer], returning the enum case name of e. func (e IPAddressFamily) String() string { - return stringsIPAddressFamily[e] + return _IPAddressFamilyStrings[e] } +// MarshalText implements [encoding.TextMarshaler]. +func (e IPAddressFamily) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *IPAddressFamily) UnmarshalText(text []byte) error { + return _IPAddressFamilyUnmarshalCase(e, text) +} + +var _IPAddressFamilyUnmarshalCase = cm.CaseUnmarshaler[IPAddressFamily](_IPAddressFamilyStrings[:]) + // IPv4Address represents the tuple "wasi:sockets/network@0.2.0#ipv4-address". // // type ipv4-address = tuple @@ -246,14 +272,14 @@ func (self *IPAddress) IPv6() *IPv6Address { return cm.Case[IPv6Address](self, 1) } -var stringsIPAddress = [2]string{ +var _IPAddressStrings = [2]string{ "ipv4", "ipv6", } // String implements [fmt.Stringer], returning the variant case name of v. func (v IPAddress) String() string { - return stringsIPAddress[v.Tag()] + return _IPAddressStrings[v.Tag()] } // IPv4SocketAddress represents the record "wasi:sockets/network@0.2.0#ipv4-socket-address". @@ -263,12 +289,12 @@ func (v IPAddress) String() string { // address: ipv4-address, // } type IPv4SocketAddress struct { - _ cm.HostLayout + _ cm.HostLayout `json:"-"` // sin_port - Port uint16 + Port uint16 `json:"port"` // sin_addr - Address IPv4Address + Address IPv4Address `json:"address"` } // IPv6SocketAddress represents the record "wasi:sockets/network@0.2.0#ipv6-socket-address". @@ -280,18 +306,18 @@ type IPv4SocketAddress struct { // scope-id: u32, // } type IPv6SocketAddress struct { - _ cm.HostLayout + _ cm.HostLayout `json:"-"` // sin6_port - Port uint16 + Port uint16 `json:"port"` // sin6_flowinfo - FlowInfo uint32 + FlowInfo uint32 `json:"flow-info"` // sin6_addr - Address IPv6Address + Address IPv6Address `json:"address"` // sin6_scope_id - ScopeID uint32 + ScopeID uint32 `json:"scope-id"` } // IPSocketAddress represents the variant "wasi:sockets/network@0.2.0#ip-socket-address". @@ -322,12 +348,12 @@ func (self *IPSocketAddress) IPv6() *IPv6SocketAddress { return cm.Case[IPv6SocketAddress](self, 1) } -var stringsIPSocketAddress = [2]string{ +var _IPSocketAddressStrings = [2]string{ "ipv4", "ipv6", } // String implements [fmt.Stringer], returning the variant case name of v. func (v IPSocketAddress) String() string { - return stringsIPSocketAddress[v.Tag()] + return _IPSocketAddressStrings[v.Tag()] } diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/empty.s b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcpcreatesocket.wasm.go b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcpcreatesocket.wasm.go rename to src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wasm.go diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/abi.go b/src/internal/wasi/sockets/v0.2.0/tcp/abi.go old mode 100644 new mode 100755 index 8174d298fd..8bfc6292e3 --- a/src/internal/wasi/sockets/v0.2.0/tcp/abi.go +++ b/src/internal/wasi/sockets/v0.2.0/tcp/abi.go @@ -23,7 +23,7 @@ type TupleInputStreamOutputStreamShape struct { // IPSocketAddressShape is used for storage in variant or result types. type IPSocketAddressShape struct { _ cm.HostLayout - shape [unsafe.Sizeof(network.IPSocketAddress{})]byte + shape [unsafe.Sizeof(IPSocketAddress{})]byte } func lower_IPv4Address(v network.IPv4Address) (f0 uint32, f1 uint32, f2 uint32, f3 uint32) { @@ -64,14 +64,14 @@ func lower_IPSocketAddress(v network.IPSocketAddress) (f0 uint32, f1 uint32, f2 f0 = (uint32)(v.Tag()) switch f0 { case 0: // ipv4 - v1, v2, v3, v4, v5 := lower_IPv4SocketAddress(*v.IPv4()) + v1, v2, v3, v4, v5 := lower_IPv4SocketAddress(*cm.Case[network.IPv4SocketAddress](&v, 0)) f1 = (uint32)(v1) f2 = (uint32)(v2) f3 = (uint32)(v3) f4 = (uint32)(v4) f5 = (uint32)(v5) case 1: // ipv6 - v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 := lower_IPv6SocketAddress(*v.IPv6()) + v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 := lower_IPv6SocketAddress(*cm.Case[network.IPv6SocketAddress](&v, 1)) f1 = (uint32)(v1) f2 = (uint32)(v2) f3 = (uint32)(v3) diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/empty.s b/src/internal/wasi/sockets/v0.2.0/tcp/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go old mode 100644 new mode 100755 index 1257fcb146..3ab1acde6d --- a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go +++ b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go @@ -46,7 +46,7 @@ func wasmimport_TCPSocketKeepAliveCount(self0 uint32, result *cm.Result[uint32, //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-enabled //go:noescape -func wasmimport_TCPSocketKeepAliveEnabled(self0 uint32, result *cm.Result[bool, bool, ErrorCode]) +func wasmimport_TCPSocketKeepAliveEnabled(self0 uint32, result *cm.Result[ErrorCode, bool, ErrorCode]) //go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-idle-time //go:noescape diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go old mode 100644 new mode 100755 index 5eb102cd30..c82fd59337 --- a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go @@ -71,7 +71,7 @@ const ( ShutdownTypeBoth ) -var stringsShutdownType = [3]string{ +var _ShutdownTypeStrings = [3]string{ "receive", "send", "both", @@ -79,9 +79,22 @@ var stringsShutdownType = [3]string{ // String implements [fmt.Stringer], returning the enum case name of e. func (e ShutdownType) String() string { - return stringsShutdownType[e] + return _ShutdownTypeStrings[e] } +// MarshalText implements [encoding.TextMarshaler]. +func (e ShutdownType) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *ShutdownType) UnmarshalText(text []byte) error { + return _ShutdownTypeUnmarshalCase(e, text) +} + +var _ShutdownTypeUnmarshalCase = cm.CaseUnmarshaler[ShutdownType](_ShutdownTypeStrings[:]) + // TCPSocket represents the imported resource "wasi:sockets/tcp@0.2.0#tcp-socket". // // A TCP socket resource. @@ -241,7 +254,7 @@ func (self TCPSocket) HopLimit() (result cm.Result[uint8, uint8, ErrorCode]) { func (self TCPSocket) IsListening() (result bool) { self0 := cm.Reinterpret[uint32](self) result0 := wasmimport_TCPSocketIsListening((uint32)(self0)) - result = cm.U32ToBool((uint32)(result0)) + result = (bool)(cm.U32ToBool((uint32)(result0))) return } @@ -285,7 +298,7 @@ func (self TCPSocket) KeepAliveCount() (result cm.Result[uint32, uint32, ErrorCo // keep-alive-enabled: func() -> result // //go:nosplit -func (self TCPSocket) KeepAliveEnabled() (result cm.Result[bool, bool, ErrorCode]) { +func (self TCPSocket) KeepAliveEnabled() (result cm.Result[ErrorCode, bool, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) wasmimport_TCPSocketKeepAliveEnabled((uint32)(self0), &result) return @@ -457,7 +470,7 @@ func (self TCPSocket) SetKeepAliveCount(value uint32) (result cm.Result[ErrorCod //go:nosplit func (self TCPSocket) SetKeepAliveEnabled(value bool) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { self0 := cm.Reinterpret[uint32](self) - value0 := cm.BoolToU32(value) + value0 := (uint32)(cm.BoolToU32(value)) wasmimport_TCPSocketSetKeepAliveEnabled((uint32)(self0), (uint32)(value0), &result) return } diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/empty.s b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udpcreatesocket.wasm.go b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wasm.go old mode 100644 new mode 100755 similarity index 100% rename from src/internal/wasi/sockets/v0.2.0/udp-create-socket/udpcreatesocket.wasm.go rename to src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wasm.go diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/udp/abi.go b/src/internal/wasi/sockets/v0.2.0/udp/abi.go old mode 100644 new mode 100755 index 71d00f9d6b..e535da64ad --- a/src/internal/wasi/sockets/v0.2.0/udp/abi.go +++ b/src/internal/wasi/sockets/v0.2.0/udp/abi.go @@ -11,7 +11,7 @@ import ( // IPSocketAddressShape is used for storage in variant or result types. type IPSocketAddressShape struct { _ cm.HostLayout - shape [unsafe.Sizeof(network.IPSocketAddress{})]byte + shape [unsafe.Sizeof(IPSocketAddress{})]byte } func lower_IPv4Address(v network.IPv4Address) (f0 uint32, f1 uint32, f2 uint32, f3 uint32) { @@ -52,14 +52,14 @@ func lower_IPSocketAddress(v network.IPSocketAddress) (f0 uint32, f1 uint32, f2 f0 = (uint32)(v.Tag()) switch f0 { case 0: // ipv4 - v1, v2, v3, v4, v5 := lower_IPv4SocketAddress(*v.IPv4()) + v1, v2, v3, v4, v5 := lower_IPv4SocketAddress(*cm.Case[network.IPv4SocketAddress](&v, 0)) f1 = (uint32)(v1) f2 = (uint32)(v2) f3 = (uint32)(v3) f4 = (uint32)(v4) f5 = (uint32)(v5) case 1: // ipv6 - v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 := lower_IPv6SocketAddress(*v.IPv6()) + v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 := lower_IPv6SocketAddress(*cm.Case[network.IPv6SocketAddress](&v, 1)) f1 = (uint32)(v1) f2 = (uint32)(v2) f3 = (uint32)(v3) diff --git a/src/internal/wasi/sockets/v0.2.0/udp/empty.s b/src/internal/wasi/sockets/v0.2.0/udp/empty.s old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go b/src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go old mode 100644 new mode 100755 diff --git a/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go b/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go old mode 100644 new mode 100755 index f773fa48d0..f10df3091d --- a/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go +++ b/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go @@ -43,11 +43,11 @@ type IPAddressFamily = network.IPAddressFamily // remote-address: ip-socket-address, // } type IncomingDatagram struct { - _ cm.HostLayout + _ cm.HostLayout `json:"-"` // The payload. // // Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. - Data cm.List[uint8] + Data cm.List[uint8] `json:"data"` // The source address. // @@ -55,7 +55,7 @@ type IncomingDatagram struct { // with, if any. // // Equivalent to the `src_addr` out parameter of `recvfrom`. - RemoteAddress IPSocketAddress + RemoteAddress IPSocketAddress `json:"remote-address"` } // OutgoingDatagram represents the record "wasi:sockets/udp@0.2.0#outgoing-datagram". @@ -67,9 +67,9 @@ type IncomingDatagram struct { // remote-address: option, // } type OutgoingDatagram struct { - _ cm.HostLayout + _ cm.HostLayout `json:"-"` // The payload. - Data cm.List[uint8] + Data cm.List[uint8] `json:"data"` // The destination address. // @@ -80,7 +80,7 @@ type OutgoingDatagram struct { // // If this value is None, the send operation is equivalent to `send` in POSIX. Otherwise // it is equivalent to `sendto`. - RemoteAddress cm.Option[IPSocketAddress] + RemoteAddress cm.Option[IPSocketAddress] `json:"remote-address"` } // UDPSocket represents the imported resource "wasi:sockets/udp@0.2.0#udp-socket". @@ -321,10 +321,10 @@ func (self UDPSocket) StartBind(network_ Network, localAddress IPSocketAddress) // The POSIX equivalent in pseudo-code is: // // if (was previously connected) { -// connect(s, AF_UNSPEC) +// connect(s, AF_UNSPEC) // } // if (remote_address is Some) { -// connect(s, remote_address) +// connect(s, remote_address) // } // // Unlike in POSIX, the socket must already be explicitly bound. From d6527ece0241adb1ea7b5ab684623175c2d47e7a Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 15 Mar 2025 22:05:45 -0700 Subject: [PATCH 438/444] internal/cm: exclude certain files when copying package --- GNUmakefile | 2 +- src/internal/cm/CHANGELOG.md | 23 ---- src/internal/cm/LICENSE | 220 ----------------------------------- src/internal/cm/README.md | 15 --- src/internal/cm/RELEASE.md | 34 ------ 5 files changed, 1 insertion(+), 293 deletions(-) delete mode 100644 src/internal/cm/CHANGELOG.md delete mode 100644 src/internal/cm/LICENSE delete mode 100644 src/internal/cm/README.md delete mode 100644 src/internal/cm/RELEASE.md diff --git a/GNUmakefile b/GNUmakefile index 4e630c6a3f..958d08b4e4 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -277,7 +277,7 @@ wasi-syscall: wasi-cm .PHONY: wasi-cm wasi-cm: rm -rf ./src/internal/cm/* - rsync -rv --delete --exclude '*_test.go' $(shell go list -modfile ./internal/wasm-tools/go.mod -m -f {{.Dir}} $(WASM_TOOLS_MODULE)/cm)/ ./src/internal/cm + rsync -rv --delete --exclude '*_test.go' --exclude '*.md' --exclude LICENSE $(shell go list -modfile ./internal/wasm-tools/go.mod -m -f {{.Dir}} $(WASM_TOOLS_MODULE)/cm)/ ./src/internal/cm # Check for Node.js used during WASM tests. NODEJS_VERSION := $(word 1,$(subst ., ,$(shell node -v | cut -c 2-))) diff --git a/src/internal/cm/CHANGELOG.md b/src/internal/cm/CHANGELOG.md deleted file mode 100644 index 35efaf2f57..0000000000 --- a/src/internal/cm/CHANGELOG.md +++ /dev/null @@ -1,23 +0,0 @@ -# Changelog - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [v0.2.0] — 2025-03-15 - -### Added - -- Initial support for Component Model [async](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Async.md) types `stream`, `future`, and `error-context`. -- Initial support for JSON serialization of WIT `list`, `enum`, and `record` types. -- Added `cm.CaseUnmarshaler` helper for text and JSON unmarshaling of `enum` and `variant` types. - -### Changed - -- Breaking: package `cm`: removed `bool` from `Discriminant` type constraint. It was not used by code generation. - -## [v0.1.0] — 2024-12-14 - -Initial version, extracted into module [`go.bytecodealliance.org/cm`](https://pkg.go.dev/go.bytecodealliance.org/cm). - -[Unreleased]: -[v0.2.0]: -[v0.1.0]: diff --git a/src/internal/cm/LICENSE b/src/internal/cm/LICENSE deleted file mode 100644 index f9d81955f4..0000000000 --- a/src/internal/cm/LICENSE +++ /dev/null @@ -1,220 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. - - ---- LLVM Exceptions to the Apache 2.0 License ---- - -As an exception, if, as a result of your compiling your source code, portions -of this Software are embedded into an Object form of such source code, you -may redistribute such embedded portions in such Object form without complying -with the conditions of Sections 4(a), 4(b) and 4(d) of the License. - -In addition, if you combine or link compiled forms of this Software with -software that is licensed under the GPLv2 ("Combined Software") and if a -court of competent jurisdiction determines that the patent provision (Section -3), the indemnity provision (Section 9) or other Section of the License -conflicts with the conditions of the GPLv2, you may retroactively and -prospectively choose to deem waived or otherwise exclude such Section(s) of -the License, but only in their entirety and only with respect to the Combined -Software. - diff --git a/src/internal/cm/README.md b/src/internal/cm/README.md deleted file mode 100644 index d6c5fd4fd5..0000000000 --- a/src/internal/cm/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# go.bytecodealliance.org/cm - -[![pkg.go.dev](https://img.shields.io/badge/docs-pkg.go.dev-blue.svg)](https://pkg.go.dev/go.bytecodealliance.org/cm) [![build status](https://img.shields.io/github/actions/workflow/status/bytecodealliance/go-modules/test.yaml?branch=main)](https://github.com/bytecodealliance/go-modules/actions) - -## About - -Package `cm` contains helper types and functions used by generated packages, such as `option`, `result`, `variant`, `list`, and `resource`. These are intended for use by generated [Component Model](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#type-definitions) bindings, where the caller converts to a Go equivalent. It attempts to map WIT semantics to their equivalent in Go where possible. - -### Note on Memory Safety - -Package `cm` and generated bindings from `wit-bindgen-go` may have compatibility issues with the Go garbage collector, as they directly represent `variant` and `result` types as tagged unions where a pointer shape may be occupied by a non-pointer value. The GC may detect and throw an error if it detects a non-pointer value in an area it expects to see a pointer. This is an area of active development. - -## License - -This project is licensed under the Apache 2.0 license with the LLVM exception. See [LICENSE](../LICENSE) for more details. diff --git a/src/internal/cm/RELEASE.md b/src/internal/cm/RELEASE.md deleted file mode 100644 index 5a6283a606..0000000000 --- a/src/internal/cm/RELEASE.md +++ /dev/null @@ -1,34 +0,0 @@ -# Release - -This document describes the steps to release a new version of module `go.bytecodealliance.org/cm`. - -## 1. Update [CHANGELOG.md](./CHANGELOG.md) - -1. Add the latest changes to [CHANGELOG.md](./CHANGELOG.md). -1. Rename the Unreleased section to reflect the new version number. - 1. Update the links to new version tag in the footer of CHANGELOG.md -1. Add today’s date (YYYY-MM-DD) after an em dash (—). -1. Submit a [GitHub Pull Request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests) with these updates. - -## 2. Create a new release - -Once the PR is merged, tag the new version in Git and push the tag to GitHub. - -**Note:** the tag **must** start with the prefix `cm/` in order to correctly tag this module. - -For example, to tag version `cm/v0.2.0`: - -```console -git tag cm/v0.2.0 -git push origin cm/v0.2.0 -``` - -## 3. Update the root module - -Once the tag is pushed, you can update the root module to depend on the newly created version of package `cm` by running the following: - -```console -go get -u go.bytecodealliance.org/cm@latest -``` - -Then follow the instructions in [RELEASE.md](../RELEASE.md) to release a new version of the root module. From 8e009de8a82c9d6b89a96045454924641077882b Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 15 Mar 2025 22:09:30 -0700 Subject: [PATCH 439/444] internal/cm: remove go.mod file --- GNUmakefile | 2 +- src/internal/cm/go.mod | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 src/internal/cm/go.mod diff --git a/GNUmakefile b/GNUmakefile index 958d08b4e4..7ac712e0cf 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -277,7 +277,7 @@ wasi-syscall: wasi-cm .PHONY: wasi-cm wasi-cm: rm -rf ./src/internal/cm/* - rsync -rv --delete --exclude '*_test.go' --exclude '*.md' --exclude LICENSE $(shell go list -modfile ./internal/wasm-tools/go.mod -m -f {{.Dir}} $(WASM_TOOLS_MODULE)/cm)/ ./src/internal/cm + rsync -rv --delete --exclude go.mod --exclude '*_test.go' --exclude '*.md' --exclude LICENSE $(shell go list -modfile ./internal/wasm-tools/go.mod -m -f {{.Dir}} $(WASM_TOOLS_MODULE)/cm)/ ./src/internal/cm # Check for Node.js used during WASM tests. NODEJS_VERSION := $(word 1,$(subst ., ,$(shell node -v | cut -c 2-))) diff --git a/src/internal/cm/go.mod b/src/internal/cm/go.mod deleted file mode 100644 index f8e24af713..0000000000 --- a/src/internal/cm/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module go.bytecodealliance.org/cm - -go 1.23.0 From d63d8e08991ac27721066ba39522e0272d9510e4 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sun, 16 Mar 2025 13:35:44 -0700 Subject: [PATCH 440/444] internal/wasm-tools, internal/cm: udpate to go.bytecodealliance.org@v0.6.1 and cm@v0.2.1 --- GNUmakefile | 2 +- internal/wasm-tools/go.mod | 4 +-- internal/wasm-tools/go.sum | 8 +++--- src/internal/cm/case.go | 17 ++++++++---- src/internal/cm/list.go | 56 -------------------------------------- 5 files changed, 18 insertions(+), 69 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 7ac712e0cf..1a97269e9b 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -277,7 +277,7 @@ wasi-syscall: wasi-cm .PHONY: wasi-cm wasi-cm: rm -rf ./src/internal/cm/* - rsync -rv --delete --exclude go.mod --exclude '*_test.go' --exclude '*.md' --exclude LICENSE $(shell go list -modfile ./internal/wasm-tools/go.mod -m -f {{.Dir}} $(WASM_TOOLS_MODULE)/cm)/ ./src/internal/cm + rsync -rv --delete --exclude go.mod --exclude '*_test.go' --exclude '*_json.go' --exclude '*.md' --exclude LICENSE $(shell go list -modfile ./internal/wasm-tools/go.mod -m -f {{.Dir}} $(WASM_TOOLS_MODULE)/cm)/ ./src/internal/cm # Check for Node.js used during WASM tests. NODEJS_VERSION := $(word 1,$(subst ., ,$(shell node -v | cut -c 2-))) diff --git a/internal/wasm-tools/go.mod b/internal/wasm-tools/go.mod index 47ad4889c7..c790a7ab05 100644 --- a/internal/wasm-tools/go.mod +++ b/internal/wasm-tools/go.mod @@ -3,8 +3,8 @@ module github.com/tinygo-org/tinygo/internal/wasm-tools go 1.23.0 require ( - go.bytecodealliance.org v0.6.0 - go.bytecodealliance.org/cm v0.2.0 + go.bytecodealliance.org v0.6.1 + go.bytecodealliance.org/cm v0.2.1 ) require ( diff --git a/internal/wasm-tools/go.sum b/internal/wasm-tools/go.sum index 45b74397d1..3f2a32ded1 100644 --- a/internal/wasm-tools/go.sum +++ b/internal/wasm-tools/go.sum @@ -29,10 +29,10 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg= github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= -go.bytecodealliance.org v0.6.0 h1:9ziqj963aEKqOiu5cl87Hb78KImNP4zEABl+OWwBwxk= -go.bytecodealliance.org v0.6.0/go.mod h1:j0lprXhqeVSE8kYN2EjcN9gm+fxUyNWtl//WA+3ypFg= -go.bytecodealliance.org/cm v0.2.0 h1:HMkj1x1LZWU/Ghu2TtUk3VTyS7gyDHLyIfIut0Cpc5M= -go.bytecodealliance.org/cm v0.2.0/go.mod h1:JD5vtVNZv7sBoQQkvBvAAVKJPhR/bqBH7yYXTItMfZI= +go.bytecodealliance.org v0.6.1 h1:H3vVYeEZa8Gs9t9YyZ4ZjA8sr5c6Xfkz3p+cM3543aA= +go.bytecodealliance.org v0.6.1/go.mod h1:qPL6PksrsjAxl6o23HuX4pGO6n3UxiESPbvRn8ZVot0= +go.bytecodealliance.org/cm v0.2.1 h1:sFUQRPfM+ku7hKgFwxGdwjghv4QtTLukpqsAHgE1Tek= +go.bytecodealliance.org/cm v0.2.1/go.mod h1:JD5vtVNZv7sBoQQkvBvAAVKJPhR/bqBH7yYXTItMfZI= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= diff --git a/src/internal/cm/case.go b/src/internal/cm/case.go index 65ade4949a..fa212bb0c9 100644 --- a/src/internal/cm/case.go +++ b/src/internal/cm/case.go @@ -1,7 +1,5 @@ package cm -import "errors" - // CaseUnmarshaler returns an function that can unmarshal text into // [variant] or [enum] case T. // @@ -33,8 +31,7 @@ func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, te if len(text) == 0 { return errEmpty } - s := string(text) - c, ok := m[s] + c, ok := m[string(text)] if !ok { return errNoMatchingCase } @@ -46,6 +43,14 @@ func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, te const linearScanThreshold = 16 var ( - errEmpty = errors.New("empty text") - errNoMatchingCase = errors.New("no matching case") + errEmpty = &stringError{"empty text"} + errNoMatchingCase = &stringError{"no matching case"} ) + +type stringError struct { + err string +} + +func (err *stringError) Error() string { + return err.err +} diff --git a/src/internal/cm/list.go b/src/internal/cm/list.go index 0a171dbd37..22d9d31f2e 100644 --- a/src/internal/cm/list.go +++ b/src/internal/cm/list.go @@ -1,8 +1,6 @@ package cm import ( - "bytes" - "encoding/json" "unsafe" ) @@ -62,57 +60,3 @@ func (l list[T]) Data() *T { func (l list[T]) Len() uintptr { return l.len } - -// MarshalJSON implements json.Marshaler. -func (l list[T]) MarshalJSON() ([]byte, error) { - if l.len == 0 { - return []byte("[]"), nil - } - - s := l.Slice() - var zero T - if unsafe.Sizeof(zero) == 1 { - // The default Go json.Encoder will marshal []byte as base64. - // We override that behavior so all int types have the same serialization format. - // []uint8{1,2,3} -> [1,2,3] - // []uint32{1,2,3} -> [1,2,3] - return json.Marshal(sliceOf(s)) - } - return json.Marshal(s) -} - -type slice[T any] []entry[T] - -func sliceOf[S ~[]E, E any](s S) slice[E] { - return *(*slice[E])(unsafe.Pointer(&s)) -} - -type entry[T any] [1]T - -func (v entry[T]) MarshalJSON() ([]byte, error) { - return json.Marshal(v[0]) -} - -// UnmarshalJSON implements json.Unmarshaler. -func (l *list[T]) UnmarshalJSON(data []byte) error { - if bytes.Equal(data, nullLiteral) { - return nil - } - - var s []T - err := json.Unmarshal(data, &s) - if err != nil { - return err - } - - l.data = unsafe.SliceData([]T(s)) - l.len = uintptr(len(s)) - - return nil -} - -// nullLiteral is the JSON representation of a null literal. -// By convention, to approximate the behavior of Unmarshal itself, -// Unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op. -// See https://pkg.go.dev/encoding/json#Unmarshaler for more information. -var nullLiteral = []byte("null") From a7416e7300d0ae92faf580d6a4e4341a1d076b68 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sun, 16 Mar 2025 14:18:33 -0700 Subject: [PATCH 441/444] internal/wasi: remove x bit from generated files --- src/internal/wasi/cli/v0.2.0/command/command.wit.go | 0 src/internal/wasi/cli/v0.2.0/environment/empty.s | 0 src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go | 0 src/internal/wasi/cli/v0.2.0/environment/environment.wit.go | 0 src/internal/wasi/cli/v0.2.0/exit/empty.s | 0 src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go | 0 src/internal/wasi/cli/v0.2.0/exit/exit.wit.go | 0 src/internal/wasi/cli/v0.2.0/run/empty.s | 0 src/internal/wasi/cli/v0.2.0/run/run.exports.go | 0 src/internal/wasi/cli/v0.2.0/run/run.wasm.go | 0 src/internal/wasi/cli/v0.2.0/run/run.wit.go | 0 src/internal/wasi/cli/v0.2.0/stderr/empty.s | 0 src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go | 0 src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go | 0 src/internal/wasi/cli/v0.2.0/stdin/empty.s | 0 src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go | 0 src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go | 0 src/internal/wasi/cli/v0.2.0/stdout/empty.s | 0 src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go | 0 src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go | 0 src/internal/wasi/cli/v0.2.0/terminal-input/empty.s | 0 .../wasi/cli/v0.2.0/terminal-input/terminal-input.wasm.go | 0 src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go | 0 src/internal/wasi/cli/v0.2.0/terminal-output/empty.s | 0 .../wasi/cli/v0.2.0/terminal-output/terminal-output.wasm.go | 0 .../wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go | 0 src/internal/wasi/cli/v0.2.0/terminal-stderr/empty.s | 0 .../wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wasm.go | 0 .../wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go | 0 src/internal/wasi/cli/v0.2.0/terminal-stdin/empty.s | 0 .../wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wasm.go | 0 src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go | 0 src/internal/wasi/cli/v0.2.0/terminal-stdout/empty.s | 0 .../wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wasm.go | 0 .../wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go | 0 src/internal/wasi/clocks/v0.2.0/monotonic-clock/empty.s | 0 .../wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wasm.go | 0 .../wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go | 0 src/internal/wasi/clocks/v0.2.0/wall-clock/empty.s | 0 src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wasm.go | 0 src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go | 0 src/internal/wasi/filesystem/v0.2.0/preopens/empty.s | 0 src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go | 0 src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go | 0 src/internal/wasi/filesystem/v0.2.0/types/abi.go | 0 src/internal/wasi/filesystem/v0.2.0/types/empty.s | 0 src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go | 0 src/internal/wasi/filesystem/v0.2.0/types/types.wit.go | 0 src/internal/wasi/io/v0.2.0/error/empty.s | 0 src/internal/wasi/io/v0.2.0/error/error.wasm.go | 0 src/internal/wasi/io/v0.2.0/error/error.wit.go | 0 src/internal/wasi/io/v0.2.0/poll/empty.s | 0 src/internal/wasi/io/v0.2.0/poll/poll.wasm.go | 0 src/internal/wasi/io/v0.2.0/poll/poll.wit.go | 0 src/internal/wasi/io/v0.2.0/streams/empty.s | 0 src/internal/wasi/io/v0.2.0/streams/streams.wasm.go | 0 src/internal/wasi/io/v0.2.0/streams/streams.wit.go | 0 src/internal/wasi/random/v0.2.0/insecure-seed/empty.s | 0 .../wasi/random/v0.2.0/insecure-seed/insecure-seed.wasm.go | 0 .../wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go | 0 src/internal/wasi/random/v0.2.0/insecure/empty.s | 0 src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go | 0 src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go | 0 src/internal/wasi/random/v0.2.0/random/empty.s | 0 src/internal/wasi/random/v0.2.0/random/random.wasm.go | 0 src/internal/wasi/random/v0.2.0/random/random.wit.go | 0 src/internal/wasi/sockets/v0.2.0/instance-network/empty.s | 0 .../wasi/sockets/v0.2.0/instance-network/instance-network.wasm.go | 0 .../wasi/sockets/v0.2.0/instance-network/instance-network.wit.go | 0 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go | 0 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/empty.s | 0 .../wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wasm.go | 0 .../wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go | 0 src/internal/wasi/sockets/v0.2.0/network/abi.go | 0 src/internal/wasi/sockets/v0.2.0/network/empty.s | 0 src/internal/wasi/sockets/v0.2.0/network/network.wasm.go | 0 src/internal/wasi/sockets/v0.2.0/network/network.wit.go | 0 src/internal/wasi/sockets/v0.2.0/tcp-create-socket/empty.s | 0 .../sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wasm.go | 0 .../sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go | 0 src/internal/wasi/sockets/v0.2.0/tcp/abi.go | 0 src/internal/wasi/sockets/v0.2.0/tcp/empty.s | 0 src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go | 0 src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go | 0 src/internal/wasi/sockets/v0.2.0/udp-create-socket/empty.s | 0 .../sockets/v0.2.0/udp-create-socket/udp-create-socket.wasm.go | 0 .../sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go | 0 src/internal/wasi/sockets/v0.2.0/udp/abi.go | 0 src/internal/wasi/sockets/v0.2.0/udp/empty.s | 0 src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go | 0 src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go | 0 91 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/command/command.wit.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/environment/empty.s mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/environment/environment.wit.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/exit/empty.s mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/exit/exit.wit.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/run/empty.s mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/run/run.exports.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/run/run.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/run/run.wit.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/stderr/empty.s mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/stdin/empty.s mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/stdout/empty.s mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-input/empty.s mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-output/empty.s mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-stderr/empty.s mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-stdin/empty.s mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-stdout/empty.s mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wasm.go mode change 100755 => 100644 src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go mode change 100755 => 100644 src/internal/wasi/clocks/v0.2.0/monotonic-clock/empty.s mode change 100755 => 100644 src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wasm.go mode change 100755 => 100644 src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go mode change 100755 => 100644 src/internal/wasi/clocks/v0.2.0/wall-clock/empty.s mode change 100755 => 100644 src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wasm.go mode change 100755 => 100644 src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go mode change 100755 => 100644 src/internal/wasi/filesystem/v0.2.0/preopens/empty.s mode change 100755 => 100644 src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go mode change 100755 => 100644 src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go mode change 100755 => 100644 src/internal/wasi/filesystem/v0.2.0/types/abi.go mode change 100755 => 100644 src/internal/wasi/filesystem/v0.2.0/types/empty.s mode change 100755 => 100644 src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go mode change 100755 => 100644 src/internal/wasi/filesystem/v0.2.0/types/types.wit.go mode change 100755 => 100644 src/internal/wasi/io/v0.2.0/error/empty.s mode change 100755 => 100644 src/internal/wasi/io/v0.2.0/error/error.wasm.go mode change 100755 => 100644 src/internal/wasi/io/v0.2.0/error/error.wit.go mode change 100755 => 100644 src/internal/wasi/io/v0.2.0/poll/empty.s mode change 100755 => 100644 src/internal/wasi/io/v0.2.0/poll/poll.wasm.go mode change 100755 => 100644 src/internal/wasi/io/v0.2.0/poll/poll.wit.go mode change 100755 => 100644 src/internal/wasi/io/v0.2.0/streams/empty.s mode change 100755 => 100644 src/internal/wasi/io/v0.2.0/streams/streams.wasm.go mode change 100755 => 100644 src/internal/wasi/io/v0.2.0/streams/streams.wit.go mode change 100755 => 100644 src/internal/wasi/random/v0.2.0/insecure-seed/empty.s mode change 100755 => 100644 src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wasm.go mode change 100755 => 100644 src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go mode change 100755 => 100644 src/internal/wasi/random/v0.2.0/insecure/empty.s mode change 100755 => 100644 src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go mode change 100755 => 100644 src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go mode change 100755 => 100644 src/internal/wasi/random/v0.2.0/random/empty.s mode change 100755 => 100644 src/internal/wasi/random/v0.2.0/random/random.wasm.go mode change 100755 => 100644 src/internal/wasi/random/v0.2.0/random/random.wit.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/instance-network/empty.s mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wasm.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/empty.s mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wasm.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/network/abi.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/network/empty.s mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/network/network.wasm.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/network/network.wit.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/tcp-create-socket/empty.s mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wasm.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/tcp/abi.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/tcp/empty.s mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/udp-create-socket/empty.s mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wasm.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/udp/abi.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/udp/empty.s mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go mode change 100755 => 100644 src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go diff --git a/src/internal/wasi/cli/v0.2.0/command/command.wit.go b/src/internal/wasi/cli/v0.2.0/command/command.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/environment/empty.s b/src/internal/wasi/cli/v0.2.0/environment/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go b/src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go b/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/exit/empty.s b/src/internal/wasi/cli/v0.2.0/exit/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go b/src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go b/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/run/empty.s b/src/internal/wasi/cli/v0.2.0/run/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/run/run.exports.go b/src/internal/wasi/cli/v0.2.0/run/run.exports.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/run/run.wasm.go b/src/internal/wasi/cli/v0.2.0/run/run.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/run/run.wit.go b/src/internal/wasi/cli/v0.2.0/run/run.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/stderr/empty.s b/src/internal/wasi/cli/v0.2.0/stderr/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/stdin/empty.s b/src/internal/wasi/cli/v0.2.0/stdin/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/stdout/empty.s b/src/internal/wasi/cli/v0.2.0/stdout/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-input/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-input/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-output/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-output/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stderr/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-stderr/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdin/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-stdin/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdout/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-stdout/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/empty.s b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wasm.go b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/clocks/v0.2.0/wall-clock/empty.s b/src/internal/wasi/clocks/v0.2.0/wall-clock/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wasm.go b/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go b/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/filesystem/v0.2.0/preopens/empty.s b/src/internal/wasi/filesystem/v0.2.0/preopens/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/filesystem/v0.2.0/types/abi.go b/src/internal/wasi/filesystem/v0.2.0/types/abi.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/filesystem/v0.2.0/types/empty.s b/src/internal/wasi/filesystem/v0.2.0/types/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go b/src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go b/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/io/v0.2.0/error/empty.s b/src/internal/wasi/io/v0.2.0/error/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/io/v0.2.0/error/error.wasm.go b/src/internal/wasi/io/v0.2.0/error/error.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/io/v0.2.0/error/error.wit.go b/src/internal/wasi/io/v0.2.0/error/error.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/io/v0.2.0/poll/empty.s b/src/internal/wasi/io/v0.2.0/poll/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/io/v0.2.0/poll/poll.wasm.go b/src/internal/wasi/io/v0.2.0/poll/poll.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/io/v0.2.0/poll/poll.wit.go b/src/internal/wasi/io/v0.2.0/poll/poll.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/io/v0.2.0/streams/empty.s b/src/internal/wasi/io/v0.2.0/streams/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/io/v0.2.0/streams/streams.wasm.go b/src/internal/wasi/io/v0.2.0/streams/streams.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/io/v0.2.0/streams/streams.wit.go b/src/internal/wasi/io/v0.2.0/streams/streams.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/random/v0.2.0/insecure-seed/empty.s b/src/internal/wasi/random/v0.2.0/insecure-seed/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wasm.go b/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go b/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/random/v0.2.0/insecure/empty.s b/src/internal/wasi/random/v0.2.0/insecure/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go b/src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go b/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/random/v0.2.0/random/empty.s b/src/internal/wasi/random/v0.2.0/random/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/random/v0.2.0/random/random.wasm.go b/src/internal/wasi/random/v0.2.0/random/random.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/random/v0.2.0/random/random.wit.go b/src/internal/wasi/random/v0.2.0/random/random.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/instance-network/empty.s b/src/internal/wasi/sockets/v0.2.0/instance-network/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wasm.go b/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go b/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/empty.s b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wasm.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/network/abi.go b/src/internal/wasi/sockets/v0.2.0/network/abi.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/network/empty.s b/src/internal/wasi/sockets/v0.2.0/network/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/network/network.wasm.go b/src/internal/wasi/sockets/v0.2.0/network/network.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/network/network.wit.go b/src/internal/wasi/sockets/v0.2.0/network/network.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/empty.s b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wasm.go b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/abi.go b/src/internal/wasi/sockets/v0.2.0/tcp/abi.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/empty.s b/src/internal/wasi/sockets/v0.2.0/tcp/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/empty.s b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wasm.go b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/udp/abi.go b/src/internal/wasi/sockets/v0.2.0/udp/abi.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/udp/empty.s b/src/internal/wasi/sockets/v0.2.0/udp/empty.s old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go b/src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go old mode 100755 new mode 100644 diff --git a/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go b/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go old mode 100755 new mode 100644 From 4f8672c9ab290c752ce97da4008b407b19111ff7 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sun, 16 Mar 2025 17:44:33 -0700 Subject: [PATCH 442/444] internal/cm: change error type from struct{string} This is a temporary fix until cm@v0.2.2 is released, and package internal/cm and internal/wasi can be regenerated. See #4810 for more information. --- src/internal/cm/case.go | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/internal/cm/case.go b/src/internal/cm/case.go index fa212bb0c9..2ca7c28da9 100644 --- a/src/internal/cm/case.go +++ b/src/internal/cm/case.go @@ -9,7 +9,7 @@ func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, te if len(cases) <= linearScanThreshold { return func(v *T, text []byte) error { if len(text) == 0 { - return errEmpty + return &emptyTextError{} } s := string(text) for i := 0; i < len(cases); i++ { @@ -18,7 +18,7 @@ func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, te return nil } } - return errNoMatchingCase + return &noMatchingCaseError{} } } @@ -29,11 +29,11 @@ func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, te return func(v *T, text []byte) error { if len(text) == 0 { - return errEmpty + return &emptyTextError{} } c, ok := m[string(text)] if !ok { - return errNoMatchingCase + return &noMatchingCaseError{} } *v = c return nil @@ -42,15 +42,10 @@ func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, te const linearScanThreshold = 16 -var ( - errEmpty = &stringError{"empty text"} - errNoMatchingCase = &stringError{"no matching case"} -) +type emptyTextError struct{} -type stringError struct { - err string -} +func (*emptyTextError) Error() string { return "empty text" } -func (err *stringError) Error() string { - return err.err -} +type noMatchingCaseError struct{} + +func (*noMatchingCaseError) Error() string { return "no matching case" } From e5a8d693006cdaf6da471e5eb6fb53188122fa28 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sun, 16 Mar 2025 20:08:14 -0700 Subject: [PATCH 443/444] internal/wasm-tools: update to go.bytecodealliance.org@v0.6.2 --- internal/wasm-tools/go.mod | 4 ++-- internal/wasm-tools/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/wasm-tools/go.mod b/internal/wasm-tools/go.mod index c790a7ab05..d91b5475cd 100644 --- a/internal/wasm-tools/go.mod +++ b/internal/wasm-tools/go.mod @@ -3,8 +3,8 @@ module github.com/tinygo-org/tinygo/internal/wasm-tools go 1.23.0 require ( - go.bytecodealliance.org v0.6.1 - go.bytecodealliance.org/cm v0.2.1 + go.bytecodealliance.org v0.6.2 + go.bytecodealliance.org/cm v0.2.2 ) require ( diff --git a/internal/wasm-tools/go.sum b/internal/wasm-tools/go.sum index 3f2a32ded1..374f54e169 100644 --- a/internal/wasm-tools/go.sum +++ b/internal/wasm-tools/go.sum @@ -29,10 +29,10 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg= github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= -go.bytecodealliance.org v0.6.1 h1:H3vVYeEZa8Gs9t9YyZ4ZjA8sr5c6Xfkz3p+cM3543aA= -go.bytecodealliance.org v0.6.1/go.mod h1:qPL6PksrsjAxl6o23HuX4pGO6n3UxiESPbvRn8ZVot0= -go.bytecodealliance.org/cm v0.2.1 h1:sFUQRPfM+ku7hKgFwxGdwjghv4QtTLukpqsAHgE1Tek= -go.bytecodealliance.org/cm v0.2.1/go.mod h1:JD5vtVNZv7sBoQQkvBvAAVKJPhR/bqBH7yYXTItMfZI= +go.bytecodealliance.org v0.6.2 h1:Jy4u5DVmSkXgsnwojBhJ+AD/YsJsR3VzVnxF0xRCqTQ= +go.bytecodealliance.org v0.6.2/go.mod h1:gqjTJm0y9NSksG4py/lSjIQ/SNuIlOQ+hCIEPQwtJgA= +go.bytecodealliance.org/cm v0.2.2 h1:M9iHS6qs884mbQbIjtLX1OifgyPG9DuMs2iwz8G4WQA= +go.bytecodealliance.org/cm v0.2.2/go.mod h1:JD5vtVNZv7sBoQQkvBvAAVKJPhR/bqBH7yYXTItMfZI= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= From 3e60eeb368f25f237a512e7553fd6d70f36dc74c Mon Sep 17 00:00:00 2001 From: deadprogram Date: Tue, 18 Mar 2025 23:33:27 +0100 Subject: [PATCH 444/444] Prepare for release 0.37.0 Signed-off-by: deadprogram --- CHANGELOG.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ goenv/version.go | 2 +- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4415de438e..1c1d488934 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,53 @@ +0.37.0 +--- +* **general** + - add the Boehm-Demers-Weiser GC on Linux +* **ci** + - add more tests for wasm and baremetal +* **compiler** + - crypto/internal/sysrand is allowed to use unsafe signatures +* **examples** + - add goroutine benchmark to examples +* **fixes** + - ensure use of pointers for SPI interface on atsam21/atsam51 and other machines/boards that were missing implementation (#4798) + - replace loop counter with hw timer for USB SetAddressReq on rp2040 (#4796) +* **internal** + - update to go.bytecodealliance.org@v0.6.2 in GNUmakefile and internal/wasm-tools + - exclude certain files when copying package in internal/cm + - update to go.bytecodealliance.org/cm@v0.2.2 in internal/cm + - remove old reflect.go in internal/reflectlite +* **loader** + - use build tags for package iter and iter methods on reflect.Value in loader, iter, reflect + - add shim for go1.22 and earlier in loader, iter +* **machine** + - bump rp2040 to 200MHz (#4768) + - correct register address for Pin.SetInterrupt for rp2350 (#4782) + - don't block the rp2xxx UART interrupt handler + - fix RP2040 Pico board on the playground + - add flash support for rp2350 (#4803) +* **os** + - add stub Symlink for wasm +* **refactor** + - use *SPI everywhere to make consistent for implementations. Fixes #4663 "in reverse" by making SPI a pointer everywhere, as discussed in the comments. +* **reflect** + - add Value.SetIter{Key,Value} and MapIter.Reset in reflect, internal/reflectlite + - embed reflectlite types into reflect types in reflect, internal/reflectlite + - add Go 1.24 iter.Seq[2] methods + - copy reflect iter tests from upstream Go + - panic on Type.CanSeq[2] instead of returning false + - remove strconv.go + - remove unused go:linkname functions +* **riscv-qemu** + - add VirtIO RNG device + - increase stack size +* **runtime** + - only allocate heap memory when needed + - remove unused file func.go + - use package reflectlite +* **transform** + - cherry-pick from #4774 + + 0.36.0 --- * **general** diff --git a/goenv/version.go b/goenv/version.go index 1f0f4c75ea..8b0aa07631 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -10,7 +10,7 @@ import ( // Version of TinyGo. // Update this value before release of new version of software. -const version = "0.37.0-dev" +const version = "0.37.0" // Return TinyGo version, either in the form 0.30.0 or as a development version // (like 0.30.0-dev-abcd012).