From 915132645eb29eed4de3fbf78d0e1077a8c6a8cf Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 26 Oct 2024 10:58:27 +0200 Subject: [PATCH 001/196] 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 002/196] 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 003/196] 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 004/196] 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 005/196] 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 006/196] 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 007/196] 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 008/196] 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 009/196] 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 010/196] 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 011/196] 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 012/196] 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 013/196] 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 014/196] 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 015/196] 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 016/196] 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 017/196] 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 018/196] 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 019/196] 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 020/196] 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 021/196] 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 022/196] 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 023/196] 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 024/196] 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 025/196] 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 026/196] 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 027/196] 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 028/196] 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 029/196] 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 030/196] 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 031/196] 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 032/196] 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 033/196] 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 034/196] 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 035/196] 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 036/196] 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 037/196] 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 038/196] 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 039/196] 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 040/196] 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 041/196] 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 042/196] 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 043/196] 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 044/196] 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 045/196] 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 046/196] 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 047/196] 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 048/196] 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 049/196] 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 050/196] 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 051/196] 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 052/196] 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 053/196] 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 054/196] 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 055/196] 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 056/196] 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 057/196] 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 058/196] 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 059/196] 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 060/196] 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 061/196] 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 062/196] 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 063/196] 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 064/196] 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 065/196] 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 066/196] 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 067/196] 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 068/196] 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 069/196] 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 070/196] 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 071/196] 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 072/196] 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 073/196] 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 074/196] 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 075/196] 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 076/196] 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 077/196] 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 078/196] 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 079/196] 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 080/196] 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 081/196] 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 082/196] 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 083/196] 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 084/196] 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 085/196] 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 086/196] 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 087/196] 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 088/196] 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 089/196] 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 090/196] 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 091/196] 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 092/196] 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 093/196] 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 094/196] 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 095/196] 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 096/196] 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 097/196] 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 098/196] 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 099/196] 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 100/196] 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 101/196] 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 102/196] 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 103/196] 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 104/196] 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 105/196] 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 106/196] 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 107/196] 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 108/196] 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 109/196] 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 110/196] 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 111/196] 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 112/196] 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 113/196] 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 114/196] 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 115/196] 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 116/196] 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 117/196] 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 118/196] 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 119/196] 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 120/196] 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 121/196] 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 122/196] 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 123/196] 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 124/196] 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 125/196] 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 126/196] 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 127/196] 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 128/196] 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 129/196] 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 130/196] 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 131/196] 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 132/196] 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 133/196] 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 134/196] 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 135/196] 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 136/196] 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 137/196] 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 138/196] 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 139/196] 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 140/196] 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 141/196] 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 142/196] 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 143/196] 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 144/196] 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 145/196] 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 146/196] 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 147/196] 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 148/196] 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 149/196] 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 150/196] 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 151/196] 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 152/196] 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 153/196] 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 154/196] 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 155/196] 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 156/196] 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 157/196] 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 158/196] 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 159/196] 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 160/196] 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 161/196] 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 162/196] 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 163/196] 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 164/196] 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 165/196] 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 166/196] =?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 167/196] 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 168/196] 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 169/196] 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 170/196] 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 171/196] 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 172/196] 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 173/196] 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 174/196] 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 175/196] 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 176/196] 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 177/196] 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 178/196] 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 179/196] 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 180/196] 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 181/196] 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 182/196] 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 183/196] 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 184/196] 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 185/196] 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 186/196] 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 187/196] 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 188/196] 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 189/196] 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 190/196] 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 191/196] 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 192/196] 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 193/196] 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 194/196] 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 195/196] 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 196/196] 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).