From e041a8ed882c282b8ff76d8ce8d94838ba053f3f Mon Sep 17 00:00:00 2001 From: sago35 Date: Mon, 12 Jun 2023 22:01:59 +0900 Subject: [PATCH 01/75] goenv: update to new v0.29.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 e74f14c35e..4be6bcbc0c 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -12,7 +12,7 @@ import ( // Version of TinyGo. // Update this value before release of new version of software. -const Version = "0.28.1" +const Version = "0.29.0-dev" var ( // This variable is set at build time using -ldflags parameters. From 785eb93b62aedf1bc2fbcaf3c9f36838117d9842 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 12 Jun 2023 00:07:53 +0200 Subject: [PATCH 02/75] ci: rename release-double-zipped to something more useful The Linux artifacts have clear names (linux-amd64-double-zipped etc), but the MacOS and Windows ones didn't. This patch renames these artifact names to be more readable, especially when downloading the artifacts. --- .github/workflows/build-macos.yml | 2 +- .github/workflows/windows.yml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 3151ac5637..51c145f337 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -108,7 +108,7 @@ jobs: # We're doing the former here, to keep artifact uploads fast. uses: actions/upload-artifact@v2 with: - name: release-double-zipped + name: darwin-amd64-double-zipped path: build/tinygo.darwin-amd64.tar.gz - name: Smoke tests shell: bash diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index fd46e8d874..55e752d707 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -118,7 +118,7 @@ jobs: # We're doing the former here, to keep artifact uploads fast. uses: actions/upload-artifact@v3 with: - name: release-double-zipped + name: windows-amd64-double-zipped path: build/release/release.zip smoke-test-windows: @@ -148,7 +148,7 @@ jobs: - name: Download TinyGo build uses: actions/download-artifact@v2 with: - name: release-double-zipped + name: windows-amd64-double-zipped path: build/ - name: Unzip TinyGo build shell: bash @@ -178,7 +178,7 @@ jobs: - name: Download TinyGo build uses: actions/download-artifact@v2 with: - name: release-double-zipped + name: windows-amd64-double-zipped path: build/ - name: Unzip TinyGo build shell: bash @@ -214,7 +214,7 @@ jobs: - name: Download TinyGo build uses: actions/download-artifact@v2 with: - name: release-double-zipped + name: windows-amd64-double-zipped path: build/ - name: Unzip TinyGo build shell: bash From 91ee4d0030a12a5d65536aab5f64570acc43283c Mon Sep 17 00:00:00 2001 From: Stepan Rakitin Date: Mon, 12 Jun 2023 15:55:21 +0200 Subject: [PATCH 03/75] os: define ErrNoDeadline --- src/os/errors.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/os/errors.go b/src/os/errors.go index 23b738c97c..4b087a8435 100644 --- a/src/os/errors.go +++ b/src/os/errors.go @@ -32,6 +32,10 @@ var ( // The following code is copied from the official implementation. // src/internal/poll/fd.go +// ErrNoDeadline is returned when a request is made to set a deadline +// on a file type that does not use the poller. +var ErrNoDeadline = errors.New("file type does not support deadline") + // ErrDeadlineExceeded is returned for an expired deadline. // This is exported by the os package as os.ErrDeadlineExceeded. var ErrDeadlineExceeded error = &DeadlineExceededError{} From 0cb5d336f428d8bfc420461f4a1d0b50d8294a48 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 10 Jun 2023 15:03:57 +0200 Subject: [PATCH 04/75] reflect: use .key() instead of a type assert This should be ever so slightly more efficient. --- src/reflect/value.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reflect/value.go b/src/reflect/value.go index 96cbcb8046..92d26409dd 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -913,7 +913,7 @@ func (v Value) MapKeys() []Value { k := New(v.typecode.Key()) e := New(v.typecode.Elem()) - keyType := v.typecode.Key().(*rawType) + keyType := v.typecode.key() isKeyStoredAsInterface := keyType.Kind() != String && !keyType.isBinary() for hashmapNext(v.pointer(), it, k.value, e.value) { From 6d5f4c4be2096b0d84dffbd7a0960fd6249c4b6b Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 30 Apr 2023 17:53:07 +0200 Subject: [PATCH 05/75] ci: update Node.js from version 14 to version 16 Node.js 14 is not maintained anymore, so we can drop support for it. --- .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 bd020b77e0..05199e21ec 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -182,7 +182,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v3 with: - node-version: '14' + node-version: '16' - name: Install wasmtime run: | mkdir -p $HOME/.wasmtime $HOME/.wasmtime/bin From 4d2a6d2bbeef8db2a2e9714f3d91372f89bc9b80 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 30 Apr 2023 17:54:03 +0200 Subject: [PATCH 06/75] wasm: remove i64 workaround, use BigInt instead Browsers previously didn't support the WebAssembly i64 type, so we had to work around that limitation by converting the LLVM i64 type to something else. Some people used a pair of i32 values, but we used a pointer to a stack allocated i64. Now however, all major browsers and Node.js do support WebAssembly BigInt integration so that i64 values can be passed back and forth between WebAssembly and JavaScript easily. Therefore, I think the time has come to drop support for this workaround. For more information: https://v8.dev/features/wasm-bigint (note that TinyGo has used a slightly different way of passing i64 values between JS and Wasm). For information on browser support: https://webassembly.org/roadmap/ --- builder/build.go | 11 -- compileopts/config.go | 5 - compileopts/target.go | 1 - targets/wasi.json | 3 +- targets/wasm.json | 3 +- targets/wasm_exec.js | 150 ++++++++++++-------------- transform/testdata/wasm-abi.ll | 28 ----- transform/testdata/wasm-abi.out.ll | 45 -------- transform/wasm-abi.go | 167 ----------------------------- transform/wasm-abi_test.go | 19 ---- 10 files changed, 72 insertions(+), 360 deletions(-) delete mode 100644 transform/testdata/wasm-abi.ll delete mode 100644 transform/testdata/wasm-abi.out.ll delete mode 100644 transform/wasm-abi.go delete mode 100644 transform/wasm-abi_test.go diff --git a/builder/build.go b/builder/build.go index 516b9e64f7..2e8577e377 100644 --- a/builder/build.go +++ b/builder/build.go @@ -1054,17 +1054,6 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error { return err } - // Browsers cannot handle external functions that have type i64 because it - // cannot be represented exactly in JavaScript (JS only has doubles). To - // keep functions interoperable, pass int64 types as pointers to - // stack-allocated values. - if config.WasmAbi() == "js" { - err := transform.ExternalInt64AsPtr(mod, config) - if err != nil { - return err - } - } - // Optimization levels here are roughly the same as Clang, but probably not // exactly. optLevel, sizeLevel, inlinerThreshold := config.OptLevels() diff --git a/compileopts/config.go b/compileopts/config.go index 9a4bc31069..39fc4f2ac2 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -492,11 +492,6 @@ func (c *Config) RelocationModel() string { return "static" } -// WasmAbi returns the WASM ABI which is specified in the target JSON file. -func (c *Config) WasmAbi() string { - return c.Target.WasmAbi -} - // EmulatorName is a shorthand to get the command for this emulator, something // like qemu-system-arm or simavr. func (c *Config) EmulatorName() string { diff --git a/compileopts/target.go b/compileopts/target.go index 40a1d445c7..3040f9022f 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -62,7 +62,6 @@ type TargetSpec struct { JLinkDevice string `json:"jlink-device"` CodeModel string `json:"code-model"` RelocationModel string `json:"relocation-model"` - WasmAbi string `json:"wasm-abi"` } // overrideProperties overrides all properties that are set in child into itself using reflection. diff --git a/targets/wasi.json b/targets/wasi.json index 4c43193f04..f55c7c1fd7 100644 --- a/targets/wasi.json +++ b/targets/wasi.json @@ -21,6 +21,5 @@ "extra-files": [ "src/runtime/asm_tinygowasm.S" ], - "emulator": "wasmtime --mapdir=/tmp::{tmpDir} {}", - "wasm-abi": "generic" + "emulator": "wasmtime --mapdir=/tmp::{tmpDir} {}" } diff --git a/targets/wasm.json b/targets/wasm.json index 26494cc450..37034c6c3a 100644 --- a/targets/wasm.json +++ b/targets/wasm.json @@ -22,6 +22,5 @@ "extra-files": [ "src/runtime/asm_tinygowasm.S" ], - "emulator": "node {root}/targets/wasm_exec.js {}", - "wasm-abi": "js" + "emulator": "node {root}/targets/wasm_exec.js {}" } diff --git a/targets/wasm_exec.js b/targets/wasm_exec.js index 8021b44eb9..8e29d6266c 100644 --- a/targets/wasm_exec.js +++ b/targets/wasm_exec.js @@ -130,6 +130,7 @@ const encoder = new TextEncoder("utf-8"); const decoder = new TextDecoder("utf-8"); + let reinterpretBuf = new DataView(new ArrayBuffer(8)); var logLine = []; global.Go = class { @@ -142,19 +143,9 @@ return new DataView(this._inst.exports.memory.buffer); } - const setInt64 = (addr, v) => { - mem().setUint32(addr + 0, v, true); - mem().setUint32(addr + 4, Math.floor(v / 4294967296), true); - } - - const getInt64 = (addr) => { - const low = mem().getUint32(addr + 0, true); - const high = mem().getInt32(addr + 4, true); - return low + high * 4294967296; - } - - const loadValue = (addr) => { - const f = mem().getFloat64(addr, true); + const unboxValue = (v_ref) => { + reinterpretBuf.setBigInt64(0, v_ref, true); + const f = reinterpretBuf.getFloat64(0, true); if (f === 0) { return undefined; } @@ -162,71 +153,70 @@ return f; } - const id = mem().getUint32(addr, true); + const id = v_ref & 0xffffffffn; return this._values[id]; } - const storeValue = (addr, v) => { - const nanHead = 0x7FF80000; + + const loadValue = (addr) => { + let v_ref = mem().getBigUint64(addr, true); + return unboxValue(v_ref); + } + + const boxValue = (v) => { + const nanHead = 0x7FF80000n; if (typeof v === "number") { if (isNaN(v)) { - mem().setUint32(addr + 4, nanHead, true); - mem().setUint32(addr, 0, true); - return; + return nanHead << 32n; } if (v === 0) { - mem().setUint32(addr + 4, nanHead, true); - mem().setUint32(addr, 1, true); - return; + return (nanHead << 32n) | 1n; } - mem().setFloat64(addr, v, true); - return; + reinterpretBuf.setFloat64(0, v, true); + return reinterpretBuf.getBigInt64(0, true); } switch (v) { case undefined: - mem().setFloat64(addr, 0, true); - return; + return 0n; case null: - mem().setUint32(addr + 4, nanHead, true); - mem().setUint32(addr, 2, true); - return; + return (nanHead << 32n) | 2n; case true: - mem().setUint32(addr + 4, nanHead, true); - mem().setUint32(addr, 3, true); - return; + return (nanHead << 32n) | 3n; case false: - mem().setUint32(addr + 4, nanHead, true); - mem().setUint32(addr, 4, true); - return; + return (nanHead << 32n) | 4n; } let id = this._ids.get(v); if (id === undefined) { id = this._idPool.pop(); if (id === undefined) { - id = this._values.length; + id = BigInt(this._values.length); } this._values[id] = v; this._goRefCounts[id] = 0; this._ids.set(v, id); } this._goRefCounts[id]++; - let typeFlag = 1; + let typeFlag = 1n; switch (typeof v) { case "string": - typeFlag = 2; + typeFlag = 2n; break; case "symbol": - typeFlag = 3; + typeFlag = 3n; break; case "function": - typeFlag = 4; + typeFlag = 4n; break; } - mem().setUint32(addr + 4, nanHead | typeFlag, true); - mem().setUint32(addr, id, true); + return id | ((nanHead | typeFlag) << 32n); + } + + const storeValue = (addr, v) => { + let v_ref = boxValue(v); + mem().setBigUint64(addr, v_ref, true); } const loadSlice = (array, len, cap) => { @@ -307,54 +297,54 @@ }, // func finalizeRef(v ref) - "syscall/js.finalizeRef": (sp) => { + "syscall/js.finalizeRef": (v_ref) => { // Note: TinyGo does not support finalizers so this should never be // called. console.error('syscall/js.finalizeRef not implemented'); }, // func stringVal(value string) ref - "syscall/js.stringVal": (ret_ptr, value_ptr, value_len) => { + "syscall/js.stringVal": (value_ptr, value_len) => { const s = loadString(value_ptr, value_len); - storeValue(ret_ptr, s); + return boxValue(s); }, // func valueGet(v ref, p string) ref - "syscall/js.valueGet": (retval, v_addr, p_ptr, p_len) => { + "syscall/js.valueGet": (v_ref, p_ptr, p_len) => { let prop = loadString(p_ptr, p_len); - let value = loadValue(v_addr); - let result = Reflect.get(value, prop); - storeValue(retval, result); + let v = unboxValue(v_ref); + let result = Reflect.get(v, prop); + return boxValue(result); }, // func valueSet(v ref, p string, x ref) - "syscall/js.valueSet": (v_addr, p_ptr, p_len, x_addr) => { - const v = loadValue(v_addr); + "syscall/js.valueSet": (v_ref, p_ptr, p_len, x_ref) => { + const v = unboxValue(v_ref); const p = loadString(p_ptr, p_len); - const x = loadValue(x_addr); + const x = unboxValue(x_ref); Reflect.set(v, p, x); }, // func valueDelete(v ref, p string) - "syscall/js.valueDelete": (v_addr, p_ptr, p_len) => { - const v = loadValue(v_addr); + "syscall/js.valueDelete": (v_ref, p_ptr, p_len) => { + const v = unboxValue(v_ref); const p = loadString(p_ptr, p_len); Reflect.deleteProperty(v, p); }, // func valueIndex(v ref, i int) ref - "syscall/js.valueIndex": (ret_addr, v_addr, i) => { - storeValue(ret_addr, Reflect.get(loadValue(v_addr), i)); + "syscall/js.valueIndex": (v_ref, i) => { + return boxValue(Reflect.get(unboxValue(v_ref), i)); }, // valueSetIndex(v ref, i int, x ref) - "syscall/js.valueSetIndex": (v_addr, i, x_addr) => { - Reflect.set(loadValue(v_addr), i, loadValue(x_addr)); + "syscall/js.valueSetIndex": (v_ref, i, x_ref) => { + Reflect.set(unboxValue(v_ref), i, unboxValue(x_ref)); }, // func valueCall(v ref, m string, args []ref) (ref, bool) - "syscall/js.valueCall": (ret_addr, v_addr, m_ptr, m_len, args_ptr, args_len, args_cap) => { - const v = loadValue(v_addr); + "syscall/js.valueCall": (ret_addr, v_ref, m_ptr, m_len, args_ptr, args_len, args_cap) => { + const v = unboxValue(v_ref); const name = loadString(m_ptr, m_len); const args = loadSliceOfValues(args_ptr, args_len, args_cap); try { @@ -368,9 +358,9 @@ }, // func valueInvoke(v ref, args []ref) (ref, bool) - "syscall/js.valueInvoke": (ret_addr, v_addr, args_ptr, args_len, args_cap) => { + "syscall/js.valueInvoke": (ret_addr, v_ref, args_ptr, args_len, args_cap) => { try { - const v = loadValue(v_addr); + const v = unboxValue(v_ref); const args = loadSliceOfValues(args_ptr, args_len, args_cap); storeValue(ret_addr, Reflect.apply(v, undefined, args)); mem().setUint8(ret_addr + 8, 1); @@ -381,8 +371,8 @@ }, // func valueNew(v ref, args []ref) (ref, bool) - "syscall/js.valueNew": (ret_addr, v_addr, args_ptr, args_len, args_cap) => { - const v = loadValue(v_addr); + "syscall/js.valueNew": (ret_addr, v_ref, args_ptr, args_len, args_cap) => { + const v = unboxValue(v_ref); const args = loadSliceOfValues(args_ptr, args_len, args_cap); try { storeValue(ret_addr, Reflect.construct(v, args)); @@ -394,62 +384,62 @@ }, // func valueLength(v ref) int - "syscall/js.valueLength": (v_addr) => { - return loadValue(v_addr).length; + "syscall/js.valueLength": (v_ref) => { + return unboxValue(v_ref).length; }, // valuePrepareString(v ref) (ref, int) - "syscall/js.valuePrepareString": (ret_addr, v_addr) => { - const s = String(loadValue(v_addr)); + "syscall/js.valuePrepareString": (ret_addr, v_ref) => { + const s = String(unboxValue(v_ref)); const str = encoder.encode(s); storeValue(ret_addr, str); - setInt64(ret_addr + 8, str.length); + mem().setInt32(ret_addr + 8, str.length, true); }, // valueLoadString(v ref, b []byte) - "syscall/js.valueLoadString": (v_addr, slice_ptr, slice_len, slice_cap) => { - const str = loadValue(v_addr); + "syscall/js.valueLoadString": (v_ref, slice_ptr, slice_len, slice_cap) => { + const str = unboxValue(v_ref); loadSlice(slice_ptr, slice_len, slice_cap).set(str); }, // func valueInstanceOf(v ref, t ref) bool - "syscall/js.valueInstanceOf": (v_addr, t_addr) => { - return loadValue(v_addr) instanceof loadValue(t_addr); + "syscall/js.valueInstanceOf": (v_ref, t_ref) => { + return unboxValue(v_ref) instanceof unboxValue(t_ref); }, // func copyBytesToGo(dst []byte, src ref) (int, bool) - "syscall/js.copyBytesToGo": (ret_addr, dest_addr, dest_len, dest_cap, source_addr) => { + "syscall/js.copyBytesToGo": (ret_addr, dest_addr, dest_len, dest_cap, src_ref) => { let num_bytes_copied_addr = ret_addr; let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable const dst = loadSlice(dest_addr, dest_len); - const src = loadValue(source_addr); + const src = unboxValue(src_ref); if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { mem().setUint8(returned_status_addr, 0); // Return "not ok" status return; } const toCopy = src.subarray(0, dst.length); dst.set(toCopy); - setInt64(num_bytes_copied_addr, toCopy.length); + mem().setUint32(num_bytes_copied_addr, toCopy.length, true); mem().setUint8(returned_status_addr, 1); // Return "ok" status }, // copyBytesToJS(dst ref, src []byte) (int, bool) // Originally copied from upstream Go project, then modified: // https://github.com/golang/go/blob/3f995c3f3b43033013013e6c7ccc93a9b1411ca9/misc/wasm/wasm_exec.js#L404-L416 - "syscall/js.copyBytesToJS": (ret_addr, dest_addr, source_addr, source_len, source_cap) => { + "syscall/js.copyBytesToJS": (ret_addr, dst_ref, src_addr, src_len, src_cap) => { let num_bytes_copied_addr = ret_addr; let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable - const dst = loadValue(dest_addr); - const src = loadSlice(source_addr, source_len); + const dst = unboxValue(dst_ref); + const src = loadSlice(src_addr, src_len); if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { mem().setUint8(returned_status_addr, 0); // Return "not ok" status return; } const toCopy = src.subarray(0, dst.length); dst.set(toCopy); - setInt64(num_bytes_copied_addr, toCopy.length); + mem().setUint32(num_bytes_copied_addr, toCopy.length, true); mem().setUint8(returned_status_addr, 1); // Return "ok" status }, } diff --git a/transform/testdata/wasm-abi.ll b/transform/testdata/wasm-abi.ll deleted file mode 100644 index ade4b5af56..0000000000 --- a/transform/testdata/wasm-abi.ll +++ /dev/null @@ -1,28 +0,0 @@ -target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" -target triple = "wasm32-unknown-unknown-wasm" - -declare i64 @externalCall(ptr, i32, i64) - -define internal i64 @testCall(ptr %ptr, i32 %len, i64 %foo) { - %val = call i64 @externalCall(ptr %ptr, i32 %len, i64 %foo) - ret i64 %val -} - -define internal i64 @testCallNonEntry(ptr %ptr, i32 %len) { -entry: - br label %bb1 - -bb1: - %val = call i64 @externalCall(ptr %ptr, i32 %len, i64 3) - ret i64 %val -} - -define void @exportedFunction(i64 %foo) { - %unused = shl i64 %foo, 1 - ret void -} - -define internal void @callExportedFunction(i64 %foo) { - call void @exportedFunction(i64 %foo) - ret void -} diff --git a/transform/testdata/wasm-abi.out.ll b/transform/testdata/wasm-abi.out.ll deleted file mode 100644 index a1fc7d6a9b..0000000000 --- a/transform/testdata/wasm-abi.out.ll +++ /dev/null @@ -1,45 +0,0 @@ -target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" -target triple = "wasm32-unknown-unknown-wasm" - -declare i64 @"externalCall$i64wrap"(ptr, i32, i64) - -define internal i64 @testCall(ptr %ptr, i32 %len, i64 %foo) { - %i64asptr = alloca i64, align 8 - %i64asptr1 = alloca i64, align 8 - store i64 %foo, ptr %i64asptr1, align 8 - call void @externalCall(ptr %i64asptr, ptr %ptr, i32 %len, ptr %i64asptr1) - %retval = load i64, ptr %i64asptr, align 8 - ret i64 %retval -} - -define internal i64 @testCallNonEntry(ptr %ptr, i32 %len) { -entry: - %i64asptr = alloca i64, align 8 - %i64asptr1 = alloca i64, align 8 - br label %bb1 - -bb1: ; preds = %entry - store i64 3, ptr %i64asptr1, align 8 - call void @externalCall(ptr %i64asptr, ptr %ptr, i32 %len, ptr %i64asptr1) - %retval = load i64, ptr %i64asptr, align 8 - ret i64 %retval -} - -define internal void @"exportedFunction$i64wrap"(i64 %foo) unnamed_addr { - %unused = shl i64 %foo, 1 - ret void -} - -define internal void @callExportedFunction(i64 %foo) { - call void @"exportedFunction$i64wrap"(i64 %foo) - ret void -} - -declare void @externalCall(ptr, ptr, i32, ptr) - -define void @exportedFunction(ptr %0) { -entry: - %i64 = load i64, ptr %0, align 8 - call void @"exportedFunction$i64wrap"(i64 %i64) - ret void -} diff --git a/transform/wasm-abi.go b/transform/wasm-abi.go deleted file mode 100644 index 081558c9e4..0000000000 --- a/transform/wasm-abi.go +++ /dev/null @@ -1,167 +0,0 @@ -package transform - -import ( - "errors" - "strings" - - "github.com/tinygo-org/tinygo/compileopts" - "tinygo.org/x/go-llvm" -) - -// ExternalInt64AsPtr converts i64 parameters in externally-visible functions to -// values passed by reference (*i64), to work around the lack of 64-bit integers -// in JavaScript (commonly used together with WebAssembly). Once that's -// resolved, this pass may be avoided. For more details: -// https://github.com/WebAssembly/design/issues/1172 -// -// This pass is enabled via the wasm-abi JSON target key. -func ExternalInt64AsPtr(mod llvm.Module, config *compileopts.Config) error { - ctx := mod.Context() - builder := ctx.NewBuilder() - defer builder.Dispose() - int64Type := ctx.Int64Type() - int64PtrType := llvm.PointerType(int64Type, 0) - - // This builder is only used for creating new allocas in the entry block of - // a function, avoiding many SetInsertPoint* calls. - entryBlockBuilder := ctx.NewBuilder() - defer entryBlockBuilder.Dispose() - - for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { - if fn.Linkage() != llvm.ExternalLinkage { - // Only change externally visible functions (exports and imports). - continue - } - if strings.HasPrefix(fn.Name(), "llvm.") || strings.HasPrefix(fn.Name(), "runtime.") { - // Do not try to modify the signature of internal LLVM functions and - // assume that runtime functions are only temporarily exported for - // transforms. - continue - } - if !fn.GetStringAttributeAtIndex(-1, "tinygo-methods").IsNil() { - // These are internal functions (interface method call, interface - // type assert) that will be lowered by the interface lowering pass. - // Don't transform them. - continue - } - - hasInt64 := false - paramTypes := []llvm.Type{} - - // Check return type for 64-bit integer. - fnType := fn.GlobalValueType() - returnType := fnType.ReturnType() - if returnType == int64Type { - hasInt64 = true - paramTypes = append(paramTypes, int64PtrType) - returnType = ctx.VoidType() - } - - // Check param types for 64-bit integers. - for param := fn.FirstParam(); !param.IsNil(); param = llvm.NextParam(param) { - if param.Type() == int64Type { - hasInt64 = true - paramTypes = append(paramTypes, int64PtrType) - } else { - paramTypes = append(paramTypes, param.Type()) - } - } - - if !hasInt64 { - // No i64 in the paramter list. - continue - } - - // Add $i64wrapper to the real function name as it is only used - // internally. - // Add a new function with the correct signature that is exported. - name := fn.Name() - fn.SetName(name + "$i64wrap") - externalFnType := llvm.FunctionType(returnType, paramTypes, fnType.IsFunctionVarArg()) - externalFn := llvm.AddFunction(mod, name, externalFnType) - AddStandardAttributes(fn, config) - - if fn.IsDeclaration() { - // Just a declaration: the definition doesn't exist on the Go side - // so it cannot be called from external code. - // Update all users to call the external function. - // The old $i64wrapper function could be removed, but it may as well - // be left in place. - for _, call := range getUses(fn) { - entryBlockBuilder.SetInsertPointBefore(call.InstructionParent().Parent().EntryBasicBlock().FirstInstruction()) - builder.SetInsertPointBefore(call) - callParams := []llvm.Value{} - var retvalAlloca llvm.Value - if fnType.ReturnType() == int64Type { - retvalAlloca = entryBlockBuilder.CreateAlloca(int64Type, "i64asptr") - callParams = append(callParams, retvalAlloca) - } - for i := 0; i < call.OperandsCount()-1; i++ { - operand := call.Operand(i) - if operand.Type() == int64Type { - // Pass a stack-allocated pointer instead of the value - // itself. - alloca := entryBlockBuilder.CreateAlloca(int64Type, "i64asptr") - builder.CreateStore(operand, alloca) - callParams = append(callParams, alloca) - } else { - // Unchanged parameter. - callParams = append(callParams, operand) - } - } - var callName string - if returnType.TypeKind() != llvm.VoidTypeKind { - // Only use the name of the old call instruction if the new - // call is not a void call. - // A call instruction with an i64 return type may have had a - // name, but it cannot have a name after this transform - // because the return type will now be void. - callName = call.Name() - } - if fnType.ReturnType() == int64Type { - // Pass a stack-allocated pointer as the first parameter - // where the return value should be stored, instead of using - // the regular return value. - builder.CreateCall(externalFnType, externalFn, callParams, callName) - returnValue := builder.CreateLoad(int64Type, retvalAlloca, "retval") - call.ReplaceAllUsesWith(returnValue) - call.EraseFromParentAsInstruction() - } else { - newCall := builder.CreateCall(externalFnType, externalFn, callParams, callName) - call.ReplaceAllUsesWith(newCall) - call.EraseFromParentAsInstruction() - } - } - } else { - // The function has a definition in Go. This means that it may still - // be called both Go and from external code. - // Keep existing calls with the existing convention in place (for - // better performance), but export a new wrapper function with the - // correct calling convention. - fn.SetLinkage(llvm.InternalLinkage) - fn.SetUnnamedAddr(true) - entryBlock := ctx.AddBasicBlock(externalFn, "entry") - builder.SetInsertPointAtEnd(entryBlock) - var callParams []llvm.Value - if fnType.ReturnType() == int64Type { - return errors.New("not yet implemented: exported function returns i64 with the JS wasm-abi; " + - "see https://tinygo.org/compiler-internals/calling-convention/") - } - for i, origParam := range fn.Params() { - paramValue := externalFn.Param(i) - if origParam.Type() == int64Type { - paramValue = builder.CreateLoad(int64Type, paramValue, "i64") - } - callParams = append(callParams, paramValue) - } - retval := builder.CreateCall(fn.GlobalValueType(), fn, callParams, "") - if retval.Type().TypeKind() == llvm.VoidTypeKind { - builder.CreateRetVoid() - } else { - builder.CreateRet(retval) - } - } - } - - return nil -} diff --git a/transform/wasm-abi_test.go b/transform/wasm-abi_test.go deleted file mode 100644 index 374bba1388..0000000000 --- a/transform/wasm-abi_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package transform_test - -import ( - "testing" - - "github.com/tinygo-org/tinygo/transform" - "tinygo.org/x/go-llvm" -) - -func TestWasmABI(t *testing.T) { - t.Parallel() - testTransform(t, "testdata/wasm-abi", func(mod llvm.Module) { - // Run ABI change pass. - err := transform.ExternalInt64AsPtr(mod, defaultTestConfig) - if err != nil { - t.Errorf("failed to change wasm ABI: %v", err) - } - }) -} From cec237917f2b0f8dc4fb09d83c350ed94ca5a603 Mon Sep 17 00:00:00 2001 From: Yurii Soldak Date: Mon, 12 Jun 2023 15:27:20 +0200 Subject: [PATCH 07/75] example: simplify pininterrupt --- builder/sizes_test.go | 2 +- src/examples/pininterrupt/pininterrupt.go | 11 +---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/builder/sizes_test.go b/builder/sizes_test.go index d640fa21f5..a97fc01c33 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -43,7 +43,7 @@ func TestBinarySize(t *testing.T) { // microcontrollers {"hifive1b", "examples/echo", 4612, 280, 0, 2252}, {"microbit", "examples/serial", 2724, 388, 8, 2256}, - {"wioterminal", "examples/pininterrupt", 6159, 1485, 116, 6816}, + {"wioterminal", "examples/pininterrupt", 6039, 1485, 116, 6816}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the diff --git a/src/examples/pininterrupt/pininterrupt.go b/src/examples/pininterrupt/pininterrupt.go index 0cb29bc854..f693f11721 100644 --- a/src/examples/pininterrupt/pininterrupt.go +++ b/src/examples/pininterrupt/pininterrupt.go @@ -8,7 +8,6 @@ package main import ( "machine" - "runtime/volatile" "time" ) @@ -18,8 +17,6 @@ const ( ) func main() { - var lightLed volatile.Register8 - lightLed.Set(0) // Configure the LED, defaulting to on (usually setting the pin to low will // turn the LED on). @@ -33,13 +30,7 @@ func main() { // Set an interrupt on this pin. err := button.SetInterrupt(buttonPinChange, func(machine.Pin) { - if lightLed.Get() != 0 { - lightLed.Set(0) - led.Low() - } else { - lightLed.Set(1) - led.High() - } + led.Set(!led.Get()) }) if err != nil { println("could not configure pin interrupt:", err.Error()) From 4619896c190c2725ac1d24d9a597d5c776e2dcc8 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 21 Jun 2023 18:48:13 +0200 Subject: [PATCH 08/75] nrf: wait for stop condition after reading from the I2C bus Found while working on the PineTime. For some reason it still kind of works in most cases, but I was hitting this issue when interacting with two different I2C devices (the touch sensor and the BMA421). --- src/machine/machine_nrf5x.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/machine/machine_nrf5x.go b/src/machine/machine_nrf5x.go index f36c7c93c9..8c012b49e8 100644 --- a/src/machine/machine_nrf5x.go +++ b/src/machine/machine_nrf5x.go @@ -70,11 +70,16 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) { i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND_Disabled) } - // Stop explicitly when no reads were executed, stoping unconditionally would be a mistake. - // It may execute after I2C peripheral has already been stopped by the shortcut in the read block, - // so stop task will trigger first thing in a subsequent transaction, hanging it. if len(r) == 0 { + // Stop the I2C transaction after the write. i2c.signalStop() + } else { + // The last byte read has already stopped the transaction, via + // TWI_SHORTS_BB_STOP. But we still need to wait until we receive the + // STOPPED event. + for i2c.Bus.EVENTS_STOPPED.Get() == 0 { + } + i2c.Bus.EVENTS_STOPPED.Set(0) } return From ad32d2651161d9f51b3b0d259f2872e15fc23d07 Mon Sep 17 00:00:00 2001 From: sago35 Date: Sat, 1 Jul 2023 18:58:23 +0900 Subject: [PATCH 09/75] machine/usb/hid,joystick: fix hidreport (3) (#3802) * machine/usb/hid,joystick: fix hidreport (3) and handling of logical, usage, and physical minimum/maximum values --- src/machine/usb/descriptor/hidreport.go | 85 ++++++++++++++++--------- src/machine/usb/descriptor/joystick.go | 5 +- 2 files changed, 56 insertions(+), 34 deletions(-) diff --git a/src/machine/usb/descriptor/hidreport.go b/src/machine/usb/descriptor/hidreport.go index 914279a51f..0d90d47c0a 100644 --- a/src/machine/usb/descriptor/hidreport.go +++ b/src/machine/usb/descriptor/hidreport.go @@ -3,12 +3,12 @@ package descriptor const ( hidUsagePage = 0x05 hidUsage = 0x09 - hidLogicalMinimum = 0x15 - hidLogicalMaximum = 0x25 - hidUsageMinimum = 0x19 - hidUsageMaximum = 0x29 - hidPhysicalMinimum = 0x35 - hidPhysicalMaximum = 0x46 + hidLogicalMinimum = 0x14 + hidLogicalMaximum = 0x24 + hidUsageMinimum = 0x18 + hidUsageMaximum = 0x28 + hidPhysicalMinimum = 0x34 + hidPhysicalMaximum = 0x44 hidUnitExponent = 0x55 hidUnit = 0x65 hidCollection = 0xa1 @@ -18,6 +18,13 @@ const ( hidReportID = 0x85 ) +const ( + hidSizeValue0 = 0x00 + hidSizeValue1 = 0x01 + hidSizeValue2 = 0x02 + hidSizeValue4 = 0x03 +) + var ( HIDUsagePageGenericDesktop = []byte{hidUsagePage, 0x01} HIDUsagePageSimulationControls = []byte{hidUsagePage, 0x02} @@ -129,51 +136,69 @@ func HIDReportID(id int) []byte { } func HIDLogicalMinimum(min int) []byte { - if min > 255 { - return []byte{hidLogicalMinimum + 1, uint8(min), uint8(min >> 8)} + switch { + case min < -32767 || 65535 < min: + return []byte{hidLogicalMinimum + hidSizeValue4, uint8(min), uint8(min >> 8), uint8(min >> 16), uint8(min >> 24)} + case min < -127 || 255 < min: + return []byte{hidLogicalMinimum + hidSizeValue2, uint8(min), uint8(min >> 8)} + default: + return []byte{hidLogicalMinimum + hidSizeValue1, byte(min)} } - - return []byte{hidLogicalMinimum, byte(min)} } func HIDLogicalMaximum(max int) []byte { - if max > 255 { - return []byte{hidLogicalMaximum + 1, uint8(max), uint8(max >> 8)} + switch { + case max < -32767 || 65535 < max: + return []byte{hidLogicalMaximum + hidSizeValue4, uint8(max), uint8(max >> 8), uint8(max >> 16), uint8(max >> 24)} + case max < -127 || 255 < max: + return []byte{hidLogicalMaximum + hidSizeValue2, uint8(max), uint8(max >> 8)} + default: + return []byte{hidLogicalMaximum + hidSizeValue1, byte(max)} } - - return []byte{hidLogicalMaximum, byte(max)} } func HIDUsageMinimum(min int) []byte { - if min > 255 { - return []byte{hidUsageMinimum + 1, uint8(min), uint8(min >> 8)} + switch { + case min < -32767 || 65535 < min: + return []byte{hidUsageMinimum + hidSizeValue4, uint8(min), uint8(min >> 8), uint8(min >> 16), uint8(min >> 24)} + case min < -127 || 255 < min: + return []byte{hidUsageMinimum + hidSizeValue2, uint8(min), uint8(min >> 8)} + default: + return []byte{hidUsageMinimum + hidSizeValue1, byte(min)} } - - return []byte{hidUsageMinimum, byte(min)} } func HIDUsageMaximum(max int) []byte { - if max > 255 { - return []byte{hidUsageMaximum + 1, uint8(max), uint8(max >> 8)} + switch { + case max < -32767 || 65535 < max: + return []byte{hidUsageMaximum + hidSizeValue4, uint8(max), uint8(max >> 8), uint8(max >> 16), uint8(max >> 24)} + case max < -127 || 255 < max: + return []byte{hidUsageMaximum + hidSizeValue2, uint8(max), uint8(max >> 8)} + default: + return []byte{hidUsageMaximum + hidSizeValue1, byte(max)} } - - return []byte{hidUsageMaximum, byte(max)} } func HIDPhysicalMinimum(min int) []byte { - if min > 255 { - return []byte{hidPhysicalMinimum + 1, uint8(min), uint8(min >> 8)} + switch { + case min < -32767 || 65535 < min: + return []byte{hidPhysicalMinimum + hidSizeValue4, uint8(min), uint8(min >> 8), uint8(min >> 16), uint8(min >> 24)} + case min < -127 || 255 < min: + return []byte{hidPhysicalMinimum + hidSizeValue2, uint8(min), uint8(min >> 8)} + default: + return []byte{hidPhysicalMinimum + hidSizeValue1, byte(min)} } - - return []byte{hidPhysicalMinimum, byte(min)} } func HIDPhysicalMaximum(max int) []byte { - if max > 255 { - return []byte{hidPhysicalMaximum + 1, uint8(max), uint8(max >> 8)} + switch { + case max < -32767 || 65535 < max: + return []byte{hidPhysicalMaximum + hidSizeValue4, uint8(max), uint8(max >> 8), uint8(max >> 16), uint8(max >> 24)} + case max < -127 || 255 < max: + return []byte{hidPhysicalMaximum + hidSizeValue2, uint8(max), uint8(max >> 8)} + default: + return []byte{hidPhysicalMaximum + hidSizeValue1, byte(max)} } - - return []byte{hidPhysicalMaximum, byte(max)} } func HIDUnitExponent(exp int) []byte { diff --git a/src/machine/usb/descriptor/joystick.go b/src/machine/usb/descriptor/joystick.go index ebf2f7c314..03bde25904 100644 --- a/src/machine/usb/descriptor/joystick.go +++ b/src/machine/usb/descriptor/joystick.go @@ -80,10 +80,7 @@ var JoystickDefaultHIDReport = Append([][]byte{ HIDLogicalMaximum(1), HIDReportSize(1), HIDReportCount(16), - HIDInputDataVarAbs, - HIDReportCount(1), - HIDReportSize(3), - HIDUnitExponent(-16), + HIDUnitExponent(0), HIDUnit(0), HIDInputDataVarAbs, From dd4e9e86e7e2ed09512b24b99927be1987228b7b Mon Sep 17 00:00:00 2001 From: soypat Date: Fri, 23 Jun 2023 21:25:25 -0300 Subject: [PATCH 10/75] reflect: remove unecessary heap allocations --- src/reflect/type.go | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/reflect/type.go b/src/reflect/type.go index 2201790030..9fbe3b3a82 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -601,6 +601,18 @@ func (t *rawType) Kind() Kind { 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 { @@ -619,14 +631,14 @@ func (t *rawType) elem() *rawType { case Chan, Slice, Array, Map: return (*elemType)(unsafe.Pointer(underlying)).elem default: - panic(&TypeError{"Elem"}) + panic(errTypeElem) } } func (t *rawType) key() *rawType { underlying := t.underlying() if underlying.Kind() != Map { - panic(&TypeError{"Key"}) + panic(errTypeKey) } return (*mapType)(unsafe.Pointer(underlying)).key } @@ -683,7 +695,7 @@ func rawStructFieldFromPointer(descriptor *structType, fieldType *rawType, data // For internal use only. func (t *rawType) rawField(n int) rawStructField { if t.Kind() != Struct { - panic(&TypeError{"Field"}) + panic(errTypeField) } descriptor := (*structType)(unsafe.Pointer(t.underlying())) if uint(n) >= uint(descriptor.numField) { @@ -716,7 +728,7 @@ func (t *rawType) rawField(n int) rawStructField { // For internal use only. func (t *rawType) rawFieldByName(n string) (rawStructField, []int, bool) { if t.Kind() != Struct { - panic(&TypeError{"Field"}) + panic(errTypeField) } type fieldWalker struct { @@ -812,14 +824,14 @@ func (t *rawType) Bits() int { if kind >= Int && kind <= Complex128 { return int(t.Size()) * 8 } - panic(TypeError{"Bits"}) + 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(TypeError{"Len"}) + panic(errTypeLen) } return int((*arrayType)(unsafe.Pointer(t.underlying())).arrayLen) @@ -829,7 +841,7 @@ func (t *rawType) Len() int { // type kinds. func (t *rawType) NumField() int { if t.Kind() != Struct { - panic(&TypeError{"NumField"}) + panic(errTypeNumField) } return int((*structType)(unsafe.Pointer(t.underlying())).numField) } @@ -973,7 +985,7 @@ func (t *rawType) isBinary() bool { func (t *rawType) ChanDir() ChanDir { if t.Kind() != Chan { - panic(TypeError{"ChanDir"}) + panic(errTypeChanDir) } dir := int((*elemType)(unsafe.Pointer(t)).numMethod) @@ -1084,7 +1096,7 @@ func (t *rawType) PkgPath() string { func (t *rawType) FieldByName(name string) (StructField, bool) { if t.Kind() != Struct { - panic(TypeError{"FieldByName"}) + panic(errTypeFieldByName) } field, index, ok := t.rawFieldByName(name) @@ -1110,7 +1122,7 @@ func (t *rawType) FieldByIndex(index []int) StructField { for _, n := range index { structOrPtrToStruct := ftype.Kind() == Struct || (ftype.Kind() == Pointer && ftype.elem().Kind() == Struct) if !structOrPtrToStruct { - panic(&TypeError{"FieldByIndex:" + ftype.Kind().String()}) + panic(errTypeFieldByIndex) } if ftype.Kind() == Pointer { From fdc4bbbfad3cee047c4e7222706fa5c9101d08e3 Mon Sep 17 00:00:00 2001 From: Tyler Rockwood Date: Thu, 22 Jun 2023 15:23:19 -0500 Subject: [PATCH 11/75] reflect: Add FieldByNameFunc This adds FieldByNameFunc, which some libraries like reflect2 need. For my usecase I could also just stub FieldByNameFunc to panic, but figured that it would work OK to just make it work. I'm not sure if the overhead to FieldByName using a closure is acceptable. Signed-off-by: Tyler Rockwood --- src/reflect/type.go | 31 ++++++++++++++++++++++++++----- src/reflect/value.go | 11 +++++++++++ src/reflect/value_test.go | 11 +++++++++++ 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/reflect/type.go b/src/reflect/type.go index 9fbe3b3a82..bd717af0d6 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -360,7 +360,7 @@ type Type interface { // and FieldByNameFunc returns no match. // This behavior mirrors Go's handling of name lookup in // structs containing embedded fields. - //FieldByNameFunc(match func(string) bool) (StructField, bool) + FieldByNameFunc(match func(string) bool) (StructField, bool) // In returns the type of a function type's i'th input parameter. // It panics if the type's Kind is not Func. @@ -722,11 +722,11 @@ func (t *rawType) rawField(n int) rawStructField { return rawStructFieldFromPointer(descriptor, field.fieldType, data, flagsByte, name, offset) } -// rawFieldByName returns nearly the same value as FieldByName but without converting the +// rawFieldByNameFunc returns nearly the same value as FieldByNameFunc but without converting the // Type member to an interface. // // For internal use only. -func (t *rawType) rawFieldByName(n string) (rawStructField, []int, bool) { +func (t *rawType) rawFieldByNameFunc(match func(string) bool) (rawStructField, []int, bool) { if t.Kind() != Struct { panic(errTypeField) } @@ -769,7 +769,7 @@ func (t *rawType) rawFieldByName(n string) (rawStructField, []int, bool) { name := readStringZ(data) data = unsafe.Add(data, len(name)) - if name == n { + if match(name) { found = append(found, result{ rawStructFieldFromPointer(descriptor, field.fieldType, data, flagsByte, name, offset), append(ll.index, int(i)), @@ -1099,7 +1099,28 @@ func (t *rawType) FieldByName(name string) (StructField, bool) { panic(errTypeFieldByName) } - field, index, ok := t.rawFieldByName(name) + 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 } diff --git a/src/reflect/value.go b/src/reflect/value.go index 92d26409dd..a5014329c2 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -1846,6 +1846,17 @@ func (v Value) FieldByName(name string) Value { 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.hashmapMakeUnsafePointer func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) unsafe.Pointer diff --git a/src/reflect/value_test.go b/src/reflect/value_test.go index b6c1770501..ef6eabcf2f 100644 --- a/src/reflect/value_test.go +++ b/src/reflect/value_test.go @@ -4,6 +4,7 @@ import ( "encoding/base64" . "reflect" "sort" + "strings" "testing" ) @@ -452,10 +453,20 @@ func TestTinyStruct(t *testing.T) { t.Errorf("StrucTag for Foo=%v, want %v", got, want) } + q, ok = reffb.FieldByNameFunc(func(s string) bool { return strings.ToLower(s) == "bar" }) + if q.Name != "Bar" || !ok { + t.Errorf("FieldByNameFunc(bar)=%v,%v, want Bar, true", q.Name, ok) + } + q, ok = reffb.FieldByName("Snorble") if q.Name != "" || ok { t.Errorf("FieldByName(Snorble)=%v,%v, want ``, false", q.Name, ok) } + + q, ok = reffb.FieldByNameFunc(func(s string) bool { return strings.ToLower(s) == "snorble" }) + if q.Name != "" || ok { + t.Errorf("FieldByName(snorble)=%v,%v, want ``, false", q.Name, ok) + } } func TestTinyZero(t *testing.T) { From 93cc03b15a6c5e4e9f21d5390084b3012acc54f5 Mon Sep 17 00:00:00 2001 From: Rado M Date: Fri, 23 Jun 2023 17:55:00 +0300 Subject: [PATCH 12/75] docker: update clang to version 15 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e4c2e67f92..2a61a221d9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM golang:1.20 AS tinygo-llvm RUN apt-get update && \ - apt-get install -y apt-utils make cmake clang-11 ninja-build + apt-get install -y apt-utils make cmake clang-15 ninja-build COPY ./Makefile /tinygo/Makefile From ef72c5bb4e544843badec2e2c63ed10a09c281e6 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Fri, 16 Jun 2023 12:19:53 -0700 Subject: [PATCH 13/75] reflect: fix iterating over maps with interface{} keys Fixes #3794 --- src/reflect/value.go | 21 ++++++++++++--------- src/reflect/value_test.go | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/reflect/value.go b/src/reflect/value.go index a5014329c2..5ee7e5fc13 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -914,10 +914,11 @@ func (v Value) MapKeys() []Value { e := New(v.typecode.Elem()) keyType := v.typecode.key() - isKeyStoredAsInterface := keyType.Kind() != String && !keyType.isBinary() + keyTypeIsEmptyInterface := keyType.Kind() == Interface && keyType.NumMethod() == 0 + shouldUnpackInterface := !keyTypeIsEmptyInterface && keyType.Kind() != String && !keyType.isBinary() for hashmapNext(v.pointer(), it, k.value, e.value) { - if isKeyStoredAsInterface { + if shouldUnpackInterface { intf := *(*interface{})(k.value) v := ValueOf(intf) keys = append(keys, v) @@ -992,12 +993,14 @@ func (v Value) MapRange() *MapIter { } keyType := v.typecode.key() - isKeyStoredAsInterface := keyType.Kind() != String && !keyType.isBinary() + + keyTypeIsEmptyInterface := keyType.Kind() == Interface && keyType.NumMethod() == 0 + shouldUnpackInterface := !keyTypeIsEmptyInterface && keyType.Kind() != String && !keyType.isBinary() return &MapIter{ - m: v, - it: hashmapNewIterator(), - keyInterface: isKeyStoredAsInterface, + m: v, + it: hashmapNewIterator(), + unpackKeyInterface: shouldUnpackInterface, } } @@ -1007,8 +1010,8 @@ type MapIter struct { key Value val Value - valid bool - keyInterface bool + valid bool + unpackKeyInterface bool } func (it *MapIter) Key() Value { @@ -1016,7 +1019,7 @@ func (it *MapIter) Key() Value { panic("reflect.MapIter.Key called on invalid iterator") } - if it.keyInterface { + if it.unpackKeyInterface { intf := *(*interface{})(it.key.value) v := ValueOf(intf) return v diff --git a/src/reflect/value_test.go b/src/reflect/value_test.go index ef6eabcf2f..305cf02e8d 100644 --- a/src/reflect/value_test.go +++ b/src/reflect/value_test.go @@ -191,6 +191,24 @@ func TestTinyMap(t *testing.T) { if _, ok := utIterKey.Interface().(unmarshalerText); !ok { t.Errorf("Map keys via MapIter() have wrong type: %v", utIterKey.Type().String()) } + + { + m := map[any]any{1: 2} + rv := ValueOf(m) + iter := rv.MapRange() + + iter.Next() + k := iter.Key() + if k.Kind().String() != "interface" { + t.Errorf("map[any]any MapRange has wrong key type: %v", k.Kind().String()) + } + + keys := rv.MapKeys() + if k := keys[0]; k.Kind().String() != "interface" { + t.Errorf("map[any]any MapRange has wrong key type: %v", k.Kind().String()) + } + + } } // For an interface type, it returns the number of exported and unexported methods. From acba0748f1daad128166e09e82badb9f1f0b2a29 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Sat, 17 Jun 2023 01:21:43 -0700 Subject: [PATCH 14/75] compiler,reflect: NumMethods reports exported methods only Fixes #3796 --- compiler/interface.go | 25 +++++++++++++++++-------- src/reflect/value_test.go | 10 +++++----- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/compiler/interface.go b/compiler/interface.go index da99821489..81234b0c9a 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -128,6 +128,15 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { hasMethodSet = false } + var numMethods int + if hasMethodSet { + for i := 0; i < ms.Len(); i++ { + if ms.At(i).Obj().Exported() { + numMethods++ + } + } + } + // Short-circuit all the global pointer logic here for pointers to pointers. if typ, ok := typ.(*types.Pointer); ok { if _, ok := typ.Elem().(*types.Pointer); ok { @@ -277,11 +286,11 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { } pkgPathPtr := c.pkgPathPtr(pkgpath) typeFields = []llvm.Value{ - llvm.ConstInt(c.ctx.Int16Type(), uint64(ms.Len()), false), // numMethods - c.getTypeCode(types.NewPointer(typ)), // ptrTo - c.getTypeCode(typ.Underlying()), // underlying - pkgPathPtr, // pkgpath pointer - c.ctx.ConstString(pkgname+"."+name+"\x00", false), // name + llvm.ConstInt(c.ctx.Int16Type(), uint64(numMethods), false), // numMethods + c.getTypeCode(types.NewPointer(typ)), // ptrTo + c.getTypeCode(typ.Underlying()), // underlying + pkgPathPtr, // pkgpath pointer + c.ctx.ConstString(pkgname+"."+name+"\x00", false), // name } metabyte |= 1 << 5 // "named" flag case *types.Chan: @@ -308,7 +317,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { } case *types.Pointer: typeFields = []llvm.Value{ - llvm.ConstInt(c.ctx.Int16Type(), uint64(ms.Len()), false), // numMethods + llvm.ConstInt(c.ctx.Int16Type(), uint64(numMethods), false), // numMethods c.getTypeCode(typ.Elem()), } case *types.Array: @@ -337,8 +346,8 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { llvmStructType := c.getLLVMType(typ) size := c.targetData.TypeStoreSize(llvmStructType) typeFields = []llvm.Value{ - llvm.ConstInt(c.ctx.Int16Type(), uint64(ms.Len()), false), // numMethods - c.getTypeCode(types.NewPointer(typ)), // ptrTo + llvm.ConstInt(c.ctx.Int16Type(), uint64(numMethods), false), // numMethods + c.getTypeCode(types.NewPointer(typ)), // ptrTo pkgPathPtr, llvm.ConstInt(c.ctx.Int32Type(), uint64(size), false), // size llvm.ConstInt(c.ctx.Int16Type(), uint64(typ.NumFields()), false), // numFields diff --git a/src/reflect/value_test.go b/src/reflect/value_test.go index 305cf02e8d..9be9789e15 100644 --- a/src/reflect/value_test.go +++ b/src/reflect/value_test.go @@ -560,7 +560,7 @@ type methodStruct struct { i int } -func (m methodStruct) valueMethod1() int { +func (m methodStruct) ValueMethod1() int { return m.i } @@ -568,11 +568,11 @@ func (m methodStruct) valueMethod2() int { return m.i } -func (m *methodStruct) pointerMethod1() int { +func (m *methodStruct) PointerMethod1() int { return m.i } -func (m *methodStruct) pointerMethod2() int { +func (m *methodStruct) PointerMethod2() int { return m.i } @@ -582,12 +582,12 @@ func (m *methodStruct) pointerMethod3() int { func TestTinyNumMethods(t *testing.T) { refptrt := TypeOf(&methodStruct{}) - if got, want := refptrt.NumMethod(), 2+3; got != want { + if got, want := refptrt.NumMethod(), 1+2; got != want { t.Errorf("Pointer Methods=%v, want %v", got, want) } reft := refptrt.Elem() - if got, want := reft.NumMethod(), 2; got != want { + if got, want := reft.NumMethod(), 1; got != want { t.Errorf("Value Methods=%v, want %v", got, want) } } From 08b3a4576d3f235a896077cf748cd224efcab6c3 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Sat, 17 Jun 2023 07:42:44 -0700 Subject: [PATCH 15/75] compiler: update .ll test output --- compiler/testdata/interface.ll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/testdata/interface.ll b/compiler/testdata/interface.ll index edf6118c78..8150dc2eec 100644 --- a/compiler/testdata/interface.ll +++ b/compiler/testdata/interface.ll @@ -9,7 +9,7 @@ target triple = "wasm32-unknown-wasi" @"reflect/types.type:basic:int" = linkonce_odr constant { i8, ptr } { i8 -62, ptr @"reflect/types.type:pointer:basic:int" }, align 4 @"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:int" }, align 4 @"reflect/types.type:pointer:named:error" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:named:error" }, align 4 -@"reflect/types.type:named:error" = linkonce_odr constant { i8, i16, ptr, ptr, ptr, [7 x i8] } { i8 116, i16 1, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}", ptr @"reflect/types.type.pkgpath.empty", [7 x i8] c".error\00" }, align 4 +@"reflect/types.type:named:error" = linkonce_odr constant { i8, i16, ptr, ptr, ptr, [7 x i8] } { i8 116, i16 0, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}", ptr @"reflect/types.type.pkgpath.empty", [7 x i8] c".error\00" }, align 4 @"reflect/types.type.pkgpath.empty" = linkonce_odr unnamed_addr constant [1 x i8] zeroinitializer, align 1 @"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr } { i8 84, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }, align 4 @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4 From dc91c96305eaef38e55ff6715597de2f62565a78 Mon Sep 17 00:00:00 2001 From: Yurii Soldak Date: Sat, 21 Jan 2023 12:02:56 +0100 Subject: [PATCH 16/75] example: adjust time offset --- Makefile | 2 ++ src/examples/time-offset/time-offset.go | 32 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 src/examples/time-offset/time-offset.go diff --git a/Makefile b/Makefile index 0ccd6baeaf..792cc53ce6 100644 --- a/Makefile +++ b/Makefile @@ -478,6 +478,8 @@ smoketest: @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/test @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=pca10040 examples/time-offset + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=wioterminal examples/hid-mouse @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=wioterminal examples/hid-keyboard diff --git a/src/examples/time-offset/time-offset.go b/src/examples/time-offset/time-offset.go new file mode 100644 index 0000000000..ef84997f7c --- /dev/null +++ b/src/examples/time-offset/time-offset.go @@ -0,0 +1,32 @@ +package main + +// This example demonstrates how to set the system time. +// +// Usually, boards don't keep time on power cycles and restarts, it resets to Unix epoch. +// The system time can be set by calling runtime.AdjustTimeOffset(). +// +// Possible time sources: an external RTC clock or network (WiFi access point or NTP server) + +import ( + "fmt" + "runtime" + "time" +) + +const myTime = "2006-01-02T15:04:05Z" // this is an example time you source somewhere + +func main() { + + // measure how many nanoseconds the internal clock is behind + timeOfMeasurement := time.Now() + actualTime, _ := time.Parse(time.RFC3339, myTime) + offset := actualTime.Sub(timeOfMeasurement) + + // adjust internal clock by adding the offset to the internal clock + runtime.AdjustTimeOffset(int64(offset)) + + for { + fmt.Printf("%v\r\n", time.Now().Format(time.RFC3339)) + time.Sleep(5 * time.Second) + } +} From db8d80755f0f37ccba73b507a84918a88f2934a6 Mon Sep 17 00:00:00 2001 From: Mansour Behabadi Date: Mon, 3 Jul 2023 15:18:48 +1000 Subject: [PATCH 17/75] rp2040: add missing suffix to CMD_READ_STATUS --- targets/rp2040-boot-stage2.S | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/targets/rp2040-boot-stage2.S b/targets/rp2040-boot-stage2.S index 9cc2ce09b9..e5135c9d4e 100644 --- a/targets/rp2040-boot-stage2.S +++ b/targets/rp2040-boot-stage2.S @@ -63,7 +63,7 @@ // Expanded include files // #define CMD_WRITE_ENABLE 0x06 -#define CMD_READ_STATUS 0x05 +#define CMD_READ_STATUS1 0x05 #define CMD_READ_STATUS2 0x35 #define CMD_WRITE_STATUS1 0x01 #define CMD_WRITE_STATUS2 0x31 @@ -301,7 +301,7 @@ program_sregs: # endif // Poll status register for write completion 1: - movs r0, #CMD_READ_STATUS + movs r0, #CMD_READ_STATUS1 bl read_flash_sreg movs r1, #1 tst r0, r1 From 6efa94035eda36f121dd6f22bff19daf346d3181 Mon Sep 17 00:00:00 2001 From: sago35 Date: Mon, 3 Jul 2023 08:43:00 +0900 Subject: [PATCH 18/75] machine/macropad_rp2040: add machine.BUTTON --- src/machine/board_macropad-rp2040.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/machine/board_macropad-rp2040.go b/src/machine/board_macropad-rp2040.go index 8b8f4a7c60..eca20f7970 100644 --- a/src/machine/board_macropad-rp2040.go +++ b/src/machine/board_macropad-rp2040.go @@ -16,6 +16,7 @@ const ( const ( SWITCH = GPIO0 + BUTTON = GPIO0 KEY1 = GPIO1 KEY2 = GPIO2 From 9aadea930fd760292a20ed5c91c89c26e6822b9a Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 30 Jun 2023 13:54:17 +0200 Subject: [PATCH 19/75] main: improve detection of filesystems This is a rewrite of how filesystems are detected. Specifically, it fixes an issue on Linux where the location of the FAT filesystem can vary between distributions (for example, we supported most distros by checking two different paths, but NixOS uses a different path): it now uses the data in /proc/mounts instead which should be universal. --- main.go | 193 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 106 insertions(+), 87 deletions(-) diff --git a/main.go b/main.go index 64bb2f6b28..a6a16f92d0 100644 --- a/main.go +++ b/main.go @@ -19,6 +19,7 @@ import ( "regexp" "runtime" "runtime/pprof" + "sort" "strconv" "strings" "sync" @@ -946,112 +947,130 @@ func touchSerialPortAt1200bps(port string) (err error) { } func flashUF2UsingMSD(volumes []string, tmppath string, options *compileopts.Options) error { - // find standard UF2 info path - infoPaths := make([]string, 0, len(volumes)) - for _, volume := range volumes { - switch runtime.GOOS { - case "linux", "freebsd": - fi, err := os.Stat("/run/media") - if err != nil || !fi.IsDir() { - infoPaths = append(infoPaths, "/media/*/"+volume+"/INFO_UF2.TXT") - } else { - infoPaths = append(infoPaths, "/run/media/*/"+volume+"/INFO_UF2.TXT") - } - case "darwin": - infoPaths = append(infoPaths, "/Volumes/"+volume+"/INFO_UF2.TXT") - case "windows": - path, err := windowsFindUSBDrive(volume, options) - if err == nil { - infoPaths = append(infoPaths, path+"/INFO_UF2.TXT") + for start := time.Now(); time.Since(start) < options.Timeout; { + // Find a UF2 mount point. + mounts, err := findFATMounts(options) + if err != nil { + return err + } + for _, mount := range mounts { + for _, volume := range volumes { + if mount.name != volume { + continue + } + if _, err := os.Stat(filepath.Join(mount.path, "INFO_UF2.TXT")); err != nil { + // No INFO_UF2.TXT found, which is expected on a UF2 + // filesystem. + continue + } + // Found the filesystem, so flash the device! + return moveFile(tmppath, filepath.Join(mount.path, "flash.uf2")) } } + time.Sleep(500 * time.Millisecond) } - - d, err := locateDevice(volumes, infoPaths, options.Timeout) - if err != nil { - return err - } - - return moveFile(tmppath, filepath.Dir(d)+"/flash.uf2") + return errors.New("unable to locate any volume: [" + strings.Join(volumes, ",") + "]") } func flashHexUsingMSD(volumes []string, tmppath string, options *compileopts.Options) error { - // find expected volume path - destPaths := make([]string, 0, len(volumes)) - for _, volume := range volumes { - switch runtime.GOOS { - case "linux", "freebsd": - fi, err := os.Stat("/run/media") - if err != nil || !fi.IsDir() { - destPaths = append(destPaths, "/media/*/"+volume) - } else { - destPaths = append(destPaths, "/run/media/*/"+volume) - } - case "darwin": - destPaths = append(destPaths, "/Volumes/"+volume) - case "windows": - path, err := windowsFindUSBDrive(volume, options) - if err == nil { - destPaths = append(destPaths, path+"/") + for start := time.Now(); time.Since(start) < options.Timeout; { + // Find all mount points. + mounts, err := findFATMounts(options) + if err != nil { + return err + } + for _, mount := range mounts { + for _, volume := range volumes { + if mount.name != volume { + continue + } + // Found the filesystem, so flash the device! + return moveFile(tmppath, filepath.Join(mount.path, "flash.hex")) } } + time.Sleep(500 * time.Millisecond) } + return errors.New("unable to locate any volume: [" + strings.Join(volumes, ",") + "]") +} - d, err := locateDevice(volumes, destPaths, options.Timeout) - if err != nil { - return err - } - - return moveFile(tmppath, d+"/flash.hex") +type mountPoint struct { + name string + path string } -func locateDevice(volumes, paths []string, timeout time.Duration) (string, error) { - var d []string - var err error - for start := time.Now(); time.Since(start) < timeout; { - for _, path := range paths { - d, err = filepath.Glob(path) - if err != nil { - return "", err +// Find all the mount points on the system that use the FAT filesystem. +func findFATMounts(options *compileopts.Options) ([]mountPoint, error) { + var points []mountPoint + switch runtime.GOOS { + case "darwin": + list, err := os.ReadDir("/Volumes") + if err != nil { + return nil, fmt.Errorf("could not list mount points: %w", err) + } + for _, elem := range list { + // TODO: find a way to check for the filesystem type. + // (Only return FAT filesystems). + points = append(points, mountPoint{ + name: elem.Name(), + path: filepath.Join("/Volumes", elem.Name()), + }) + } + sort.Slice(points, func(i, j int) bool { + return points[i].path < points[j].name + }) + return points, nil + case "linux": + tab, err := os.ReadFile("/proc/mounts") // symlink to /proc/self/mounts on my system + if err != nil { + return nil, fmt.Errorf("could not list mount points: %w", err) + } + for _, line := range strings.Split(string(tab), "\n") { + fields := strings.Fields(line) + if len(fields) <= 2 { + continue } - if d != nil { - break + fstype := fields[2] + if fstype != "vfat" { + continue } + points = append(points, mountPoint{ + name: filepath.Base(fields[1]), + path: fields[1], + }) + } + 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") + var out bytes.Buffer + cmd.Stdout = &out + err := cmd.Run() + if err != nil { + return nil, fmt.Errorf("could not list mount points: %w", err) } - if d != nil { - break - } - - time.Sleep(500 * time.Millisecond) - } - if d == nil { - return "", errors.New("unable to locate any volume: [" + strings.Join(volumes, ",") + "]") - } - return d[0], nil -} - -func windowsFindUSBDrive(volume string, options *compileopts.Options) (string, error) { - cmd := executeCommand(options, "wmic", - "PATH", "Win32_LogicalDisk", "WHERE", "VolumeName = '"+volume+"'", - "get", "DeviceID,VolumeName,FileSystem,DriveType") - - var out bytes.Buffer - cmd.Stdout = &out - err := cmd.Run() - if err != nil { - return "", err - } - - for _, line := range strings.Split(out.String(), "\n") { - words := strings.Fields(line) - if len(words) >= 3 { - if words[1] == "2" && words[2] == "FAT" { - return words[0], nil + // Extract data to convert to a []mountPoint slice. + for _, line := range strings.Split(out.String(), "\n") { + words := strings.Fields(line) + if len(words) < 3 { + continue } + if words[1] != "2" || words[2] != "FAT" { + // - DriveType 2 is removable (which we're looking for). + // - We only want to return FAT filesystems. + continue + } + points = append(points, mountPoint{ + name: words[3], + path: words[0], + }) } + return points, nil + default: + return nil, fmt.Errorf("unknown GOOS for listing mount points: %s", runtime.GOOS) } - return "", errors.New("unable to locate a USB device to be flashed") } // getDefaultPort returns the default serial port depending on the operating system. From 04601a29e8ed5070d25f7d9d1c1df98e8d1c1b03 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sun, 14 May 2023 18:47:48 +0200 Subject: [PATCH 20/75] modules: add submodule for Renesas SVD file mirror repo Signed-off-by: deadprogram --- .gitmodules | 3 +++ lib/renesas-svd | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/renesas-svd diff --git a/.gitmodules b/.gitmodules index 35e6870c08..bcb76966be 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,3 +31,6 @@ [submodule "lib/macos-minimal-sdk"] path = lib/macos-minimal-sdk url = https://github.com/aykevl/macos-minimal-sdk.git +[submodule "lib/renesas-svd"] + path = lib/renesas-svd + url = git@github.com:tinygo-org/renesas-svd.git diff --git a/lib/renesas-svd b/lib/renesas-svd new file mode 160000 index 0000000000..03d7688085 --- /dev/null +++ b/lib/renesas-svd @@ -0,0 +1 @@ +Subproject commit 03d76880854b9042f5d043f4355cdf8eef522fa5 From 7755f2385cefea465b01dc1dfabc3f539f0135d4 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sun, 14 May 2023 18:48:23 +0200 Subject: [PATCH 21/75] tools/gen-device-svd: small changes needed for Renesas MCUs Signed-off-by: deadprogram --- tools/gen-device-svd/gen-device-svd.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index 8eb308c7f5..0c49986ab9 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -18,7 +18,7 @@ import ( ) var validName = regexp.MustCompile("^[a-zA-Z0-9_]+$") -var enumBitSpecifier = regexp.MustCompile("^#[x01]+$") +var enumBitSpecifier = regexp.MustCompile("^#x*[01]+[01x]*$") type SVDFile struct { XMLName xml.Name `xml:"device"` @@ -628,6 +628,11 @@ func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPre } for _, enumEl := range enumeratedValues.EnumeratedValue { enumName := enumEl.Name + // Renesas has enum without actual values that we have to skip + if enumEl.Value == "" { + continue + } + if strings.EqualFold(enumName, "reserved") || !validName.MatchString(enumName) { continue } @@ -645,7 +650,7 @@ func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPre } if err != nil { if enumBitSpecifier.MatchString(enumEl.Value) { - // NXP SVDs use the form #xx1x, #x0xx, etc for values + // NXP and Renesas SVDs use the form #xx1x, #x0xx, etc for values enumValue, err = strconv.ParseUint(strings.ReplaceAll(enumEl.Value[1:], "x", "0"), 2, 64) if err != nil { panic(err) @@ -760,6 +765,14 @@ func (r *Register) dimIndex() []string { t := strings.Split(*r.element.DimIndex, "-") if len(t) == 2 { + // renesas uses hex letters e.g. A-B + if strings.Contains("ABCDEFabcdef", t[0]) { + t[0] = "0x" + t[0] + } + if strings.Contains("ABCDEFabcdef", t[1]) { + t[1] = "0x" + t[1] + } + x, err := strconv.ParseInt(t[0], 0, 32) if err != nil { panic(err) From 3871b831af8ccbfc0088228ccbcdb7b898303b59 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sun, 14 May 2023 18:48:55 +0200 Subject: [PATCH 22/75] make: add make task to generate Renesas device wrappers Signed-off-by: deadprogram --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 792cc53ce6..d1e37dd8c2 100644 --- a/Makefile +++ b/Makefile @@ -232,6 +232,10 @@ gen-device-rp: build/gen-device-svd ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/RaspberryPi lib/cmsis-svd/data/RaspberryPi/ src/device/rp/ GO111MODULE=off $(GO) fmt ./src/device/rp +gen-device-renesas: build/gen-device-svd + ./build/gen-device-svd -source=https://github.com/tinygo-org/renesas-svd lib/renesas-svd/ src/device/renesas/ + GO111MODULE=off $(GO) fmt ./src/device/renesas + # Get LLVM sources. $(LLVM_PROJECTDIR)/llvm: git clone -b xtensa_release_15.x --depth=1 https://github.com/espressif/llvm-project $(LLVM_PROJECTDIR) From 9126b9573711b1990f80ca1bca564c4886b8f8f2 Mon Sep 17 00:00:00 2001 From: sago35 Date: Thu, 6 Jul 2023 21:54:32 +0900 Subject: [PATCH 23/75] machine/samd51: fix i2cTimeout was decreasing due to cache activation --- src/machine/machine_atsamd51.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go index 24ae89aab8..de5491c884 100644 --- a/src/machine/machine_atsamd51.go +++ b/src/machine/machine_atsamd51.go @@ -1162,7 +1162,7 @@ const ( wireCmdStop = 3 ) -const i2cTimeout = 1000 +const i2cTimeout = 28000 // about 210us // Configure is intended to setup the I2C interface. func (i2c *I2C) Configure(config I2CConfig) error { From e98dfd6c4653a2fb1e3a64c0711808b06c5ae839 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 6 Jul 2023 13:00:18 +0200 Subject: [PATCH 24/75] reflect: implement Value.Grow This was added in Go 1.20 and becomes necessary for encoding/json in Go 1.21. --- src/reflect/all_test.go | 4 ++-- src/reflect/value.go | 32 ++++++++++++++++++++++++++------ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index 4f9f223f17..cb7e4d2ac0 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -790,6 +790,8 @@ func TestFunctionValue(t *testing.T) { assert(t, v.Type().String(), "func()") } +*/ + func TestGrow(t *testing.T) { v := ValueOf([]int(nil)) shouldPanic("reflect.Value.Grow using unaddressable value", func() { v.Grow(0) }) @@ -857,8 +859,6 @@ func TestGrow(t *testing.T) { }) } -*/ - var appendTests = []struct { orig, extra []int }{ diff --git a/src/reflect/value.go b/src/reflect/value.go index 5ee7e5fc13..60df826864 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -1641,7 +1641,7 @@ func buflen(v Value) (unsafe.Pointer, uintptr) { func sliceGrow(buf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) // extend slice to hold n new elements -func (v *Value) extendSlice(n int) { +func extendSlice(v Value, n int) sliceHeader { if v.Kind() != Slice { panic(&ValueError{Method: "extendSlice", Kind: v.Kind()}) } @@ -1664,14 +1664,11 @@ func (v *Value) extendSlice(n int) { ncap = old.cap } - newslice := sliceHeader{ + return sliceHeader{ data: nbuf, len: nlen + uintptr(n), cap: ncap, } - - v.flags = valueFlagExported - v.value = (unsafe.Pointer)(&newslice) } // Append appends the values x to a slice s and returns the resulting slice. @@ -1681,7 +1678,9 @@ func Append(v Value, x ...Value) Value { panic(&ValueError{Method: "Append", Kind: v.Kind()}) } oldLen := v.Len() - v.extendSlice(len(x)) + newslice := extendSlice(v, len(x)) + v.flags = valueFlagExported + v.value = (unsafe.Pointer)(&newslice) for i, xx := range x { v.Index(oldLen + i).Set(xx) } @@ -1716,6 +1715,27 @@ func AppendSlice(s, t Value) 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.hashmapStringSetUnsafePointer func hashmapStringSet(m unsafe.Pointer, key string, value unsafe.Pointer) From 46d2696363271dc3ca11d0672b255b25d72afbc2 Mon Sep 17 00:00:00 2001 From: Achille Roussel Date: Thu, 6 Jul 2023 11:18:04 -0700 Subject: [PATCH 25/75] wasi: allow zero inodes when reading directories Signed-off-by: Achille Roussel --- src/os/dir_wasi.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/os/dir_wasi.go b/src/os/dir_wasi.go index 088dcc7ff0..60a941f24f 100644 --- a/src/os/dir_wasi.go +++ b/src/os/dir_wasi.go @@ -53,9 +53,6 @@ func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEn if dirent == nil { // EOF break } - if dirent.Ino == 0 { - continue - } name := dirent.Name() // Check for useless names before allocating a string. if string(name) == "." || string(name) == ".." { From e075e0591d555d3e657858f5186627f412dd500f Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 23 May 2023 15:18:34 +0200 Subject: [PATCH 26/75] main: use `go env` instead of doing all detection manually This replaces our own manual detection of various variables (GOROOT, GOPATH, Go version) with a simple call to `go env`. If the `go` command is not found: error: could not find 'go' command: executable file not found in $PATH If the Go version is too old: error: requires go version 1.18 through 1.20, got go1.17 If the Go tool itself outputs an error (using GOROOT=foobar here): go: cannot find GOROOT directory: foobar This does break the case where `go` wasn't available in $PATH but we would detect it anyway (via some hardcoded OS-dependent paths). I'm not sure we want to fix that: I think it's better to tell users "make sure `go version` prints the right value" than to do some automagic detection of Go binary locations. --- builder/config.go | 10 +-- compiler/compiler_test.go | 2 +- goenv/goenv.go | 152 +++++++++++++------------------------- goenv/version.go | 32 ++------ main.go | 2 +- 5 files changed, 64 insertions(+), 134 deletions(-) diff --git a/builder/config.go b/builder/config.go index f9fe715b76..1ca3aa6414 100644 --- a/builder/config.go +++ b/builder/config.go @@ -1,7 +1,6 @@ package builder import ( - "errors" "fmt" "github.com/tinygo-org/tinygo/compileopts" @@ -24,14 +23,9 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) { spec.OpenOCDCommands = options.OpenOCDCommands } - goroot := goenv.Get("GOROOT") - if goroot == "" { - return nil, errors.New("cannot locate $GOROOT, please set it manually") - } - - major, minor, err := goenv.GetGorootVersion(goroot) + major, minor, err := goenv.GetGorootVersion() if err != nil { - return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err) + return nil, err } if major != 1 || minor < 18 || minor > 20 { // Note: when this gets updated, also update the Go compatibility matrix: diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 6b5fbe13cb..9675c40280 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -29,7 +29,7 @@ func TestCompiler(t *testing.T) { t.Parallel() // Determine Go minor version (e.g. 16 in go1.16.3). - _, goMinor, err := goenv.GetGorootVersion(goenv.Get("GOROOT")) + _, goMinor, err := goenv.GetGorootVersion() if err != nil { t.Fatal("could not read Go version:", err) } diff --git a/goenv/goenv.go b/goenv/goenv.go index d87f6f2e94..f6a32502a6 100644 --- a/goenv/goenv.go +++ b/goenv/goenv.go @@ -4,15 +4,16 @@ package goenv import ( "bytes" + "encoding/json" "errors" "fmt" "io/fs" "os" "os/exec" - "os/user" "path/filepath" "runtime" "strings" + "sync" ) // Keys is a slice of all available environment variable keys. @@ -37,6 +38,53 @@ func init() { // directory. var TINYGOROOT string +// Variables read from a `go env` command invocation. +var goEnvVars struct { + GOPATH string + GOROOT string + GOVERSION string +} + +var goEnvVarsOnce sync.Once +var goEnvVarsErr error // error returned from cmd.Run + +// Make sure goEnvVars is fresh. This can be called multiple times, the first +// time will update all environment variables in goEnvVars. +func readGoEnvVars() error { + goEnvVarsOnce.Do(func() { + cmd := exec.Command("go", "env", "-json", "GOPATH", "GOROOT", "GOVERSION") + output, err := cmd.Output() + if err != nil { + // Check for "command not found" error. + if execErr, ok := err.(*exec.Error); ok { + goEnvVarsErr = fmt.Errorf("could not find '%s' command: %w", execErr.Name, execErr.Err) + return + } + // It's perhaps a bit ugly to handle this error here, but I couldn't + // think of a better place further up in the call chain. + if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() != 0 { + if len(exitErr.Stderr) != 0 { + // The 'go' command exited with an error message. Print that + // message and exit, so we behave in a similar way. + os.Stderr.Write(exitErr.Stderr) + os.Exit(exitErr.ExitCode()) + } + } + // Other errors. Not sure whether there are any, but just in case. + goEnvVarsErr = err + return + } + err = json.Unmarshal(output, &goEnvVars) + if err != nil { + // This should never happen if we have a sane Go toolchain + // installed. + goEnvVarsErr = fmt.Errorf("unexpected error while unmarshalling `go env` output: %w", err) + } + }) + + return goEnvVarsErr +} + // Get returns a single environment variable, possibly calculating it on-demand. // The empty string is returned for unknown environment variables. func Get(name string) string { @@ -70,15 +118,11 @@ func Get(name string) string { // especially when floating point instructions are involved. return "6" case "GOROOT": - return getGoroot() + readGoEnvVars() + return goEnvVars.GOROOT case "GOPATH": - if dir := os.Getenv("GOPATH"); dir != "" { - return dir - } - - // fallback - home := getHomeDir() - return filepath.Join(home, "go") + readGoEnvVars() + return goEnvVars.GOPATH case "GOCACHE": // Get the cache directory, usually ~/.cache/tinygo dir, err := os.UserCacheDir() @@ -240,93 +284,3 @@ func isSourceDir(root string) bool { _, err = os.Stat(filepath.Join(root, "src/device/arm/arm.go")) return err == nil } - -func getHomeDir() string { - u, err := user.Current() - if err != nil { - panic("cannot get current user: " + err.Error()) - } - if u.HomeDir == "" { - // This is very unlikely, so panic here. - // Not the nicest solution, however. - panic("could not find home directory") - } - return u.HomeDir -} - -// getGoroot returns an appropriate GOROOT from various sources. If it can't be -// found, it returns an empty string. -func getGoroot() string { - // An explicitly set GOROOT always has preference. - goroot := os.Getenv("GOROOT") - if goroot != "" { - // Convert to the standard GOROOT being referenced, if it's a TinyGo cache. - return getStandardGoroot(goroot) - } - - // Check for the location of the 'go' binary and base GOROOT on that. - binpath, err := exec.LookPath("go") - if err == nil { - binpath, err = filepath.EvalSymlinks(binpath) - if err == nil { - goroot := filepath.Dir(filepath.Dir(binpath)) - if isGoroot(goroot) { - return goroot - } - } - } - - // Check what GOROOT was at compile time. - if isGoroot(runtime.GOROOT()) { - return runtime.GOROOT() - } - - // Check for some standard locations, as a last resort. - var candidates []string - switch runtime.GOOS { - case "linux": - candidates = []string{ - "/usr/local/go", // manually installed - "/usr/lib/go", // from the distribution - "/snap/go/current/", // installed using snap - } - case "darwin": - candidates = []string{ - "/usr/local/go", // manually installed - "/usr/local/opt/go/libexec", // from Homebrew - } - } - - for _, candidate := range candidates { - if isGoroot(candidate) { - return candidate - } - } - - // Can't find GOROOT... - return "" -} - -// isGoroot checks whether the given path looks like a GOROOT. -func isGoroot(goroot string) bool { - _, err := os.Stat(filepath.Join(goroot, "src", "runtime", "internal", "sys", "zversion.go")) - return err == nil -} - -// getStandardGoroot returns the physical path to a real, standard Go GOROOT -// implied by the given path. -// If the given path appears to be a TinyGo cached GOROOT, it returns the path -// referenced by symlinks contained in the cache. Otherwise, it returns the -// given path as-is. -func getStandardGoroot(path string) string { - // Check if the "bin" subdirectory of our given GOROOT is a symlink, and then - // return the _parent_ directory of its destination. - if dest, err := os.Readlink(filepath.Join(path, "bin")); nil == err { - // Clean the destination to remove any trailing slashes, so that - // filepath.Dir will always return the parent. - // (because both "/foo" and "/foo/" are valid symlink destinations, - // but filepath.Dir would return "/" and "/foo", respectively) - return filepath.Dir(filepath.Clean(dest)) - } - return path -} diff --git a/goenv/version.go b/goenv/version.go index 4be6bcbc0c..d234fb3ded 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -4,9 +4,6 @@ import ( "errors" "fmt" "io" - "os" - "path/filepath" - "regexp" "strings" ) @@ -22,8 +19,8 @@ var ( // GetGorootVersion returns the major and minor version for a given GOROOT path. // If the goroot cannot be determined, (0, 0) is returned. -func GetGorootVersion(goroot string) (major, minor int, err error) { - s, err := GorootVersionString(goroot) +func GetGorootVersion() (major, minor int, err error) { + s, err := GorootVersionString() if err != nil { return 0, 0, err } @@ -51,24 +48,9 @@ func GetGorootVersion(goroot string) (major, minor int, err error) { } // GorootVersionString returns the version string as reported by the Go -// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but -// can have some variations (for beta releases, for example). -func GorootVersionString(goroot string) (string, error) { - if data, err := os.ReadFile(filepath.Join(goroot, "VERSION")); err == nil { - return string(data), nil - - } else if data, err := os.ReadFile(filepath.Join( - goroot, "src", "internal", "buildcfg", "zbootstrap.go")); err == nil { - - r := regexp.MustCompile("const version = `(.*)`") - matches := r.FindSubmatch(data) - if len(matches) != 2 { - return "", errors.New("Invalid go version output:\n" + string(data)) - } - - return string(matches[1]), nil - - } else { - return "", err - } +// toolchain. It is usually of the form `go1.x.y` but can have some variations +// (for beta releases, for example). +func GorootVersionString() (string, error) { + err := readGoEnvVars() + return goEnvVars.GOVERSION, err } diff --git a/main.go b/main.go index a6a16f92d0..7af036f173 100644 --- a/main.go +++ b/main.go @@ -1871,7 +1871,7 @@ func main() { usage(command) case "version": goversion := "" - if s, err := goenv.GorootVersionString(goenv.Get("GOROOT")); err == nil { + if s, err := goenv.GorootVersionString(); err == nil { goversion = s } version := goenv.Version From fffad84a63488d2ffd6d143229412dc0913d5458 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 7 Jul 2023 15:58:08 +0200 Subject: [PATCH 27/75] reflect: add SetZero This was added in Go 1.20 and is required by encoding/json starting with Go 1.21. --- src/reflect/all_test.go | 3 --- src/reflect/value.go | 10 ++++++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index cb7e4d2ac0..39fc7a3747 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -1559,8 +1559,6 @@ func TestIsZero(t *testing.T) { t.Errorf("%d: IsZero(Zero(TypeOf((%s)(%+v)))) is false", i, x.Kind(), tt.x) } - /* // TODO(tinygo): missing SetZero support - p := New(x.Type()).Elem() p.Set(x) p.SetZero() @@ -1568,7 +1566,6 @@ func TestIsZero(t *testing.T) { t.Errorf("%d: IsZero((%s)(%+v)) is true after SetZero", i, p.Kind(), tt.x) } - */ } diff --git a/src/reflect/value.go b/src/reflect/value.go index 60df826864..2499fca785 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -1068,6 +1068,13 @@ func (v Value) Set(x Value) { memcpy(v.value, xptr, 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() @@ -1569,6 +1576,9 @@ func (e *ValueError) Error() string { //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 From c83f712c17b6024d2cc3130db1d7bb0899e7b419 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Tue, 4 Jul 2023 21:44:20 +0200 Subject: [PATCH 28/75] machine/rp2040: wait for 1000 us after flash reset to avoid issues with busy USB bus Signed-off-by: deadprogram --- .../machine_rp2040_usb_fix_usb_device_enumeration.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/machine/machine_rp2040_usb_fix_usb_device_enumeration.go b/src/machine/machine_rp2040_usb_fix_usb_device_enumeration.go index c67cfe1d30..eedcc4d21e 100644 --- a/src/machine/machine_rp2040_usb_fix_usb_device_enumeration.go +++ b/src/machine/machine_rp2040_usb_fix_usb_device_enumeration.go @@ -3,6 +3,7 @@ package machine import ( + "device/arm" "device/rp" ) @@ -87,6 +88,7 @@ func hw_enumeration_fix_force_ls_j() { rp.USBCTRL_REGS.USB_MUXING.Set(rp.USBCTRL_REGS_USB_MUXING_TO_DIGITAL_PAD | rp.USBCTRL_REGS_USB_MUXING_SOFTCON) // LS_J is now forced but while loop here just to check + waitCycles(125000) // if timer pool disabled, or no timer available, have to busy wait. hw_enumeration_fix_finish() @@ -109,3 +111,10 @@ func hw_enumeration_fix_finish() { // Restore the pad ctrl value padsBank0.io[dp].Set(padCtrlPrev) } + +func waitCycles(n int) { + for n > 0 { + arm.Asm("nop") + n-- + } +} From a7b205c26cab4f87d9b2e5e845482cd206ba1daf Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Sat, 15 Jul 2023 11:24:53 -0300 Subject: [PATCH 29/75] machine.UART refactor (#3832) * add gosched calls to UART * add UART.flush() stubs for all supported architectures * add comment un uart.go on flush functionality * uart.writeByte as base of UART usage * fix NXP having duplicate WriteByte * fix writeByte not returning error on some platforms * add flush method for fe310 device * check for error in WriteByte call to writeByte --- src/machine/machine_atmega.go | 7 +++++-- src/machine/machine_atsamd21.go | 4 +++- src/machine/machine_atsamd51.go | 4 +++- src/machine/machine_esp32.go | 4 +++- src/machine/machine_esp32c3.go | 4 +++- src/machine/machine_esp8266.go | 6 ++++-- src/machine/machine_fe310.go | 5 ++++- src/machine/machine_k210.go | 5 ++++- src/machine/machine_mimxrt1062_uart.go | 4 +++- src/machine/machine_nrf.go | 4 +++- src/machine/machine_nxpmk66f18_uart.go | 4 +++- src/machine/machine_rp2040_uart.go | 9 ++++++++- src/machine/machine_stm32_uart.go | 4 +++- src/machine/uart.go | 22 +++++++++++++++++++--- 14 files changed, 68 insertions(+), 18 deletions(-) diff --git a/src/machine/machine_atmega.go b/src/machine/machine_atmega.go index 90977fb877..81a47c0dfa 100644 --- a/src/machine/machine_atmega.go +++ b/src/machine/machine_atmega.go @@ -97,7 +97,7 @@ func (i2c *I2C) stop() { } // writeByte writes a single byte to the I2C bus. -func (i2c *I2C) writeByte(data byte) { +func (i2c *I2C) writeByte(data byte) error { // Write data to register. avr.TWDR.Set(data) @@ -107,6 +107,7 @@ func (i2c *I2C) writeByte(data byte) { // Wait till data is transmitted. for !avr.TWCR.HasBits(avr.TWCR_TWINT) { } + return nil } // readByte reads a single byte from the I2C bus. @@ -190,7 +191,7 @@ func (uart *UART) handleInterrupt(intr interrupt.Interrupt) { } // WriteByte writes a byte of data to the UART. -func (uart *UART) WriteByte(c byte) error { +func (uart *UART) writeByte(c byte) error { // Wait until UART buffer is not busy. for !uart.statusRegA.HasBits(avr.UCSR0A_UDRE0) { } @@ -198,6 +199,8 @@ func (uart *UART) WriteByte(c byte) error { return nil } +func (uart *UART) flush() {} + // SPIConfig is used to store config info for SPI. type SPIConfig struct { Frequency uint32 diff --git a/src/machine/machine_atsamd21.go b/src/machine/machine_atsamd21.go index 59df853e5c..443c7af56a 100644 --- a/src/machine/machine_atsamd21.go +++ b/src/machine/machine_atsamd21.go @@ -626,7 +626,7 @@ func (uart *UART) SetBaudRate(br uint32) { } // WriteByte writes a byte of data to the UART. -func (uart *UART) WriteByte(c byte) error { +func (uart *UART) writeByte(c byte) error { // wait until ready to receive for !uart.Bus.INTFLAG.HasBits(sam.SERCOM_USART_INTFLAG_DRE) { } @@ -634,6 +634,8 @@ func (uart *UART) WriteByte(c byte) error { return nil } +func (uart *UART) flush() {} + // handleInterrupt should be called from the appropriate interrupt handler for // this UART instance. func (uart *UART) handleInterrupt(interrupt.Interrupt) { diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go index de5491c884..7c4c4d07c7 100644 --- a/src/machine/machine_atsamd51.go +++ b/src/machine/machine_atsamd51.go @@ -1114,7 +1114,7 @@ func (uart *UART) SetBaudRate(br uint32) { } // WriteByte writes a byte of data to the UART. -func (uart *UART) WriteByte(c byte) error { +func (uart *UART) writeByte(c byte) error { // wait until ready to receive for !uart.Bus.INTFLAG.HasBits(sam.SERCOM_USART_INT_INTFLAG_DRE) { } @@ -1122,6 +1122,8 @@ func (uart *UART) WriteByte(c byte) error { return nil } +func (uart *UART) flush() {} + func (uart *UART) handleInterrupt(interrupt.Interrupt) { // should reset IRQ uart.Receive(byte((uart.Bus.DATA.Get() & 0xFF))) diff --git a/src/machine/machine_esp32.go b/src/machine/machine_esp32.go index b58cef66ae..4491b3dd95 100644 --- a/src/machine/machine_esp32.go +++ b/src/machine/machine_esp32.go @@ -314,7 +314,7 @@ func (uart *UART) Configure(config UARTConfig) { uart.Bus.CLKDIV.Set(peripheralClock / config.BaudRate) } -func (uart *UART) WriteByte(b byte) error { +func (uart *UART) writeByte(b byte) error { for (uart.Bus.STATUS.Get()>>16)&0xff >= 128 { // Read UART_TXFIFO_CNT from the status register, which indicates how // many bytes there are in the transmit buffer. Wait until there are @@ -324,6 +324,8 @@ func (uart *UART) WriteByte(b byte) error { return nil } +func (uart *UART) flush() {} + // Serial Peripheral Interface on the ESP32. type SPI struct { Bus *esp.SPI_Type diff --git a/src/machine/machine_esp32c3.go b/src/machine/machine_esp32c3.go index f1f646fd5e..e447e4fe0d 100644 --- a/src/machine/machine_esp32c3.go +++ b/src/machine/machine_esp32c3.go @@ -493,7 +493,7 @@ func (uart *UART) enableReceiver() { uart.Bus.SetINT_ENA_RXFIFO_OVF_INT_ENA(1) } -func (uart *UART) WriteByte(b byte) error { +func (uart *UART) writeByte(b byte) error { for (uart.Bus.STATUS.Get()&esp.UART_STATUS_TXFIFO_CNT_Msk)>>esp.UART_STATUS_TXFIFO_CNT_Pos >= 128 { // Read UART_TXFIFO_CNT from the status register, which indicates how // many bytes there are in the transmit buffer. Wait until there are @@ -502,3 +502,5 @@ func (uart *UART) WriteByte(b byte) error { uart.Bus.FIFO.Set(uint32(b)) return nil } + +func (uart *UART) flush() {} diff --git a/src/machine/machine_esp8266.go b/src/machine/machine_esp8266.go index 37ab29a17d..4edd4a535f 100644 --- a/src/machine/machine_esp8266.go +++ b/src/machine/machine_esp8266.go @@ -181,12 +181,14 @@ func (uart *UART) Configure(config UARTConfig) { esp.UART0.UART_CLKDIV.Set(CPUFrequency() / config.BaudRate) } -// WriteByte writes a single byte to the output buffer. Note that the hardware +// writeByte writes a single byte to the output buffer. Note that the hardware // includes a buffer of 128 bytes which will be used first. -func (uart *UART) WriteByte(c byte) error { +func (uart *UART) writeByte(c byte) error { for (esp.UART0.UART_STATUS.Get()>>16)&0xff >= 128 { // Wait until the TX buffer has room. } esp.UART0.UART_FIFO.Set(uint32(c)) return nil } + +func (uart *UART) flush() {} diff --git a/src/machine/machine_fe310.go b/src/machine/machine_fe310.go index 3a0c451349..85a2c5bd37 100644 --- a/src/machine/machine_fe310.go +++ b/src/machine/machine_fe310.go @@ -111,13 +111,16 @@ func (uart *UART) handleInterrupt(interrupt.Interrupt) { uart.Receive(c) } -func (uart *UART) WriteByte(c byte) { +func (uart *UART) writeByte(c byte) error { for sifive.UART0.TXDATA.Get()&sifive.UART_TXDATA_FULL != 0 { } sifive.UART0.TXDATA.Set(uint32(c)) + return nil } +func (uart *UART) flush() {} + // SPI on the FE310. The normal SPI0 is actually a quad-SPI meant for flash, so it is best // to use SPI1 or SPI2 port for most applications. type SPI struct { diff --git a/src/machine/machine_k210.go b/src/machine/machine_k210.go index 9c63a8f766..e8a304c850 100644 --- a/src/machine/machine_k210.go +++ b/src/machine/machine_k210.go @@ -392,13 +392,16 @@ func (uart *UART) handleInterrupt(interrupt.Interrupt) { uart.Receive(c) } -func (uart *UART) WriteByte(c byte) { +func (uart *UART) writeByte(c byte) error { for uart.Bus.TXDATA.Get()&kendryte.UARTHS_TXDATA_FULL != 0 { } uart.Bus.TXDATA.Set(uint32(c)) + return nil } +func (uart *UART) flush() {} + type SPI struct { Bus *kendryte.SPI_Type } diff --git a/src/machine/machine_mimxrt1062_uart.go b/src/machine/machine_mimxrt1062_uart.go index d24206dfb9..6265b85656 100644 --- a/src/machine/machine_mimxrt1062_uart.go +++ b/src/machine/machine_mimxrt1062_uart.go @@ -173,7 +173,7 @@ func (uart *UART) Sync() error { } // WriteByte writes a single byte of data to the UART interface. -func (uart *UART) WriteByte(c byte) error { +func (uart *UART) writeByte(c byte) error { uart.startTransmitting() for !uart.txBuffer.Put(c) { } @@ -181,6 +181,8 @@ func (uart *UART) WriteByte(c byte) error { return nil } +func (uart *UART) flush() {} + // getBaudRateDivisor finds the greatest over-sampling factor (4..32) and // corresponding baud rate divisor (1..8191) that best partition a given baud // rate into equal intervals. diff --git a/src/machine/machine_nrf.go b/src/machine/machine_nrf.go index 83fa57b3e9..346a0bb2e9 100644 --- a/src/machine/machine_nrf.go +++ b/src/machine/machine_nrf.go @@ -186,7 +186,7 @@ func (uart *UART) SetBaudRate(br uint32) { } // WriteByte writes a byte of data to the UART. -func (uart *UART) WriteByte(c byte) error { +func (uart *UART) writeByte(c byte) error { nrf.UART0.EVENTS_TXDRDY.Set(0) nrf.UART0.TXD.Set(uint32(c)) for nrf.UART0.EVENTS_TXDRDY.Get() == 0 { @@ -194,6 +194,8 @@ func (uart *UART) WriteByte(c byte) error { return nil } +func (uart *UART) flush() {} + func (uart *UART) handleInterrupt(interrupt.Interrupt) { if nrf.UART0.EVENTS_RXDRDY.Get() != 0 { uart.Receive(byte(nrf.UART0.RXD.Get())) diff --git a/src/machine/machine_nxpmk66f18_uart.go b/src/machine/machine_nxpmk66f18_uart.go index d62fc64e01..a14d18f5ef 100644 --- a/src/machine/machine_nxpmk66f18_uart.go +++ b/src/machine/machine_nxpmk66f18_uart.go @@ -292,7 +292,7 @@ func (u *UART) handleStatusInterrupt(interrupt.Interrupt) { } // WriteByte writes a byte of data to the UART. -func (u *UART) WriteByte(c byte) error { +func (u *UART) writeByte(c byte) error { if !u.Configured { return ErrNotConfigured } @@ -305,3 +305,5 @@ func (u *UART) WriteByte(c byte) error { u.C2.Set(uartC2TXActive) return nil } + +func (uart *UART) flush() {} diff --git a/src/machine/machine_rp2040_uart.go b/src/machine/machine_rp2040_uart.go index e5e4f77de3..1d927df128 100644 --- a/src/machine/machine_rp2040_uart.go +++ b/src/machine/machine_rp2040_uart.go @@ -86,9 +86,10 @@ func (uart *UART) SetBaudRate(br uint32) { } // WriteByte writes a byte of data to the UART. -func (uart *UART) WriteByte(c byte) error { +func (uart *UART) writeByte(c byte) error { // wait until buffer is not full for uart.Bus.UARTFR.HasBits(rp.UART0_UARTFR_TXFF) { + gosched() } // write data @@ -96,6 +97,12 @@ func (uart *UART) WriteByte(c byte) error { return nil } +func (uart *UART) flush() { + for uart.Bus.UARTFR.HasBits(rp.UART0_UARTFR_BUSY) { + gosched() + } +} + // SetFormat for number of data bits, stop bits, and parity for the UART. func (uart *UART) SetFormat(databits, stopbits uint8, parity UARTParity) error { var pen, pev uint8 diff --git a/src/machine/machine_stm32_uart.go b/src/machine/machine_stm32_uart.go index 6ae6f0f994..6e8806c876 100644 --- a/src/machine/machine_stm32_uart.go +++ b/src/machine/machine_stm32_uart.go @@ -74,10 +74,12 @@ func (uart *UART) SetBaudRate(br uint32) { } // WriteByte writes a byte of data to the UART. -func (uart *UART) WriteByte(c byte) error { +func (uart *UART) writeByte(c byte) error { uart.txReg.Set(uint32(c)) for !uart.statusReg.HasBits(uart.txEmptyFlag) { } return nil } + +func (uart *UART) flush() {} diff --git a/src/machine/uart.go b/src/machine/uart.go index 37d3223324..eeeb7d6a0b 100644 --- a/src/machine/uart.go +++ b/src/machine/uart.go @@ -59,11 +59,27 @@ func (uart *UART) Read(data []byte) (n int, err error) { return size, nil } -// Write data to the UART. +// WriteByte writes a byte of data over the UART's Tx. +// This function blocks until the data is finished being sent. +func (uart *UART) WriteByte(c byte) error { + err := uart.writeByte(c) + if err != nil { + return err + } + uart.flush() // flush() blocks until all data has been transmitted. + return nil +} + +// Write data over the UART's Tx. +// This function blocks until the data is finished being sent. func (uart *UART) Write(data []byte) (n int, err error) { - for _, v := range data { - uart.WriteByte(v) + for i, v := range data { + err = uart.writeByte(v) + if err != nil { + return i, err + } } + uart.flush() // flush() blocks until all data has been transmitted. return len(data), nil } From 14ddba851903177b26fff97644b6979fea7ade3a Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 13 Jul 2023 15:24:58 +0200 Subject: [PATCH 30/75] nrf: add I2C timeout This commit adds I2C timeouts for nrf51 and nrf52 (but not yet for others like nrf52840). Tested on the PineTime, where I now got a timeout instead of hanging and resetting due to a watchdog reset. --- src/machine/machine_nrf.go | 10 +++++++++- src/machine/machine_nrf5x.go | 17 ++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/machine/machine_nrf.go b/src/machine/machine_nrf.go index 346a0bb2e9..d7b87d9aec 100644 --- a/src/machine/machine_nrf.go +++ b/src/machine/machine_nrf.go @@ -203,6 +203,8 @@ func (uart *UART) handleInterrupt(interrupt.Interrupt) { } } +const i2cTimeout = 0xffff // this is around 29ms on a nrf52 + // I2CConfig is used to store config info for I2C. type I2CConfig struct { Frequency uint32 @@ -261,11 +263,17 @@ func (i2c *I2C) Configure(config I2CConfig) error { } // signalStop sends a stop signal to the I2C peripheral and waits for confirmation. -func (i2c *I2C) signalStop() { +func (i2c *I2C) signalStop() error { + tries := 0 i2c.Bus.TASKS_STOP.Set(1) for i2c.Bus.EVENTS_STOPPED.Get() == 0 { + tries++ + if tries >= i2cTimeout { + return errI2CSignalStopTimeout + } } i2c.Bus.EVENTS_STOPPED.Set(0) + return nil } var rngStarted = false diff --git a/src/machine/machine_nrf5x.go b/src/machine/machine_nrf5x.go index 8c012b49e8..4c731036be 100644 --- a/src/machine/machine_nrf5x.go +++ b/src/machine/machine_nrf5x.go @@ -72,12 +72,17 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) { if len(r) == 0 { // Stop the I2C transaction after the write. - i2c.signalStop() + err = i2c.signalStop() } else { // The last byte read has already stopped the transaction, via // TWI_SHORTS_BB_STOP. But we still need to wait until we receive the // STOPPED event. + tries := 0 for i2c.Bus.EVENTS_STOPPED.Get() == 0 { + tries++ + if tries >= i2cTimeout { + return errI2CSignalStopTimeout + } } i2c.Bus.EVENTS_STOPPED.Set(0) } @@ -87,12 +92,17 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) { // writeByte writes a single byte to the I2C bus and waits for confirmation. func (i2c *I2C) writeByte(data byte) error { + tries := 0 i2c.Bus.TXD.Set(uint32(data)) for i2c.Bus.EVENTS_TXDSENT.Get() == 0 { if e := i2c.Bus.EVENTS_ERROR.Get(); e != 0 { i2c.Bus.EVENTS_ERROR.Set(0) return errI2CBusError } + tries++ + if tries >= i2cTimeout { + return errI2CWriteTimeout + } } i2c.Bus.EVENTS_TXDSENT.Set(0) return nil @@ -100,11 +110,16 @@ func (i2c *I2C) writeByte(data byte) error { // readByte reads a single byte from the I2C bus when it is ready. func (i2c *I2C) readByte() (byte, error) { + tries := 0 for i2c.Bus.EVENTS_RXDREADY.Get() == 0 { if e := i2c.Bus.EVENTS_ERROR.Get(); e != 0 { i2c.Bus.EVENTS_ERROR.Set(0) return 0, errI2CBusError } + tries++ + if tries >= i2cTimeout { + return 0, errI2CReadTimeout + } } i2c.Bus.EVENTS_RXDREADY.Set(0) return byte(i2c.Bus.RXD.Get()), nil From 4da1f6bcbeb0aac18cf7c49184d0038720fe640d Mon Sep 17 00:00:00 2001 From: soypat Date: Sat, 15 Jul 2023 11:07:39 -0300 Subject: [PATCH 31/75] rp2040:add NoPin support --- src/machine/machine_rp2040_gpio.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/machine/machine_rp2040_gpio.go b/src/machine/machine_rp2040_gpio.go index fa2051af86..94715d963f 100644 --- a/src/machine/machine_rp2040_gpio.go +++ b/src/machine/machine_rp2040_gpio.go @@ -174,6 +174,9 @@ func (p Pin) init() { // 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 { @@ -213,6 +216,9 @@ func (p Pin) Configure(config PinConfig) { // Set drives the pin high if value is true else drives it low. func (p Pin) Set(value bool) { + if p == NoPin { + return + } if value { p.set() } else { @@ -251,6 +257,9 @@ var ( // nil func to unset the pin change interrupt. If you do so, the change // parameter is ignored and can be set to any value (such as 0). func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { + if p == NoPin { + return nil + } if p > 31 || p < 0 { return ErrInvalidInputPin } From 01d2ef327d0b3a629ed3808f113f18bd16d81034 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Tue, 18 Jul 2023 15:49:05 +0200 Subject: [PATCH 32/75] sync: add implementation from upstream Go for OnceFunc, OnceValue, and OnceValues Signed-off-by: deadprogram --- src/sync/oncefunc.go | 97 +++++++++++++++++++++++ src/sync/oncefunc_test.go | 159 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+) create mode 100644 src/sync/oncefunc.go create mode 100644 src/sync/oncefunc_test.go diff --git a/src/sync/oncefunc.go b/src/sync/oncefunc.go new file mode 100644 index 0000000000..9ef8344132 --- /dev/null +++ b/src/sync/oncefunc.go @@ -0,0 +1,97 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sync + +// OnceFunc returns a function that invokes f only once. The returned function +// may be called concurrently. +// +// If f panics, the returned function will panic with the same value on every call. +func OnceFunc(f func()) func() { + var ( + once Once + valid bool + p any + ) + // Construct the inner closure just once to reduce costs on the fast path. + g := func() { + defer func() { + p = recover() + if !valid { + // Re-panic immediately so on the first call the user gets a + // complete stack trace into f. + panic(p) + } + }() + f() + valid = true // Set only if f does not panic + } + return func() { + once.Do(g) + if !valid { + panic(p) + } + } +} + +// OnceValue returns a function that invokes f only once and returns the value +// returned by f. The returned function may be called concurrently. +// +// If f panics, the returned function will panic with the same value on every call. +func OnceValue[T any](f func() T) func() T { + var ( + once Once + valid bool + p any + result T + ) + g := func() { + defer func() { + p = recover() + if !valid { + panic(p) + } + }() + result = f() + valid = true + } + return func() T { + once.Do(g) + if !valid { + panic(p) + } + return result + } +} + +// OnceValues returns a function that invokes f only once and returns the values +// returned by f. The returned function may be called concurrently. +// +// If f panics, the returned function will panic with the same value on every call. +func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { + var ( + once Once + valid bool + p any + r1 T1 + r2 T2 + ) + g := func() { + defer func() { + p = recover() + if !valid { + panic(p) + } + }() + r1, r2 = f() + valid = true + } + return func() (T1, T2) { + once.Do(g) + if !valid { + panic(p) + } + return r1, r2 + } +} diff --git a/src/sync/oncefunc_test.go b/src/sync/oncefunc_test.go new file mode 100644 index 0000000000..0273b894a9 --- /dev/null +++ b/src/sync/oncefunc_test.go @@ -0,0 +1,159 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sync_test + +import ( + "sync" + "testing" +) + +// We assume that the Once.Do tests have already covered parallelism. + +func TestOnceFunc(t *testing.T) { + calls := 0 + f := sync.OnceFunc(func() { calls++ }) + allocs := testing.AllocsPerRun(10, f) + if calls != 1 { + t.Errorf("want calls==1, got %d", calls) + } + if allocs != 0 { + t.Errorf("want 0 allocations per call, got %v", allocs) + } +} + +func TestOnceValue(t *testing.T) { + calls := 0 + f := sync.OnceValue(func() int { + calls++ + return calls + }) + allocs := testing.AllocsPerRun(10, func() { f() }) + value := f() + if calls != 1 { + t.Errorf("want calls==1, got %d", calls) + } + if value != 1 { + t.Errorf("want value==1, got %d", value) + } + if allocs != 0 { + t.Errorf("want 0 allocations per call, got %v", allocs) + } +} + +func TestOnceValues(t *testing.T) { + calls := 0 + f := sync.OnceValues(func() (int, int) { + calls++ + return calls, calls + 1 + }) + allocs := testing.AllocsPerRun(10, func() { f() }) + v1, v2 := f() + if calls != 1 { + t.Errorf("want calls==1, got %d", calls) + } + if v1 != 1 || v2 != 2 { + t.Errorf("want v1==1 and v2==2, got %d and %d", v1, v2) + } + if allocs != 0 { + t.Errorf("want 0 allocations per call, got %v", allocs) + } +} + +// TODO: need to implement more complete panic handling for these tests. +// func testOncePanicX(t *testing.T, calls *int, f func()) { +// testOncePanicWith(t, calls, f, func(label string, p any) { +// if p != "x" { +// t.Fatalf("%s: want panic %v, got %v", label, "x", p) +// } +// }) +// } + +// func testOncePanicWith(t *testing.T, calls *int, f func(), check func(label string, p any)) { +// // Check that the each call to f panics with the same value, but the +// // underlying function is only called once. +// for _, label := range []string{"first time", "second time"} { +// var p any +// panicked := true +// func() { +// defer func() { +// p = recover() +// }() +// f() +// panicked = false +// }() +// if !panicked { +// t.Fatalf("%s: f did not panic", label) +// } +// check(label, p) +// } +// if *calls != 1 { +// t.Errorf("want calls==1, got %d", *calls) +// } +// } + +// func TestOnceFuncPanic(t *testing.T) { +// calls := 0 +// f := sync.OnceFunc(func() { +// calls++ +// panic("x") +// }) +// testOncePanicX(t, &calls, f) +// } + +// func TestOnceValuePanic(t *testing.T) { +// calls := 0 +// f := sync.OnceValue(func() int { +// calls++ +// panic("x") +// }) +// testOncePanicX(t, &calls, func() { f() }) +// } + +// func TestOnceValuesPanic(t *testing.T) { +// calls := 0 +// f := sync.OnceValues(func() (int, int) { +// calls++ +// panic("x") +// }) +// testOncePanicX(t, &calls, func() { f() }) +// } +// +// func TestOnceFuncPanicNil(t *testing.T) { +// calls := 0 +// f := sync.OnceFunc(func() { +// calls++ +// panic(nil) +// }) +// testOncePanicWith(t, &calls, f, func(label string, p any) { +// switch p.(type) { +// case nil, *runtime.PanicNilError: +// return +// } +// t.Fatalf("%s: want nil panic, got %v", label, p) +// }) +// } +// +// func TestOnceFuncGoexit(t *testing.T) { +// // If f calls Goexit, the results are unspecified. But check that f doesn't +// // get called twice. +// calls := 0 +// f := sync.OnceFunc(func() { +// calls++ +// runtime.Goexit() +// }) +// var wg sync.WaitGroup +// for i := 0; i < 2; i++ { +// wg.Add(1) +// go func() { +// defer wg.Done() +// defer func() { recover() }() +// f() +// }() +// wg.Wait() +// } +// if calls != 1 { +// t.Errorf("want calls==1, got %d", calls) +// } +// } From 00d46bd25dbc92970d70d88fefde303688ffe72f Mon Sep 17 00:00:00 2001 From: Yurii Soldak Date: Mon, 24 Jul 2023 02:13:38 +0200 Subject: [PATCH 33/75] avr: pin change interrupt --- src/examples/pininterrupt/arduino.go | 11 ++ .../pininterrupt/circuitplay-express.go | 1 + src/examples/pininterrupt/pca10040.go | 1 + src/examples/pininterrupt/pininterrupt.go | 3 +- src/examples/pininterrupt/stm32.go | 1 + src/examples/pininterrupt/wioterminal.go | 1 + src/machine/machine_atmega328p.go | 104 ++++++++++++++++++ 7 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 src/examples/pininterrupt/arduino.go diff --git a/src/examples/pininterrupt/arduino.go b/src/examples/pininterrupt/arduino.go new file mode 100644 index 0000000000..aefdacf968 --- /dev/null +++ b/src/examples/pininterrupt/arduino.go @@ -0,0 +1,11 @@ +//go:build arduino + +package main + +import "machine" + +const ( + button = machine.D2 + buttonMode = machine.PinInputPullup + buttonPinChange = machine.PinRising +) diff --git a/src/examples/pininterrupt/circuitplay-express.go b/src/examples/pininterrupt/circuitplay-express.go index e036550644..54339c9168 100644 --- a/src/examples/pininterrupt/circuitplay-express.go +++ b/src/examples/pininterrupt/circuitplay-express.go @@ -5,6 +5,7 @@ package main import "machine" const ( + button = machine.BUTTON buttonMode = machine.PinInputPulldown buttonPinChange = machine.PinFalling ) diff --git a/src/examples/pininterrupt/pca10040.go b/src/examples/pininterrupt/pca10040.go index e4a9d6feb2..35bb7a866c 100644 --- a/src/examples/pininterrupt/pca10040.go +++ b/src/examples/pininterrupt/pca10040.go @@ -5,6 +5,7 @@ package main import "machine" const ( + button = machine.BUTTON buttonMode = machine.PinInputPullup buttonPinChange = machine.PinRising ) diff --git a/src/examples/pininterrupt/pininterrupt.go b/src/examples/pininterrupt/pininterrupt.go index f693f11721..e13ba5eb3a 100644 --- a/src/examples/pininterrupt/pininterrupt.go +++ b/src/examples/pininterrupt/pininterrupt.go @@ -12,8 +12,7 @@ import ( ) const ( - button = machine.BUTTON - led = machine.LED + led = machine.LED ) func main() { diff --git a/src/examples/pininterrupt/stm32.go b/src/examples/pininterrupt/stm32.go index 24d7e5f7bc..02f2fb94e8 100644 --- a/src/examples/pininterrupt/stm32.go +++ b/src/examples/pininterrupt/stm32.go @@ -5,6 +5,7 @@ package main import "machine" const ( + button = machine.BUTTON buttonMode = machine.PinInputPulldown buttonPinChange = machine.PinRising | machine.PinFalling ) diff --git a/src/examples/pininterrupt/wioterminal.go b/src/examples/pininterrupt/wioterminal.go index 90443537aa..da5291db18 100644 --- a/src/examples/pininterrupt/wioterminal.go +++ b/src/examples/pininterrupt/wioterminal.go @@ -5,6 +5,7 @@ package main import "machine" const ( + button = machine.BUTTON buttonMode = machine.PinInput buttonPinChange = machine.PinFalling ) diff --git a/src/machine/machine_atmega328p.go b/src/machine/machine_atmega328p.go index ed832e6d33..0987a145c1 100644 --- a/src/machine/machine_atmega328p.go +++ b/src/machine/machine_atmega328p.go @@ -466,3 +466,107 @@ var SPI0 = SPI{ sdo: PB3, sdi: PB4, cs: PB2} + +// Pin Change Interrupts +type PinChange uint8 + +const ( + PinRising PinChange = 1 << iota + PinFalling + PinToggle = PinRising | PinFalling +) + +func (pin Pin) SetInterrupt(pinChange PinChange, callback func(Pin)) (err error) { + + switch { + case pin >= PB0 && pin <= PB7: + // PCMSK0 - PCINT0-7 + pinStates[0] = avr.PINB.Get() + pinIndex := pin - PB0 + if pinChange&PinRising > 0 { + pinCallbacks[0][pinIndex][0] = callback + } + if pinChange&PinFalling > 0 { + pinCallbacks[0][pinIndex][1] = callback + } + if callback != nil { + avr.PCMSK0.SetBits(1 << pinIndex) + } else { + avr.PCMSK0.ClearBits(1 << pinIndex) + } + avr.PCICR.SetBits(avr.PCICR_PCIE0) + interrupt.New(avr.IRQ_PCINT0, handlePCINT0Interrupts) + case pin >= PC0 && pin <= PC7: + // PCMSK1 - PCINT8-14 + pinStates[1] = avr.PINC.Get() + pinIndex := pin - PC0 + if pinChange&PinRising > 0 { + pinCallbacks[1][pinIndex][0] = callback + } + if pinChange&PinFalling > 0 { + pinCallbacks[1][pinIndex][1] = callback + } + if callback != nil { + avr.PCMSK1.SetBits(1 << pinIndex) + } else { + avr.PCMSK1.ClearBits(1 << pinIndex) + } + avr.PCICR.SetBits(avr.PCICR_PCIE1) + interrupt.New(avr.IRQ_PCINT1, handlePCINT1Interrupts) + case pin >= PD0 && pin <= PD7: + // PCMSK2 - PCINT16-23 + pinStates[2] = avr.PIND.Get() + pinIndex := pin - PD0 + if pinChange&PinRising > 0 { + pinCallbacks[2][pinIndex][0] = callback + } + if pinChange&PinFalling > 0 { + pinCallbacks[2][pinIndex][1] = callback + } + if callback != nil { + avr.PCMSK2.SetBits(1 << pinIndex) + } else { + avr.PCMSK2.ClearBits(1 << pinIndex) + } + avr.PCICR.SetBits(avr.PCICR_PCIE2) + interrupt.New(avr.IRQ_PCINT2, handlePCINT2Interrupts) + default: + return ErrInvalidInputPin + } + + return nil +} + +var pinCallbacks [3][8][2]func(Pin) +var pinStates [3]uint8 + +func handlePCINTInterrupts(intr uint8, port *volatile.Register8) { + current := port.Get() + change := pinStates[intr] ^ current + pinStates[intr] = current + for i := uint8(0); i < 8; i++ { + if (change>>i)&0x01 != 0x01 { + continue + } + pin := Pin(intr*8 + i) + value := pin.Get() + if value && pinCallbacks[intr][i][0] != nil { + pinCallbacks[intr][i][0](pin) + } + if !value && pinCallbacks[intr][i][1] != nil { + pinCallbacks[intr][i][1](pin) + } + } +} + +func handlePCINT0Interrupts(intr interrupt.Interrupt) { + handlePCINTInterrupts(0, avr.PINB) +} + +func handlePCINT1Interrupts(intr interrupt.Interrupt) { + handlePCINTInterrupts(1, avr.PINC) +} + +func handlePCINT2Interrupts(intr interrupt.Interrupt) { + handlePCINTInterrupts(2, avr.PIND) +} From d845f1e1b2f04b1c1e778bb7caac005967c1ed7c Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 28 Jul 2023 12:49:07 +0200 Subject: [PATCH 34/75] wasm: fix functions exported through //export When a function is exported using //export, but also had a //go:wasm-module pragma, the //export name was ignored. The //go:wasm-module doesn't actually do anything besides breaking the export (exported functions don't have a module name). I've refactored and cleaned up the code, and in the process removed this weird edge case. --- compiler/symbol.go | 45 +++++++++++-------------------------- compiler/testdata/pragma.go | 13 +++++++++++ compiler/testdata/pragma.ll | 16 ++++++++++--- 3 files changed, 39 insertions(+), 35 deletions(-) diff --git a/compiler/symbol.go b/compiler/symbol.go index 6426604ec1..3e782dae88 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -23,9 +23,9 @@ import ( // The linkName value contains a valid link name, even if //go:linkname is not // present. type functionInfo struct { - module string // go:wasm-module - importName string // go:linkname, go:export - The name the developer assigns - linkName string // go:linkname, go:export - The name that we map for the particular module -> importName + wasmModule string // go:wasm-module + wasmName string // wasm-export-name or wasm-import-name in the IR + linkName string // go:linkname, go:export - the IR function name section string // go:section - object file section name exported bool // go:export, CGo interrupt bool // go:interrupt @@ -194,20 +194,14 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) // External/exported functions may not retain pointer values. // https://golang.org/cmd/cgo/#hdr-Passing_pointers if info.exported { - if c.archFamily() == "wasm32" { + if c.archFamily() == "wasm32" && len(fn.Blocks) == 0 { // We need to add the wasm-import-module and the wasm-import-name // attributes. - module := info.module - if module == "" { - module = "env" + if info.wasmModule != "" { + llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("wasm-import-module", info.wasmModule)) } - llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("wasm-import-module", module)) - name := info.importName - if name == "" { - name = info.linkName - } - llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("wasm-import-name", name)) + llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("wasm-import-name", info.wasmName)) } nocaptureKind := llvm.AttributeKindID("nocapture") nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0) @@ -260,10 +254,6 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { return } if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil { - - // Our importName for a wasm module (if we are compiling to wasm), or llvm link name - var importName string - for _, comment := range decl.Doc.List { text := comment.Text if strings.HasPrefix(text, "//export ") { @@ -281,7 +271,8 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { continue } - importName = parts[1] + info.linkName = parts[1] + info.wasmName = info.linkName info.exported = true case "//go:interrupt": if hasUnsafeImport(f.Pkg.Pkg) { @@ -289,10 +280,11 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { } case "//go:wasm-module": // Alternative comment for setting the import module. + // This is deprecated, use //go:wasmimport instead. if len(parts) != 2 { continue } - info.module = parts[1] + info.wasmModule = parts[1] case "//go:wasmimport": // Import a WebAssembly function, for example a WASI function. // Original proposal: https://github.com/golang/go/issues/38248 @@ -302,8 +294,8 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { } c.checkWasmImport(f, comment.Text) info.exported = true - info.module = parts[1] - info.importName = parts[2] + info.wasmModule = parts[1] + info.wasmName = parts[2] case "//go:inline": info.inline = inlineHint case "//go:noinline": @@ -347,17 +339,6 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { } } } - - // Set the importName for our exported function if we have one - if importName != "" { - if info.module == "" { - info.linkName = importName - } else { - // WebAssembly import - info.importName = importName - } - } - } } diff --git a/compiler/testdata/pragma.go b/compiler/testdata/pragma.go index 0a4be11aea..fa1d4b0e96 100644 --- a/compiler/testdata/pragma.go +++ b/compiler/testdata/pragma.go @@ -62,6 +62,19 @@ func exportedFunctionInSection() { //go:wasmimport modulename import1 func declaredImport() +// Legacy way of importing a function. +// +//go:wasm-module foobar +//export imported +func foobarImport() + +// The wasm-module pragma is not functional here, but it should be safe. +// +//go:wasm-module foobar +//export exported +func foobarExportModule() { +} + // This function should not: it's only a declaration and not a definition. // //go:section .special_function_section diff --git a/compiler/testdata/pragma.ll b/compiler/testdata/pragma.ll index 78cc29457d..397772e5d0 100644 --- a/compiler/testdata/pragma.ll +++ b/compiler/testdata/pragma.ll @@ -6,7 +6,7 @@ target triple = "wasm32-unknown-wasi" @extern_global = external global [0 x i8], align 1 @main.alignedGlobal = hidden global [4 x i32] zeroinitializer, align 32 @main.alignedGlobal16 = hidden global [4 x i32] zeroinitializer, align 16 -@llvm.used = appending global [2 x ptr] [ptr @extern_func, ptr @exportedFunctionInSection] +@llvm.used = appending global [3 x ptr] [ptr @extern_func, ptr @exportedFunctionInSection, ptr @exported] @main.globalInSection = hidden global i32 0, section ".special_global_section", align 4 @undefinedGlobalNotInSection = external global i32, align 4 @main.multipleGlobalPragmas = hidden global i32 0, section ".global_section", align 1024 @@ -62,13 +62,23 @@ entry: declare void @main.declaredImport() #7 +declare void @imported() #8 + +; Function Attrs: nounwind +define void @exported() #9 { +entry: + ret void +} + declare void @main.undefinedFunctionNotInSection(ptr) #1 attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } attributes #1 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } attributes #2 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } -attributes #3 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" "wasm-export-name"="extern_func" "wasm-import-module"="env" "wasm-import-name"="extern_func" } +attributes #3 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" "wasm-export-name"="extern_func" } attributes #4 = { inlinehint nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } attributes #5 = { noinline nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } -attributes #6 = { noinline nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" "wasm-export-name"="exportedFunctionInSection" "wasm-import-module"="env" "wasm-import-name"="exportedFunctionInSection" } +attributes #6 = { noinline nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" "wasm-export-name"="exportedFunctionInSection" } attributes #7 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" "wasm-import-module"="modulename" "wasm-import-name"="import1" } +attributes #8 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" "wasm-import-module"="foobar" "wasm-import-name"="imported" } +attributes #9 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" "wasm-export-name"="exported" } From 5b581d83b379d77472405ffc1296939b7af409d4 Mon Sep 17 00:00:00 2001 From: Charlie Haley Date: Wed, 26 Jul 2023 18:22:49 +0100 Subject: [PATCH 35/75] compiler: add compiler-rt and wasm symbols to table --- builder/ar.go | 20 +++++++++++++++++++- targets/wasi.json | 1 + 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/builder/ar.go b/builder/ar.go index c3aad171eb..3f1c8c213f 100644 --- a/builder/ar.go +++ b/builder/ar.go @@ -12,6 +12,7 @@ import ( "path/filepath" "time" + wasm "github.com/aykevl/go-wasm" "github.com/blakesmith/ar" ) @@ -74,8 +75,25 @@ func makeArchive(arfile *os.File, objs []string) error { fileIndex int }{symbol.Name, i}) } + } else if dbg, err := wasm.Parse(objfile); err == nil { + for _, s := range dbg.Sections { + switch section := s.(type) { + case *wasm.SectionImport: + for _, ln := range section.Entries { + + if ln.Kind != wasm.ExtKindFunction { + // Not a function + continue + } + symbolTable = append(symbolTable, struct { + name string + fileIndex int + }{ln.Field, i}) + } + } + } } else { - return fmt.Errorf("failed to open file %s as ELF or PE/COFF: %w", objpath, err) + return fmt.Errorf("failed to open file %s as WASM, ELF or PE/COFF: %w", objpath, err) } // Close file, to avoid issues with too many open files (especially on diff --git a/targets/wasi.json b/targets/wasi.json index f55c7c1fd7..83f808e8ee 100644 --- a/targets/wasi.json +++ b/targets/wasi.json @@ -7,6 +7,7 @@ "goarch": "arm", "linker": "wasm-ld", "libc": "wasi-libc", + "rtlib": "compiler-rt", "scheduler": "asyncify", "default-stack-size": 16384, "cflags": [ From 5f2e72f3716b4c3a1dde059d0c4471fc2c7adac9 Mon Sep 17 00:00:00 2001 From: Charlie Haley Date: Mon, 31 Jul 2023 19:48:11 +0100 Subject: [PATCH 36/75] compiler: add compiler-rt to wasm.json --- targets/wasm.json | 1 + 1 file changed, 1 insertion(+) diff --git a/targets/wasm.json b/targets/wasm.json index 37034c6c3a..4e0de37d1e 100644 --- a/targets/wasm.json +++ b/targets/wasm.json @@ -7,6 +7,7 @@ "goarch": "wasm", "linker": "wasm-ld", "libc": "wasi-libc", + "rtlib": "compiler-rt", "scheduler": "asyncify", "default-stack-size": 16384, "cflags": [ From d1eb642d45191218c95ab261183a6004785420ec Mon Sep 17 00:00:00 2001 From: sago35 Date: Sun, 2 Jul 2023 11:30:35 +0900 Subject: [PATCH 37/75] machine/usb: remove usbDescriptorConfig --- src/machine/usb.go | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/src/machine/usb.go b/src/machine/usb.go index a90fab177c..34c13a5c75 100644 --- a/src/machine/usb.go +++ b/src/machine/usb.go @@ -29,9 +29,7 @@ type Serialer interface { RTS() bool } -var usbDescriptor = descriptor.CDC - -var usbDescriptorConfig uint8 = usb.DescriptorConfigCDC +var usbDescriptor descriptor.Descriptor func usbVendorID() uint16 { if usb.VendorID != 0 { @@ -138,18 +136,6 @@ func sendDescriptor(setup usb.Setup) { sendUSBPacket(0, usbDescriptor.Configuration, setup.WLength) return case descriptor.TypeDevice: - // composite descriptor - switch { - case (usbDescriptorConfig & usb.DescriptorConfigHID) > 0: - usbDescriptor = descriptor.CDCHID - case (usbDescriptorConfig & usb.DescriptorConfigMIDI) > 0: - usbDescriptor = descriptor.CDCMIDI - case (usbDescriptorConfig & usb.DescriptorConfigJoystick) > 0: - usbDescriptor = descriptor.CDCJoystick - default: - usbDescriptor = descriptor.CDC - } - usbDescriptor.Configure(usbVendorID(), usbProductID()) sendUSBPacket(0, usbDescriptor.Device, setup.WLength) return @@ -273,7 +259,10 @@ func handleStandardSetup(setup usb.Setup) bool { } func EnableCDC(txHandler func(), rxHandler func([]byte), setupHandler func(usb.Setup) bool) { - usbDescriptorConfig |= usb.DescriptorConfigCDC + if len(usbDescriptor.Device) == 0 { + usbDescriptor = descriptor.CDC + } + // Initialization of endpoints is required even for non-CDC endPoints[usb.CDC_ENDPOINT_ACM] = (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn) endPoints[usb.CDC_ENDPOINT_OUT] = (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut) endPoints[usb.CDC_ENDPOINT_IN] = (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn) @@ -285,7 +274,7 @@ func EnableCDC(txHandler func(), rxHandler func([]byte), setupHandler func(usb.S // EnableHID enables HID. This function must be executed from the init(). func EnableHID(txHandler func(), rxHandler func([]byte), setupHandler func(usb.Setup) bool) { - usbDescriptorConfig |= usb.DescriptorConfigHID + usbDescriptor = descriptor.CDCHID endPoints[usb.HID_ENDPOINT_IN] = (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn) usbTxHandler[usb.HID_ENDPOINT_IN] = txHandler usbSetupHandler[usb.HID_INTERFACE] = setupHandler // 0x03 (HID - Human Interface Device) @@ -293,7 +282,7 @@ func EnableHID(txHandler func(), rxHandler func([]byte), setupHandler func(usb.S // EnableMIDI enables MIDI. This function must be executed from the init(). func EnableMIDI(txHandler func(), rxHandler func([]byte), setupHandler func(usb.Setup) bool) { - usbDescriptorConfig |= usb.DescriptorConfigMIDI + usbDescriptor = descriptor.CDCMIDI endPoints[usb.MIDI_ENDPOINT_OUT] = (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut) endPoints[usb.MIDI_ENDPOINT_IN] = (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn) usbRxHandler[usb.MIDI_ENDPOINT_OUT] = rxHandler @@ -311,7 +300,7 @@ func EnableJoystick(txHandler func(), rxHandler func([]byte), setupHandler func( class.ClassLength(uint16(len(hidDesc))) descriptor.CDCJoystick.HID[2] = hidDesc - usbDescriptorConfig |= usb.DescriptorConfigJoystick + usbDescriptor = descriptor.CDCJoystick endPoints[usb.HID_ENDPOINT_OUT] = (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut) usbRxHandler[usb.HID_ENDPOINT_OUT] = rxHandler endPoints[usb.HID_ENDPOINT_IN] = (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn) From 069e4f0d987bef4699c55e84b55c7e5caa3f928c Mon Sep 17 00:00:00 2001 From: sago35 Date: Tue, 1 Aug 2023 20:22:30 +0900 Subject: [PATCH 38/75] machine/usb: allow USB Endpoint settings to be changed externally --- src/machine/usb.go | 81 +++++++++++++----------- src/machine/usb/adc/midi/midi.go | 19 +++++- src/machine/usb/config.go | 14 ++++ src/machine/usb/hid/hid.go | 17 ++++- src/machine/usb/hid/joystick/joystick.go | 45 +++++++++++-- 5 files changed, 130 insertions(+), 46 deletions(-) create mode 100644 src/machine/usb/config.go diff --git a/src/machine/usb.go b/src/machine/usb.go index 34c13a5c75..823dfdb5ff 100644 --- a/src/machine/usb.go +++ b/src/machine/usb.go @@ -263,47 +263,52 @@ func EnableCDC(txHandler func(), rxHandler func([]byte), setupHandler func(usb.S usbDescriptor = descriptor.CDC } // Initialization of endpoints is required even for non-CDC - endPoints[usb.CDC_ENDPOINT_ACM] = (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn) - endPoints[usb.CDC_ENDPOINT_OUT] = (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut) - endPoints[usb.CDC_ENDPOINT_IN] = (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn) - usbRxHandler[usb.CDC_ENDPOINT_OUT] = rxHandler - usbTxHandler[usb.CDC_ENDPOINT_IN] = txHandler - usbSetupHandler[usb.CDC_ACM_INTERFACE] = setupHandler // 0x02 (Communications and CDC Control) - usbSetupHandler[usb.CDC_DATA_INTERFACE] = nil // 0x0A (CDC-Data) + ConfigureUSBEndpoint(usbDescriptor, + []usb.EndpointConfig{ + { + No: usb.CDC_ENDPOINT_ACM, + IsIn: true, + Type: usb.ENDPOINT_TYPE_INTERRUPT, + }, + { + No: usb.CDC_ENDPOINT_OUT, + IsIn: false, + Type: usb.ENDPOINT_TYPE_BULK, + RxHandler: rxHandler, + }, + { + No: usb.CDC_ENDPOINT_IN, + IsIn: true, + Type: usb.ENDPOINT_TYPE_BULK, + TxHandler: txHandler, + }, + }, + []usb.SetupConfig{ + { + No: usb.CDC_ACM_INTERFACE, + Handler: setupHandler, + }, + }) } -// EnableHID enables HID. This function must be executed from the init(). -func EnableHID(txHandler func(), rxHandler func([]byte), setupHandler func(usb.Setup) bool) { - usbDescriptor = descriptor.CDCHID - endPoints[usb.HID_ENDPOINT_IN] = (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn) - usbTxHandler[usb.HID_ENDPOINT_IN] = txHandler - usbSetupHandler[usb.HID_INTERFACE] = setupHandler // 0x03 (HID - Human Interface Device) -} - -// EnableMIDI enables MIDI. This function must be executed from the init(). -func EnableMIDI(txHandler func(), rxHandler func([]byte), setupHandler func(usb.Setup) bool) { - usbDescriptor = descriptor.CDCMIDI - endPoints[usb.MIDI_ENDPOINT_OUT] = (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut) - endPoints[usb.MIDI_ENDPOINT_IN] = (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn) - usbRxHandler[usb.MIDI_ENDPOINT_OUT] = rxHandler - usbTxHandler[usb.MIDI_ENDPOINT_IN] = txHandler -} +func ConfigureUSBEndpoint(desc descriptor.Descriptor, epSettings []usb.EndpointConfig, setup []usb.SetupConfig) { + usbDescriptor = desc -// EnableJoystick enables HID. This function must be executed from the init(). -func EnableJoystick(txHandler func(), rxHandler func([]byte), setupHandler func(usb.Setup) bool, hidDesc []byte) { - class, err := descriptor.FindClassHIDType(descriptor.CDCJoystick.Configuration, descriptor.ClassHIDJoystick.Bytes()) - if err != nil { - // TODO: some way to notify about error - return + for _, ep := range epSettings { + if ep.IsIn { + endPoints[ep.No] = uint32(ep.Type | usb.EndpointIn) + if ep.TxHandler != nil { + usbTxHandler[ep.No] = ep.TxHandler + } + } else { + endPoints[ep.No] = uint32(ep.Type | usb.EndpointOut) + if ep.RxHandler != nil { + usbRxHandler[ep.No] = ep.RxHandler + } + } } - class.ClassLength(uint16(len(hidDesc))) - descriptor.CDCJoystick.HID[2] = hidDesc - - usbDescriptor = descriptor.CDCJoystick - endPoints[usb.HID_ENDPOINT_OUT] = (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut) - usbRxHandler[usb.HID_ENDPOINT_OUT] = rxHandler - endPoints[usb.HID_ENDPOINT_IN] = (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn) - usbTxHandler[usb.HID_ENDPOINT_IN] = txHandler - usbSetupHandler[usb.HID_INTERFACE] = setupHandler // 0x03 (HID - Human Interface Device) + for _, s := range setup { + usbSetupHandler[s.No] = s.Handler + } } diff --git a/src/machine/usb/adc/midi/midi.go b/src/machine/usb/adc/midi/midi.go index 2697866f3c..d0ce3ee1fc 100644 --- a/src/machine/usb/adc/midi/midi.go +++ b/src/machine/usb/adc/midi/midi.go @@ -3,6 +3,7 @@ package midi import ( "machine" "machine/usb" + "machine/usb/descriptor" ) const ( @@ -40,7 +41,23 @@ func newMidi() *midi { m := &midi{ buf: NewRingBuffer(), } - machine.EnableMIDI(m.Handler, m.RxHandler, nil) + machine.ConfigureUSBEndpoint(descriptor.CDCMIDI, + []usb.EndpointConfig{ + { + No: usb.MIDI_ENDPOINT_OUT, + IsIn: false, + Type: usb.ENDPOINT_TYPE_BULK, + RxHandler: m.RxHandler, + }, + { + No: usb.MIDI_ENDPOINT_IN, + IsIn: true, + Type: usb.ENDPOINT_TYPE_BULK, + TxHandler: m.Handler, + }, + }, + []usb.SetupConfig{}, + ) return m } diff --git a/src/machine/usb/config.go b/src/machine/usb/config.go new file mode 100644 index 0000000000..de620e7aa6 --- /dev/null +++ b/src/machine/usb/config.go @@ -0,0 +1,14 @@ +package usb + +type EndpointConfig struct { + No uint8 + IsIn bool + TxHandler func() + RxHandler func([]byte) + Type uint8 +} + +type SetupConfig struct { + No uint8 + Handler func(Setup) bool +} diff --git a/src/machine/usb/hid/hid.go b/src/machine/usb/hid/hid.go index 6765696212..4a17f953a8 100644 --- a/src/machine/usb/hid/hid.go +++ b/src/machine/usb/hid/hid.go @@ -4,6 +4,7 @@ import ( "errors" "machine" "machine/usb" + "machine/usb/descriptor" ) // from usb-hid.go @@ -32,7 +33,21 @@ var size int // calls machine.EnableHID for USB configuration func SetHandler(d hidDevicer) { if size == 0 { - machine.EnableHID(handler, nil, setupHandler) + machine.ConfigureUSBEndpoint(descriptor.CDCHID, + []usb.EndpointConfig{ + { + No: usb.HID_ENDPOINT_IN, + IsIn: true, + Type: usb.ENDPOINT_TYPE_INTERRUPT, + TxHandler: handler, + }, + }, + []usb.SetupConfig{ + { + No: usb.HID_INTERFACE, + Handler: setupHandler, + }, + }) } devices[size] = d diff --git a/src/machine/usb/hid/joystick/joystick.go b/src/machine/usb/hid/joystick/joystick.go index 763b39ee2f..18a4d470e4 100644 --- a/src/machine/usb/hid/joystick/joystick.go +++ b/src/machine/usb/hid/joystick/joystick.go @@ -3,6 +3,7 @@ package joystick import ( "machine" "machine/usb" + "machine/usb/descriptor" "machine/usb/hid" ) @@ -32,18 +33,50 @@ func UseSettings(def Definitions, rxHandlerFunc func(b []byte), setupFunc func(s if setupFunc == nil { setupFunc = hid.DefaultSetupHandler } - machine.EnableJoystick(js.handler, rxHandlerFunc, setupFunc, hidDesc) + if rxHandlerFunc == nil { + rxHandlerFunc = js.rxHandlerFunc + } + if len(hidDesc) == 0 { + hidDesc = descriptor.JoystickDefaultHIDReport + } + class, err := descriptor.FindClassHIDType(descriptor.CDCJoystick.Configuration, descriptor.ClassHIDJoystick.Bytes()) + if err != nil { + // TODO: some way to notify about error + return nil + } + + class.ClassLength(uint16(len(hidDesc))) + descriptor.CDCJoystick.HID[2] = hidDesc + + machine.ConfigureUSBEndpoint(descriptor.CDCJoystick, + []usb.EndpointConfig{ + { + No: usb.HID_ENDPOINT_OUT, + IsIn: false, + Type: usb.ENDPOINT_TYPE_INTERRUPT, + RxHandler: rxHandlerFunc, + }, + { + No: usb.HID_ENDPOINT_IN, + IsIn: true, + Type: usb.ENDPOINT_TYPE_INTERRUPT, + TxHandler: js.handler, + }, + }, + []usb.SetupConfig{ + { + No: usb.HID_INTERFACE, + Handler: setupFunc, + }, + }, + ) Joystick = js return js } func newDefaultJoystick() *joystick { def := DefaultDefinitions() - js := &joystick{ - State: def.NewState(), - buf: hid.NewRingBuffer(), - } - machine.EnableJoystick(js.handler, js.rxHandler, hid.DefaultSetupHandler, def.Descriptor()) + js := UseSettings(def, nil, nil, nil) return js } From 395ee2d338d323d0b9225d7522dc419ac40aaaf4 Mon Sep 17 00:00:00 2001 From: sago35 Date: Wed, 2 Aug 2023 08:30:51 +0900 Subject: [PATCH 39/75] machine/usb: refactor endpoint configuration --- src/machine/usb.go | 22 +++++++++++----------- src/machine/usb/adc/midi/midi.go | 4 ++-- src/machine/usb/config.go | 4 ++-- src/machine/usb/hid/hid.go | 4 ++-- src/machine/usb/hid/joystick/joystick.go | 6 +++--- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/machine/usb.go b/src/machine/usb.go index 823dfdb5ff..c28462e536 100644 --- a/src/machine/usb.go +++ b/src/machine/usb.go @@ -266,18 +266,18 @@ func EnableCDC(txHandler func(), rxHandler func([]byte), setupHandler func(usb.S ConfigureUSBEndpoint(usbDescriptor, []usb.EndpointConfig{ { - No: usb.CDC_ENDPOINT_ACM, - IsIn: true, - Type: usb.ENDPOINT_TYPE_INTERRUPT, + Index: usb.CDC_ENDPOINT_ACM, + IsIn: true, + Type: usb.ENDPOINT_TYPE_INTERRUPT, }, { - No: usb.CDC_ENDPOINT_OUT, + Index: usb.CDC_ENDPOINT_OUT, IsIn: false, Type: usb.ENDPOINT_TYPE_BULK, RxHandler: rxHandler, }, { - No: usb.CDC_ENDPOINT_IN, + Index: usb.CDC_ENDPOINT_IN, IsIn: true, Type: usb.ENDPOINT_TYPE_BULK, TxHandler: txHandler, @@ -285,7 +285,7 @@ func EnableCDC(txHandler func(), rxHandler func([]byte), setupHandler func(usb.S }, []usb.SetupConfig{ { - No: usb.CDC_ACM_INTERFACE, + Index: usb.CDC_ACM_INTERFACE, Handler: setupHandler, }, }) @@ -296,19 +296,19 @@ func ConfigureUSBEndpoint(desc descriptor.Descriptor, epSettings []usb.EndpointC for _, ep := range epSettings { if ep.IsIn { - endPoints[ep.No] = uint32(ep.Type | usb.EndpointIn) + endPoints[ep.Index] = uint32(ep.Type | usb.EndpointIn) if ep.TxHandler != nil { - usbTxHandler[ep.No] = ep.TxHandler + usbTxHandler[ep.Index] = ep.TxHandler } } else { - endPoints[ep.No] = uint32(ep.Type | usb.EndpointOut) + endPoints[ep.Index] = uint32(ep.Type | usb.EndpointOut) if ep.RxHandler != nil { - usbRxHandler[ep.No] = ep.RxHandler + usbRxHandler[ep.Index] = ep.RxHandler } } } for _, s := range setup { - usbSetupHandler[s.No] = s.Handler + usbSetupHandler[s.Index] = s.Handler } } diff --git a/src/machine/usb/adc/midi/midi.go b/src/machine/usb/adc/midi/midi.go index d0ce3ee1fc..30b645ee23 100644 --- a/src/machine/usb/adc/midi/midi.go +++ b/src/machine/usb/adc/midi/midi.go @@ -44,13 +44,13 @@ func newMidi() *midi { machine.ConfigureUSBEndpoint(descriptor.CDCMIDI, []usb.EndpointConfig{ { - No: usb.MIDI_ENDPOINT_OUT, + Index: usb.MIDI_ENDPOINT_OUT, IsIn: false, Type: usb.ENDPOINT_TYPE_BULK, RxHandler: m.RxHandler, }, { - No: usb.MIDI_ENDPOINT_IN, + Index: usb.MIDI_ENDPOINT_IN, IsIn: true, Type: usb.ENDPOINT_TYPE_BULK, TxHandler: m.Handler, diff --git a/src/machine/usb/config.go b/src/machine/usb/config.go index de620e7aa6..ef7cd31530 100644 --- a/src/machine/usb/config.go +++ b/src/machine/usb/config.go @@ -1,7 +1,7 @@ package usb type EndpointConfig struct { - No uint8 + Index uint8 IsIn bool TxHandler func() RxHandler func([]byte) @@ -9,6 +9,6 @@ type EndpointConfig struct { } type SetupConfig struct { - No uint8 + Index uint8 Handler func(Setup) bool } diff --git a/src/machine/usb/hid/hid.go b/src/machine/usb/hid/hid.go index 4a17f953a8..e73c7a4a2b 100644 --- a/src/machine/usb/hid/hid.go +++ b/src/machine/usb/hid/hid.go @@ -36,7 +36,7 @@ func SetHandler(d hidDevicer) { machine.ConfigureUSBEndpoint(descriptor.CDCHID, []usb.EndpointConfig{ { - No: usb.HID_ENDPOINT_IN, + Index: usb.HID_ENDPOINT_IN, IsIn: true, Type: usb.ENDPOINT_TYPE_INTERRUPT, TxHandler: handler, @@ -44,7 +44,7 @@ func SetHandler(d hidDevicer) { }, []usb.SetupConfig{ { - No: usb.HID_INTERFACE, + Index: usb.HID_INTERFACE, Handler: setupHandler, }, }) diff --git a/src/machine/usb/hid/joystick/joystick.go b/src/machine/usb/hid/joystick/joystick.go index 18a4d470e4..2c2f719c0d 100644 --- a/src/machine/usb/hid/joystick/joystick.go +++ b/src/machine/usb/hid/joystick/joystick.go @@ -51,13 +51,13 @@ func UseSettings(def Definitions, rxHandlerFunc func(b []byte), setupFunc func(s machine.ConfigureUSBEndpoint(descriptor.CDCJoystick, []usb.EndpointConfig{ { - No: usb.HID_ENDPOINT_OUT, + Index: usb.HID_ENDPOINT_OUT, IsIn: false, Type: usb.ENDPOINT_TYPE_INTERRUPT, RxHandler: rxHandlerFunc, }, { - No: usb.HID_ENDPOINT_IN, + Index: usb.HID_ENDPOINT_IN, IsIn: true, Type: usb.ENDPOINT_TYPE_INTERRUPT, TxHandler: js.handler, @@ -65,7 +65,7 @@ func UseSettings(def Definitions, rxHandlerFunc func(b []byte), setupFunc func(s }, []usb.SetupConfig{ { - No: usb.HID_INTERFACE, + Index: usb.HID_INTERFACE, Handler: setupFunc, }, }, From c51f5cea0bd4a8c3d7b80544dc6577ddacf947b6 Mon Sep 17 00:00:00 2001 From: sago35 Date: Mon, 3 Jul 2023 22:54:14 +0900 Subject: [PATCH 40/75] machine/usb/hid: add RxHandler interface --- src/machine/usb/hid/hid.go | 12 ++++++++++++ src/machine/usb/hid/keyboard/keyboard.go | 4 ++++ src/machine/usb/hid/mouse/mouse.go | 4 ++++ 3 files changed, 20 insertions(+) diff --git a/src/machine/usb/hid/hid.go b/src/machine/usb/hid/hid.go index e73c7a4a2b..e3948dc410 100644 --- a/src/machine/usb/hid/hid.go +++ b/src/machine/usb/hid/hid.go @@ -24,6 +24,7 @@ const ( type hidDevicer interface { Handler() bool + RxHandler([]byte) bool } var devices [5]hidDevicer @@ -65,6 +66,17 @@ func handler() { } } +func rxHandler(b []byte) { + for _, d := range devices { + if d == nil { + continue + } + if done := d.RxHandler(b); done { + return + } + } +} + var DefaultSetupHandler = setupHandler func setupHandler(setup usb.Setup) bool { diff --git a/src/machine/usb/hid/keyboard/keyboard.go b/src/machine/usb/hid/keyboard/keyboard.go index 6a5bad647b..d39321410b 100644 --- a/src/machine/usb/hid/keyboard/keyboard.go +++ b/src/machine/usb/hid/keyboard/keyboard.go @@ -91,6 +91,10 @@ func (kb *keyboard) Handler() bool { return false } +func (kb *keyboard) RxHandler(b []byte) bool { + return false +} + func (kb *keyboard) tx(b []byte) { if machine.USBDev.InitEndpointComplete { if kb.waitTxc { diff --git a/src/machine/usb/hid/mouse/mouse.go b/src/machine/usb/hid/mouse/mouse.go index d778fd2018..024c1e9c3a 100644 --- a/src/machine/usb/hid/mouse/mouse.go +++ b/src/machine/usb/hid/mouse/mouse.go @@ -57,6 +57,10 @@ func (m *mouse) Handler() bool { return false } +func (m *mouse) RxHandler(b []byte) bool { + return false +} + func (m *mouse) tx(b []byte) { if machine.USBDev.InitEndpointComplete { if m.waitTxc { From 215dd3f0be095b05dcff7b718ff1ff42e8c1e026 Mon Sep 17 00:00:00 2001 From: sago35 Date: Thu, 3 Aug 2023 20:43:04 +0900 Subject: [PATCH 41/75] machine/usb/hid: rename Handler() to TxHandler() --- src/machine/usb/hid/hid.go | 8 ++++---- src/machine/usb/hid/keyboard/keyboard.go | 2 +- src/machine/usb/hid/mouse/mouse.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/machine/usb/hid/hid.go b/src/machine/usb/hid/hid.go index e3948dc410..f7f1ff66bf 100644 --- a/src/machine/usb/hid/hid.go +++ b/src/machine/usb/hid/hid.go @@ -23,7 +23,7 @@ const ( ) type hidDevicer interface { - Handler() bool + TxHandler() bool RxHandler([]byte) bool } @@ -40,7 +40,7 @@ func SetHandler(d hidDevicer) { Index: usb.HID_ENDPOINT_IN, IsIn: true, Type: usb.ENDPOINT_TYPE_INTERRUPT, - TxHandler: handler, + TxHandler: txHandler, }, }, []usb.SetupConfig{ @@ -55,12 +55,12 @@ func SetHandler(d hidDevicer) { size++ } -func handler() { +func txHandler() { for _, d := range devices { if d == nil { continue } - if done := d.Handler(); done { + if done := d.TxHandler(); done { return } } diff --git a/src/machine/usb/hid/keyboard/keyboard.go b/src/machine/usb/hid/keyboard/keyboard.go index d39321410b..237d79419e 100644 --- a/src/machine/usb/hid/keyboard/keyboard.go +++ b/src/machine/usb/hid/keyboard/keyboard.go @@ -81,7 +81,7 @@ func newKeyboard() *keyboard { } } -func (kb *keyboard) Handler() bool { +func (kb *keyboard) TxHandler() bool { kb.waitTxc = false if b, ok := kb.buf.Get(); ok { kb.waitTxc = true diff --git a/src/machine/usb/hid/mouse/mouse.go b/src/machine/usb/hid/mouse/mouse.go index 024c1e9c3a..d790bdbb83 100644 --- a/src/machine/usb/hid/mouse/mouse.go +++ b/src/machine/usb/hid/mouse/mouse.go @@ -47,7 +47,7 @@ func newMouse() *mouse { } } -func (m *mouse) Handler() bool { +func (m *mouse) TxHandler() bool { m.waitTxc = false if b, ok := m.buf.Get(); ok { m.waitTxc = true From c25dd0a972a1d68b4d154831a6d196e28f17ddad Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 26 Jul 2023 13:23:44 +0200 Subject: [PATCH 42/75] testing: add Testing function This is new in Go 1.21. --- builder/build.go | 14 +++++++++++--- src/testing/testing.go | 9 +++++++++ src/testing/testing_test.go | 6 ++++++ testdata/testing.go | 3 +++ 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/builder/build.go b/builder/build.go index 2e8577e377..85114d9d72 100644 --- a/builder/build.go +++ b/builder/build.go @@ -217,19 +217,27 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe var packageJobs []*compileJob packageActionIDJobs := make(map[string]*compileJob) + if config.Options.GlobalValues == nil { + config.Options.GlobalValues = make(map[string]map[string]string) + } if config.Options.GlobalValues["runtime"]["buildVersion"] == "" { version := goenv.Version if strings.HasSuffix(goenv.Version, "-dev") && goenv.GitSha1 != "" { version += "-" + goenv.GitSha1 } - if config.Options.GlobalValues == nil { - config.Options.GlobalValues = make(map[string]map[string]string) - } if config.Options.GlobalValues["runtime"] == nil { config.Options.GlobalValues["runtime"] = make(map[string]string) } config.Options.GlobalValues["runtime"]["buildVersion"] = version } + if config.TestConfig.CompileTestBinary { + // The testing.testBinary is set to "1" when in a test. + // This is needed for testing.Testing() to work correctly. + if config.Options.GlobalValues["testing"] == nil { + config.Options.GlobalValues["testing"] = make(map[string]string) + } + config.Options.GlobalValues["testing"]["testBinary"] = "1" + } var embedFileObjects []*compileJob for _, pkg := range lprogram.Sorted() { diff --git a/src/testing/testing.go b/src/testing/testing.go index fee9d40b19..8429e92212 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -120,6 +120,15 @@ func Verbose() bool { return flagVerbose } +// String constant that is being set when running a test. +var testBinary string + +// Testing returns whether the program was compiled as a test, using "tinygo +// test". It returns false when built using "tinygo build", "tinygo flash", etc. +func Testing() bool { + return testBinary == "1" +} + // flushToParent writes c.output to the parent after first writing the header // with the given format and arguments. func (c *common) flushToParent(testName, format string, args ...interface{}) { diff --git a/src/testing/testing_test.go b/src/testing/testing_test.go index 8a2586535e..631a313414 100644 --- a/src/testing/testing_test.go +++ b/src/testing/testing_test.go @@ -195,3 +195,9 @@ func TestSetenv(t *testing.T) { } } } + +func TestTesting(t *testing.T) { + if !testing.Testing() { + t.Error("Expected testing.Testing() to return true while in a test") + } +} diff --git a/testdata/testing.go b/testdata/testing.go index 4c1cf44efe..ff378fea95 100644 --- a/testdata/testing.go +++ b/testdata/testing.go @@ -73,6 +73,9 @@ func fakeMatchString(pat, str string) (bool, error) { } func main() { + if testing.Testing() { + println("not running a test at the moment, testing.Testing() should return false") + } testing.Init() flag.Set("test.run", ".*/B") m := testing.MainStart(matchStringOnly(fakeMatchString /*regexp.MatchString*/), tests, benchmarks, fuzzes, examples) From a93f0ed12a30d694ad8db2a2636b1247e772c3e8 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 6 Jul 2023 12:01:52 +0200 Subject: [PATCH 43/75] all: Go 1.21 support --- .github/workflows/build-macos.yml | 4 ++-- .github/workflows/linux.yml | 10 +++++----- .github/workflows/windows.yml | 8 ++++---- builder/config.go | 2 +- compiler/symbol.go | 2 +- go.mod | 2 ++ go.sum | 4 ++-- src/internal/bytealg/bytealg.go | 10 ++++++++++ src/runtime/baremetal.go | 2 ++ src/runtime/metrics.go | 10 ++++++++++ src/runtime/runtime.go | 24 ++++++++++++++++++++++++ src/runtime/runtime_nintendoswitch.go | 2 +- src/runtime/runtime_wasm_js.go | 4 ++-- src/runtime/symtab.go | 5 ++++- src/syscall/syscall_libc_darwin.go | 1 + targets/wasm_exec.js | 6 +++++- 16 files changed, 76 insertions(+), 20 deletions(-) create mode 100644 src/runtime/metrics.go diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 51c145f337..2acf285a51 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -27,7 +27,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21.0-rc.4' cache: true - name: Restore LLVM source cache uses: actions/cache/restore@v3 @@ -126,7 +126,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21.0-rc.4' cache: true - name: Build TinyGo run: go install diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 05199e21ec..1e809bf84c 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -18,7 +18,7 @@ jobs: # statically linked binary. runs-on: ubuntu-latest container: - image: golang:1.20-alpine + image: golang:1.21rc4-alpine steps: - name: Install apk dependencies # tar: needed for actions/cache@v3 @@ -135,7 +135,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21.0-rc.4' cache: true - name: Install wasmtime run: | @@ -177,7 +177,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21.0-rc.4' cache: true - name: Install Node.js uses: actions/setup-node@v3 @@ -290,7 +290,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21.0-rc.4' cache: true - name: Restore LLVM source cache uses: actions/cache/restore@v3 @@ -407,7 +407,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21.0-rc.4' cache: true - name: Restore LLVM source cache uses: actions/cache/restore@v3 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 55e752d707..606394fc9f 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -35,7 +35,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21.0-rc.4' cache: true - name: Restore cached LLVM source uses: actions/cache/restore@v3 @@ -143,7 +143,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21.0-rc.4' cache: true - name: Download TinyGo build uses: actions/download-artifact@v2 @@ -173,7 +173,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21.0-rc.4' cache: true - name: Download TinyGo build uses: actions/download-artifact@v2 @@ -209,7 +209,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21.0-rc.4' cache: true - name: Download TinyGo build uses: actions/download-artifact@v2 diff --git a/builder/config.go b/builder/config.go index 1ca3aa6414..82e8cb94e6 100644 --- a/builder/config.go +++ b/builder/config.go @@ -27,7 +27,7 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) { if err != nil { return nil, err } - if major != 1 || minor < 18 || minor > 20 { + if major != 1 || minor < 18 || minor > 21 { // Note: when this gets updated, also update the Go compatibility matrix: // https://github.com/tinygo-org/tinygo-site/blob/dev/content/docs/reference/go-compat-matrix.md return nil, fmt.Errorf("requires go version 1.18 through 1.20, got go%d.%d", major, minor) diff --git a/compiler/symbol.go b/compiler/symbol.go index 3e782dae88..7e712fbae8 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -348,7 +348,7 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { // The list of allowed types is based on this proposal: // https://github.com/golang/go/issues/59149 func (c *compilerContext) checkWasmImport(f *ssa.Function, pragma string) { - if c.pkg.Path() == "runtime" { + if c.pkg.Path() == "runtime" || c.pkg.Path() == "syscall/js" { // The runtime is a special case. Allow all kinds of parameters // (importantly, including pointers). return diff --git a/go.mod b/go.mod index 3aabc46aed..238f474e6a 100644 --- a/go.mod +++ b/go.mod @@ -31,3 +31,5 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.12 // indirect ) + +replace go.bug.st/serial => github.com/deadprogram/go-serial v0.0.0-20230717164825-4529b3232919 diff --git a/go.sum b/go.sum index 78a7ea867d..6c682c1cb4 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moA github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/deadprogram/go-serial v0.0.0-20230717164825-4529b3232919 h1:Hi7G1bCG70NwlyqGswJKEHoIi4hJVN1SsmfwZ+DQHtw= +github.com/deadprogram/go-serial v0.0.0-20230717164825-4529b3232919/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= @@ -46,8 +48,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs= github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -go.bug.st/serial v1.3.5 h1:k50SqGZCnHZ2MiBQgzccXWG+kd/XpOs1jUljpDDKzaE= -go.bug.st/serial v1.3.5/go.mod h1:z8CesKorE90Qr/oRSJiEuvzYRKol9r/anJZEb5kt304= golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/src/internal/bytealg/bytealg.go b/src/internal/bytealg/bytealg.go index a6744c4144..72be9ac587 100644 --- a/src/internal/bytealg/bytealg.go +++ b/src/internal/bytealg/bytealg.go @@ -251,3 +251,13 @@ func IndexRabinKarp(s, substr string) int { } return -1 } + +// MakeNoZero makes a slice of length and capacity n without zeroing the bytes. +// It is the caller's responsibility to ensure uninitialized bytes +// do not leak to the end user. +func MakeNoZero(n int) []byte { + // Note: this does zero the buffer even though that's not necessary. + // For performance reasons we might want to change this (similar to the + // malloc function implemented in the runtime). + return make([]byte, n) +} diff --git a/src/runtime/baremetal.go b/src/runtime/baremetal.go index a51191bb95..173d0db25e 100644 --- a/src/runtime/baremetal.go +++ b/src/runtime/baremetal.go @@ -38,6 +38,8 @@ func growHeap() bool { //export malloc func libc_malloc(size uintptr) unsafe.Pointer { + // Note: this zeroes the returned buffer which is not necessary. + // The same goes for bytealg.MakeNoZero. return alloc(size, nil) } diff --git a/src/runtime/metrics.go b/src/runtime/metrics.go new file mode 100644 index 0000000000..bd07777db6 --- /dev/null +++ b/src/runtime/metrics.go @@ -0,0 +1,10 @@ +package runtime + +// Implementation of functions needed by runtime/metrics. +// Mostly just dummy implementations: we don't currently use any of these +// metrics. + +//go:linkname godebug_registerMetric internal/godebug.registerMetric +func godebug_registerMetric(name string, read func() uint64) { + // Dummy function for compatibility with Go 1.21. +} diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go index ceb6a2240c..ac7cd25c93 100644 --- a/src/runtime/runtime.go +++ b/src/runtime/runtime.go @@ -100,3 +100,27 @@ func godebug_setUpdate(update func(string, string)) { // variable changes (for example, via os.Setenv). godebugUpdate = update } + +//go:linkname godebug_setNewIncNonDefault internal/godebug.setNewIncNonDefault +func godebug_setNewIncNonDefault(newIncNonDefault func(string) func()) { + // Dummy function necessary in Go 1.21. +} + +// Write to the given file descriptor. +// This is called from internal/godebug starting with Go 1.21, and only seems to +// be called with the stderr file descriptor. +func write(fd uintptr, p unsafe.Pointer, n int32) int32 { + if fd == 2 { // stderr + // Convert to a string, because we know that p won't change during the + // call to printstring. + // TODO: use unsafe.String instead once we require Go 1.20. + s := _string{ + ptr: (*byte)(p), + length: uintptr(n), + } + str := *(*string)(unsafe.Pointer(&s)) + printstring(str) + return n + } + return 0 +} diff --git a/src/runtime/runtime_nintendoswitch.go b/src/runtime/runtime_nintendoswitch.go index f2606023ff..bcfa5151d0 100644 --- a/src/runtime/runtime_nintendoswitch.go +++ b/src/runtime/runtime_nintendoswitch.go @@ -105,7 +105,7 @@ func abort() { } //export write -func write(fd int32, buf *byte, count int) int { +func libc_write(fd int32, buf *byte, count int) int { // TODO: Proper handling write for i := 0; i < count; i++ { putchar(*buf) diff --git a/src/runtime/runtime_wasm_js.go b/src/runtime/runtime_wasm_js.go index c735c76fc1..443ed9e2ea 100644 --- a/src/runtime/runtime_wasm_js.go +++ b/src/runtime/runtime_wasm_js.go @@ -44,8 +44,8 @@ func nanosecondsToTicks(ns int64) timeUnit { // This function is called by the scheduler. // Schedule a call to runtime.scheduler, do not actually sleep. // -//export runtime.sleepTicks +//go:wasmimport gojs runtime.sleepTicks func sleepTicks(d timeUnit) -//export runtime.ticks +//go:wasmimport gojs runtime.ticks func ticks() timeUnit diff --git a/src/runtime/symtab.go b/src/runtime/symtab.go index cfc9885d7f..37c21ee895 100644 --- a/src/runtime/symtab.go +++ b/src/runtime/symtab.go @@ -5,11 +5,14 @@ type Frames struct { } type Frame struct { + PC uintptr + + Func *Func + Function string File string Line int - PC uintptr } func CallersFrames(callers []uintptr) *Frames { diff --git a/src/syscall/syscall_libc_darwin.go b/src/syscall/syscall_libc_darwin.go index 704ba29cae..d64f1061f3 100644 --- a/src/syscall/syscall_libc_darwin.go +++ b/src/syscall/syscall_libc_darwin.go @@ -68,6 +68,7 @@ const ( EISDIR Errno = 21 EINVAL Errno = 22 EMFILE Errno = 24 + EROFS Errno = 30 EPIPE Errno = 32 EAGAIN Errno = 35 ENOTCONN Errno = 57 diff --git a/targets/wasm_exec.js b/targets/wasm_exec.js index 8e29d6266c..5dfc67c357 100644 --- a/targets/wasm_exec.js +++ b/targets/wasm_exec.js @@ -284,7 +284,7 @@ return 0; }, }, - env: { + gojs: { // func ticks() float64 "runtime.ticks": () => { return timeOrigin + performance.now(); @@ -444,6 +444,10 @@ }, } }; + + // Go 1.20 uses 'env'. Go 1.21 uses 'gojs'. + // For compatibility, we use both as long as Go 1.20 is supported. + this.importObject.env = this.importObject.gojs; } async run(instance) { From f791c821ff5d393c875fe41889b94c19cf5f508d Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 6 Jul 2023 22:14:55 +0200 Subject: [PATCH 44/75] compiler: add min and max builtin support --- compiler/compiler.go | 18 +++++ compiler/compiler_test.go | 3 + compiler/testdata/go1.21.go | 53 +++++++++++++ compiler/testdata/go1.21.ll | 152 ++++++++++++++++++++++++++++++++++++ main_test.go | 10 +++ testdata/go1.21.go | 12 +++ testdata/go1.21.txt | 2 + 7 files changed, 250 insertions(+) create mode 100644 compiler/testdata/go1.21.go create mode 100644 compiler/testdata/go1.21.ll create mode 100644 testdata/go1.21.go create mode 100644 testdata/go1.21.txt diff --git a/compiler/compiler.go b/compiler/compiler.go index 09f11e614f..46c3a0672e 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1637,6 +1637,24 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c llvmLen = b.CreateZExt(llvmLen, b.intType, "len.int") } return llvmLen, nil + case "min", "max": + // min and max builtins, added in Go 1.21. + // We can simply reuse the existing binop comparison code, which has all + // the edge cases figured out already. + tok := token.LSS + if callName == "max" { + tok = token.GTR + } + result := argValues[0] + typ := argTypes[0] + for _, arg := range argValues[1:] { + cmp, err := b.createBinOp(tok, typ, typ, result, arg, pos) + if err != nil { + return result, err + } + result = b.CreateSelect(cmp, result, arg, "") + } + return result, nil case "print", "println": for i, value := range argValues { if i >= 1 && callName == "println" { diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 9675c40280..92ce31b012 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -54,6 +54,9 @@ func TestCompiler(t *testing.T) { if goMinor >= 20 { tests = append(tests, testCase{"go1.20.go", "", ""}) } + if goMinor >= 21 { + tests = append(tests, testCase{"go1.21.go", "", ""}) + } for _, tc := range tests { name := tc.file diff --git a/compiler/testdata/go1.21.go b/compiler/testdata/go1.21.go new file mode 100644 index 0000000000..5541b489d1 --- /dev/null +++ b/compiler/testdata/go1.21.go @@ -0,0 +1,53 @@ +package main + +func min1(a int) int { + return min(a) +} + +func min2(a, b int) int { + return min(a, b) +} + +func min3(a, b, c int) int { + return min(a, b, c) +} + +func min4(a, b, c, d int) int { + return min(a, b, c, d) +} + +func minUint8(a, b uint8) uint8 { + return min(a, b) +} + +func minUnsigned(a, b uint) uint { + return min(a, b) +} + +func minFloat32(a, b float32) float32 { + return min(a, b) +} + +func minFloat64(a, b float64) float64 { + return min(a, b) +} + +func minString(a, b string) string { + return min(a, b) +} + +func maxInt(a, b int) int { + return max(a, b) +} + +func maxUint(a, b uint) uint { + return max(a, b) +} + +func maxFloat32(a, b float32) float32 { + return max(a, b) +} + +func maxString(a, b string) string { + return max(a, b) +} diff --git a/compiler/testdata/go1.21.ll b/compiler/testdata/go1.21.ll new file mode 100644 index 0000000000..5d4a70197e --- /dev/null +++ b/compiler/testdata/go1.21.ll @@ -0,0 +1,152 @@ +; ModuleID = 'go1.21.go' +source_filename = "go1.21.go" +target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20" +target triple = "wasm32-unknown-wasi" + +%runtime._string = type { ptr, i32 } + +; Function Attrs: allockind("alloc,zeroed") allocsize(0) +declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 + +declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1 + +; Function Attrs: nounwind +define hidden void @main.init(ptr %context) unnamed_addr #2 { +entry: + ret void +} + +; Function Attrs: nounwind +define hidden i32 @main.min1(i32 %a, ptr %context) unnamed_addr #2 { +entry: + ret i32 %a +} + +; Function Attrs: nounwind +define hidden i32 @main.min2(i32 %a, i32 %b, ptr %context) unnamed_addr #2 { +entry: + %0 = call i32 @llvm.smin.i32(i32 %a, i32 %b) + ret i32 %0 +} + +; Function Attrs: nounwind +define hidden i32 @main.min3(i32 %a, i32 %b, i32 %c, ptr %context) unnamed_addr #2 { +entry: + %0 = call i32 @llvm.smin.i32(i32 %a, i32 %b) + %1 = call i32 @llvm.smin.i32(i32 %0, i32 %c) + ret i32 %1 +} + +; Function Attrs: nounwind +define hidden i32 @main.min4(i32 %a, i32 %b, i32 %c, i32 %d, ptr %context) unnamed_addr #2 { +entry: + %0 = call i32 @llvm.smin.i32(i32 %a, i32 %b) + %1 = call i32 @llvm.smin.i32(i32 %0, i32 %c) + %2 = call i32 @llvm.smin.i32(i32 %1, i32 %d) + ret i32 %2 +} + +; Function Attrs: nounwind +define hidden i8 @main.minUint8(i8 %a, i8 %b, ptr %context) unnamed_addr #2 { +entry: + %0 = call i8 @llvm.umin.i8(i8 %a, i8 %b) + ret i8 %0 +} + +; Function Attrs: nounwind +define hidden i32 @main.minUnsigned(i32 %a, i32 %b, ptr %context) unnamed_addr #2 { +entry: + %0 = call i32 @llvm.umin.i32(i32 %a, i32 %b) + ret i32 %0 +} + +; Function Attrs: nounwind +define hidden float @main.minFloat32(float %a, float %b, ptr %context) unnamed_addr #2 { +entry: + %0 = fcmp olt float %a, %b + %1 = select i1 %0, float %a, float %b + ret float %1 +} + +; Function Attrs: nounwind +define hidden double @main.minFloat64(double %a, double %b, ptr %context) unnamed_addr #2 { +entry: + %0 = fcmp olt double %a, %b + %1 = select i1 %0, double %a, double %b + ret double %1 +} + +; Function Attrs: nounwind +define hidden %runtime._string @main.minString(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr %context) unnamed_addr #2 { +entry: + %0 = insertvalue %runtime._string zeroinitializer, ptr %a.data, 0 + %1 = insertvalue %runtime._string %0, i32 %a.len, 1 + %2 = insertvalue %runtime._string zeroinitializer, ptr %b.data, 0 + %3 = insertvalue %runtime._string %2, i32 %b.len, 1 + %stackalloc = alloca i8, align 1 + %4 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr undef) #4 + %5 = select i1 %4, %runtime._string %1, %runtime._string %3 + %6 = extractvalue %runtime._string %5, 0 + call void @runtime.trackPointer(ptr %6, ptr nonnull %stackalloc, ptr undef) #4 + ret %runtime._string %5 +} + +declare i1 @runtime.stringLess(ptr, i32, ptr, i32, ptr) #1 + +; Function Attrs: nounwind +define hidden i32 @main.maxInt(i32 %a, i32 %b, ptr %context) unnamed_addr #2 { +entry: + %0 = call i32 @llvm.smax.i32(i32 %a, i32 %b) + ret i32 %0 +} + +; Function Attrs: nounwind +define hidden i32 @main.maxUint(i32 %a, i32 %b, ptr %context) unnamed_addr #2 { +entry: + %0 = call i32 @llvm.umax.i32(i32 %a, i32 %b) + ret i32 %0 +} + +; Function Attrs: nounwind +define hidden float @main.maxFloat32(float %a, float %b, ptr %context) unnamed_addr #2 { +entry: + %0 = fcmp ogt float %a, %b + %1 = select i1 %0, float %a, float %b + ret float %1 +} + +; Function Attrs: nounwind +define hidden %runtime._string @main.maxString(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr %context) unnamed_addr #2 { +entry: + %0 = insertvalue %runtime._string zeroinitializer, ptr %a.data, 0 + %1 = insertvalue %runtime._string %0, i32 %a.len, 1 + %2 = insertvalue %runtime._string zeroinitializer, ptr %b.data, 0 + %3 = insertvalue %runtime._string %2, i32 %b.len, 1 + %stackalloc = alloca i8, align 1 + %4 = call i1 @runtime.stringLess(ptr %b.data, i32 %b.len, ptr %a.data, i32 %a.len, ptr undef) #4 + %5 = select i1 %4, %runtime._string %1, %runtime._string %3 + %6 = extractvalue %runtime._string %5, 0 + call void @runtime.trackPointer(ptr %6, ptr nonnull %stackalloc, ptr undef) #4 + ret %runtime._string %5 +} + +; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn +declare i32 @llvm.smin.i32(i32, i32) #3 + +; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn +declare i8 @llvm.umin.i8(i8, i8) #3 + +; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn +declare i32 @llvm.umin.i32(i32, i32) #3 + +; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn +declare i32 @llvm.smax.i32(i32, i32) #3 + +; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn +declare i32 @llvm.umax.i32(i32, i32) #3 + +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } +attributes #1 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } +attributes #3 = { nocallback nofree nosync nounwind readnone speculatable willreturn } +attributes #4 = { nounwind } diff --git a/main_test.go b/main_test.go index 59254656ee..32d13f9103 100644 --- a/main_test.go +++ b/main_test.go @@ -71,6 +71,16 @@ func TestBuild(t *testing.T) { "zeroalloc.go", } + // Go 1.21 made some changes to the language, which we can only test when + // we're actually on Go 1.21. + _, minor, err := goenv.GetGorootVersion() + if err != nil { + t.Fatal("could not get version:", minor) + } + if minor >= 21 { + tests = append(tests, "go1.21.go") + } + if *testTarget != "" { // This makes it possible to run one specific test (instead of all), // which is especially useful to quickly check whether some changes diff --git a/testdata/go1.21.go b/testdata/go1.21.go new file mode 100644 index 0000000000..885e588da8 --- /dev/null +++ b/testdata/go1.21.go @@ -0,0 +1,12 @@ +package main + +func main() { + ia := 1 + ib := 5 + ic := -3 + fa := 1.0 + fb := 5.0 + fc := -3.0 + println("min/max:", min(ia, ib, ic), max(ia, ib, ic)) + println("min/max:", min(fa, fb, fc), max(fa, fb, fc)) +} diff --git a/testdata/go1.21.txt b/testdata/go1.21.txt new file mode 100644 index 0000000000..ad81dcfe96 --- /dev/null +++ b/testdata/go1.21.txt @@ -0,0 +1,2 @@ +min/max: -3 5 +min/max: -3.000000e+000 +5.000000e+000 From a2f886a67a645add0c2e42289e20f89fe402294d Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 7 Jul 2023 15:15:49 +0200 Subject: [PATCH 45/75] compiler: implement clear builtin for slices --- compiler/compiler.go | 35 ++++++++++++++++++++++++++++++++ compiler/intrinsics.go | 24 +++++++++++++--------- compiler/testdata/go1.21.go | 8 ++++++++ compiler/testdata/go1.21.ll | 40 +++++++++++++++++++++++++++---------- testdata/go1.21.go | 6 ++++++ testdata/go1.21.txt | 1 + 6 files changed, 94 insertions(+), 20 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index 46c3a0672e..6af6debe89 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1600,6 +1600,41 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c cplx = b.CreateInsertValue(cplx, r, 0, "") cplx = b.CreateInsertValue(cplx, i, 1, "") return cplx, nil + case "clear": + value := argValues[0] + switch typ := argTypes[0].Underlying().(type) { + case *types.Slice: + elementType := b.getLLVMType(typ.Elem()) + elementSize := b.targetData.TypeAllocSize(elementType) + elementAlign := b.targetData.ABITypeAlignment(elementType) + + // The pointer to the data to be cleared. + llvmBuf := b.CreateExtractValue(value, 0, "buf") + if llvmBuf.Type() != b.i8ptrType { // compatibility with LLVM 14 + llvmBuf = b.CreateBitCast(llvmBuf, b.i8ptrType, "") + } + + // The length (in bytes) to be cleared. + llvmLen := b.CreateExtractValue(value, 1, "len") + llvmLen = b.CreateMul(llvmLen, llvm.ConstInt(llvmLen.Type(), elementSize, false), "") + + // Do the clear operation using the LLVM memset builtin. + // This is also correct for nil slices: in those cases, len will be + // 0 which means the memset call is a no-op (according to the LLVM + // LangRef). + memset := b.getMemsetFunc() + call := b.createCall(memset.GlobalValueType(), memset, []llvm.Value{ + llvmBuf, // dest + llvm.ConstInt(b.ctx.Int8Type(), 0, false), // val + llvmLen, // len + llvm.ConstInt(b.ctx.Int1Type(), 0, false), // isVolatile + }, "") + call.AddCallSiteAttribute(1, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(elementAlign))) + + return llvm.Value{}, nil + default: + return llvm.Value{}, b.makeError(pos, "unsupported type in clear builtin: "+typ.String()) + } case "copy": dst := argValues[0] src := argValues[1] diff --git a/compiler/intrinsics.go b/compiler/intrinsics.go index 5761a438f1..af3a57de1b 100644 --- a/compiler/intrinsics.go +++ b/compiler/intrinsics.go @@ -70,15 +70,7 @@ func (b *builder) createMemoryCopyImpl() { // regular libc memset calls if they aren't optimized out in a different way. func (b *builder) createMemoryZeroImpl() { b.createFunctionStart(true) - fnName := "llvm.memset.p0.i" + strconv.Itoa(b.uintptrType.IntTypeWidth()) - if llvmutil.Major() < 15 { // compatibility with LLVM 14 - fnName = "llvm.memset.p0i8.i" + strconv.Itoa(b.uintptrType.IntTypeWidth()) - } - llvmFn := b.mod.NamedFunction(fnName) - if llvmFn.IsNil() { - fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType, b.ctx.Int8Type(), b.uintptrType, b.ctx.Int1Type()}, false) - llvmFn = llvm.AddFunction(b.mod, fnName, fnType) - } + llvmFn := b.getMemsetFunc() params := []llvm.Value{ b.getValue(b.fn.Params[0], getPos(b.fn)), llvm.ConstInt(b.ctx.Int8Type(), 0, false), @@ -89,6 +81,20 @@ func (b *builder) createMemoryZeroImpl() { b.CreateRetVoid() } +// Return the llvm.memset.p0.i8 function declaration. +func (c *compilerContext) getMemsetFunc() llvm.Value { + fnName := "llvm.memset.p0.i" + strconv.Itoa(c.uintptrType.IntTypeWidth()) + if llvmutil.Major() < 15 { // compatibility with LLVM 14 + fnName = "llvm.memset.p0i8.i" + strconv.Itoa(c.uintptrType.IntTypeWidth()) + } + llvmFn := c.mod.NamedFunction(fnName) + if llvmFn.IsNil() { + fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType, c.ctx.Int8Type(), c.uintptrType, c.ctx.Int1Type()}, false) + llvmFn = llvm.AddFunction(c.mod, fnName, fnType) + } + return llvmFn +} + // createKeepAlive creates the runtime.KeepAlive function. It is implemented // using inline assembly. func (b *builder) createKeepAliveImpl() { diff --git a/compiler/testdata/go1.21.go b/compiler/testdata/go1.21.go index 5541b489d1..3d92b69b17 100644 --- a/compiler/testdata/go1.21.go +++ b/compiler/testdata/go1.21.go @@ -51,3 +51,11 @@ func maxFloat32(a, b float32) float32 { func maxString(a, b string) string { return max(a, b) } + +func clearSlice(s []int) { + clear(s) +} + +func clearZeroSizedSlice(s []struct{}) { + clear(s) +} diff --git a/compiler/testdata/go1.21.ll b/compiler/testdata/go1.21.ll index 5d4a70197e..73a3683839 100644 --- a/compiler/testdata/go1.21.ll +++ b/compiler/testdata/go1.21.ll @@ -84,10 +84,10 @@ entry: %2 = insertvalue %runtime._string zeroinitializer, ptr %b.data, 0 %3 = insertvalue %runtime._string %2, i32 %b.len, 1 %stackalloc = alloca i8, align 1 - %4 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr undef) #4 + %4 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr undef) #5 %5 = select i1 %4, %runtime._string %1, %runtime._string %3 %6 = extractvalue %runtime._string %5, 0 - call void @runtime.trackPointer(ptr %6, ptr nonnull %stackalloc, ptr undef) #4 + call void @runtime.trackPointer(ptr %6, ptr nonnull %stackalloc, ptr undef) #5 ret %runtime._string %5 } @@ -123,30 +123,48 @@ entry: %2 = insertvalue %runtime._string zeroinitializer, ptr %b.data, 0 %3 = insertvalue %runtime._string %2, i32 %b.len, 1 %stackalloc = alloca i8, align 1 - %4 = call i1 @runtime.stringLess(ptr %b.data, i32 %b.len, ptr %a.data, i32 %a.len, ptr undef) #4 + %4 = call i1 @runtime.stringLess(ptr %b.data, i32 %b.len, ptr %a.data, i32 %a.len, ptr undef) #5 %5 = select i1 %4, %runtime._string %1, %runtime._string %3 %6 = extractvalue %runtime._string %5, 0 - call void @runtime.trackPointer(ptr %6, ptr nonnull %stackalloc, ptr undef) #4 + call void @runtime.trackPointer(ptr %6, ptr nonnull %stackalloc, ptr undef) #5 ret %runtime._string %5 } +; Function Attrs: nounwind +define hidden void @main.clearSlice(ptr %s.data, i32 %s.len, i32 %s.cap, ptr %context) unnamed_addr #2 { +entry: + %0 = shl i32 %s.len, 2 + call void @llvm.memset.p0.i32(ptr align 4 %s.data, i8 0, i32 %0, i1 false) + ret void +} + +; Function Attrs: argmemonly nocallback nofree nounwind willreturn writeonly +declare void @llvm.memset.p0.i32(ptr nocapture writeonly, i8, i32, i1 immarg) #3 + +; Function Attrs: nounwind +define hidden void @main.clearZeroSizedSlice(ptr %s.data, i32 %s.len, i32 %s.cap, ptr %context) unnamed_addr #2 { +entry: + ret void +} + ; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn -declare i32 @llvm.smin.i32(i32, i32) #3 +declare i32 @llvm.smin.i32(i32, i32) #4 ; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn -declare i8 @llvm.umin.i8(i8, i8) #3 +declare i8 @llvm.umin.i8(i8, i8) #4 ; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn -declare i32 @llvm.umin.i32(i32, i32) #3 +declare i32 @llvm.umin.i32(i32, i32) #4 ; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn -declare i32 @llvm.smax.i32(i32, i32) #3 +declare i32 @llvm.smax.i32(i32, i32) #4 ; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn -declare i32 @llvm.umax.i32(i32, i32) #3 +declare i32 @llvm.umax.i32(i32, i32) #4 attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } attributes #1 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } attributes #2 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } -attributes #3 = { nocallback nofree nosync nounwind readnone speculatable willreturn } -attributes #4 = { nounwind } +attributes #3 = { argmemonly nocallback nofree nounwind willreturn writeonly } +attributes #4 = { nocallback nofree nosync nounwind readnone speculatable willreturn } +attributes #5 = { nounwind } diff --git a/testdata/go1.21.go b/testdata/go1.21.go index 885e588da8..184bb2d8a7 100644 --- a/testdata/go1.21.go +++ b/testdata/go1.21.go @@ -1,6 +1,7 @@ package main func main() { + // The new min/max builtins. ia := 1 ib := 5 ic := -3 @@ -9,4 +10,9 @@ func main() { fc := -3.0 println("min/max:", min(ia, ib, ic), max(ia, ib, ic)) println("min/max:", min(fa, fb, fc), max(fa, fb, fc)) + + // The clear builtin, for slices. + s := []int{1, 2, 3, 4, 5} + clear(s[:3]) + println("cleared s[:3]:", s[0], s[1], s[2], s[3], s[4]) } diff --git a/testdata/go1.21.txt b/testdata/go1.21.txt index ad81dcfe96..459631a302 100644 --- a/testdata/go1.21.txt +++ b/testdata/go1.21.txt @@ -1,2 +1,3 @@ min/max: -3 5 min/max: -3.000000e+000 +5.000000e+000 +cleared s[:3]: 0 0 0 4 5 From f1e25a18d2584cda1d7b2d478e17a4358ee7daf0 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 7 Jul 2023 15:47:49 +0200 Subject: [PATCH 46/75] compiler: implement clear builtin for maps --- compiler/compiler.go | 4 ++++ compiler/map.go | 5 +++++ compiler/testdata/go1.21.go | 4 ++++ compiler/testdata/go1.21.ll | 9 +++++++++ src/runtime/hashmap.go | 29 +++++++++++++++++++++++++++++ testdata/go1.21.go | 11 +++++++++++ testdata/go1.21.txt | 2 ++ 7 files changed, 64 insertions(+) diff --git a/compiler/compiler.go b/compiler/compiler.go index 6af6debe89..6dd43935a6 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1631,6 +1631,10 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c }, "") call.AddCallSiteAttribute(1, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(elementAlign))) + return llvm.Value{}, nil + case *types.Map: + m := argValues[0] + b.createMapClear(m) return llvm.Value{}, nil default: return llvm.Value{}, b.makeError(pos, "unsupported type in clear builtin: "+typ.String()) diff --git a/compiler/map.go b/compiler/map.go index c3d4289023..21f0ee4a67 100644 --- a/compiler/map.go +++ b/compiler/map.go @@ -185,6 +185,11 @@ func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos tok } } +// Clear the given map. +func (b *builder) createMapClear(m llvm.Value) { + b.createRuntimeCall("hashmapClear", []llvm.Value{m}, "") +} + // createMapIteratorNext lowers the *ssa.Next instruction for iterating over a // map. It returns a tuple of {bool, key, value} with the result of the // iteration. diff --git a/compiler/testdata/go1.21.go b/compiler/testdata/go1.21.go index 3d92b69b17..589486d024 100644 --- a/compiler/testdata/go1.21.go +++ b/compiler/testdata/go1.21.go @@ -59,3 +59,7 @@ func clearSlice(s []int) { func clearZeroSizedSlice(s []struct{}) { clear(s) } + +func clearMap(m map[string]int) { + clear(m) +} diff --git a/compiler/testdata/go1.21.ll b/compiler/testdata/go1.21.ll index 73a3683839..d65c75f4f0 100644 --- a/compiler/testdata/go1.21.ll +++ b/compiler/testdata/go1.21.ll @@ -147,6 +147,15 @@ entry: ret void } +; Function Attrs: nounwind +define hidden void @main.clearMap(ptr dereferenceable_or_null(40) %m, ptr %context) unnamed_addr #2 { +entry: + call void @runtime.hashmapClear(ptr %m, ptr undef) #5 + ret void +} + +declare void @runtime.hashmapClear(ptr dereferenceable_or_null(40), ptr) #1 + ; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn declare i32 @llvm.smin.i32(i32, i32) #4 diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index 8a902a55d2..dfbec300ed 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -91,6 +91,35 @@ func hashmapMakeUnsafePointer(keySize, valueSize uintptr, sizeHint uintptr, alg return (unsafe.Pointer)(hashmapMake(keySize, valueSize, sizeHint, alg)) } +// Remove all entries from the map, without actually deallocating the space for +// it. This is used for the clear builtin, and can be used to reuse a map (to +// avoid extra heap allocations). +func hashmapClear(m *hashmap) { + if m == nil { + // Nothing to do. According to the spec: + // > If the map or slice is nil, clear is a no-op. + return + } + + m.count = 0 + numBuckets := uintptr(1) << m.bucketBits + bucketSize := hashmapBucketSize(m) + for i := uintptr(0); i < numBuckets; i++ { + bucket := hashmapBucketAddr(m, m.buckets, i) + for bucket != nil { + // Clear the tophash, to mark these keys/values as removed. + bucket.tophash = [8]uint8{} + + // Clear the keys and values in the bucket so that the GC won't pin + // these allocations. + memzero(unsafe.Add(unsafe.Pointer(bucket), unsafe.Sizeof(hashmapBucket{})), bucketSize-unsafe.Sizeof(hashmapBucket{})) + + // Move on to the next bucket in the chain. + bucket = bucket.next + } + } +} + func hashmapKeyEqualAlg(alg hashmapAlgorithm) func(x, y unsafe.Pointer, n uintptr) bool { switch alg { case hashmapAlgorithmBinary: diff --git a/testdata/go1.21.go b/testdata/go1.21.go index 184bb2d8a7..603bd06e27 100644 --- a/testdata/go1.21.go +++ b/testdata/go1.21.go @@ -15,4 +15,15 @@ func main() { s := []int{1, 2, 3, 4, 5} clear(s[:3]) println("cleared s[:3]:", s[0], s[1], s[2], s[3], s[4]) + + // The clear builtin, for maps. + m := map[int]string{ + 1: "one", + 2: "two", + 3: "three", + } + clear(m) + println("cleared map:", m[1], m[2], m[3], len(m)) + m[4] = "four" + println("added to cleared map:", m[1], m[2], m[3], m[4], len(m)) } diff --git a/testdata/go1.21.txt b/testdata/go1.21.txt index 459631a302..3edfdb4568 100644 --- a/testdata/go1.21.txt +++ b/testdata/go1.21.txt @@ -1,3 +1,5 @@ min/max: -3 5 min/max: -3.000000e+000 +5.000000e+000 cleared s[:3]: 0 0 0 4 5 +cleared map: 0 +added to cleared map: four 1 From 62294feb5609e6e39ec09e21469fe166dfbc55e1 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 7 Jul 2023 15:21:23 +0200 Subject: [PATCH 47/75] compiler: improve panic message when a runtime call is unavailable This should not happen under normal circumstances. It can still happen when there is a mismatch between TinyGo version and the associated runtime, or while developing the compiler package. --- compiler/calls.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/calls.go b/compiler/calls.go index 65a69fea3c..a110addcf6 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -36,7 +36,11 @@ const ( // createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or // createRuntimeInvoke instead. func (b *builder) createRuntimeCallCommon(fnName string, args []llvm.Value, name string, isInvoke bool) llvm.Value { - fn := b.program.ImportedPackage("runtime").Members[fnName].(*ssa.Function) + member := b.program.ImportedPackage("runtime").Members[fnName] + if member == nil { + panic("unknown runtime call: " + fnName) + } + fn := member.(*ssa.Function) fnType, llvmFn := b.getFunction(fn) if llvmFn.IsNil() { panic("trying to call non-existent function: " + fn.RelString(nil)) From 67ec722a741307cc9565251fd5a18765e79c5977 Mon Sep 17 00:00:00 2001 From: sago35 Date: Fri, 4 Aug 2023 21:24:09 +0900 Subject: [PATCH 48/75] board: add AKIZUKI DENSHI AE-RP2040 --- Makefile | 2 + src/machine/board_ae_rp2040.go | 111 +++++++++++++++++++++++++++++++++ targets/ae-rp2040.json | 14 +++++ 3 files changed, 127 insertions(+) create mode 100644 src/machine/board_ae_rp2040.go create mode 100644 targets/ae-rp2040.json diff --git a/Makefile b/Makefile index d1e37dd8c2..1bb84463b0 100644 --- a/Makefile +++ b/Makefile @@ -648,6 +648,8 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=gopher-badge examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=ae-rp2040 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_ae_rp2040.go b/src/machine/board_ae_rp2040.go new file mode 100644 index 0000000000..91432e4b41 --- /dev/null +++ b/src/machine/board_ae_rp2040.go @@ -0,0 +1,111 @@ +//go:build ae_rp2040 + +package machine + +import ( + "device/rp" + "runtime/interrupt" +) + +// 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 + GP29 Pin = GPIO29 + + // 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 +) + +// UART on the RP2040 +var ( + UART0 = &_UART0 + _UART0 = UART{ + Buffer: NewRingBuffer(), + Bus: rp.UART0, + } + + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: rp.UART1, + } +) + +var DefaultUART = UART0 + +func init() { + UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) + UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) +} + +// USB identifiers +const ( + usb_STRING_PRODUCT = "AE-RP2040" + usb_STRING_MANUFACTURER = "AKIZUKI DENSHI" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000A +) diff --git a/targets/ae-rp2040.json b/targets/ae-rp2040.json new file mode 100644 index 0000000000..026ea4437c --- /dev/null +++ b/targets/ae-rp2040.json @@ -0,0 +1,14 @@ +{ + "inherits": [ + "rp2040" + ], + "build-tags": ["ae_rp2040"], + "serial-port": ["2e8a:000A"], + "ldflags": [ + "--defsym=__flash_size=2048K" + ], + "extra-files": [ + "targets/pico-boot-stage2.S" + ] +} + From 72270f90529a4f138517ce6c5a15095b0c625079 Mon Sep 17 00:00:00 2001 From: Kenneth Bell Date: Mon, 7 Aug 2023 08:30:30 +0100 Subject: [PATCH 49/75] all: use https for renesas submodule #3856 --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index bcb76966be..857acaf46d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -33,4 +33,4 @@ url = https://github.com/aykevl/macos-minimal-sdk.git [submodule "lib/renesas-svd"] path = lib/renesas-svd - url = git@github.com:tinygo-org/renesas-svd.git + url = https://github.com/tinygo-org/renesas-svd.git From 0ef86e1534d15f2b291dc2512c9c877138c0887f Mon Sep 17 00:00:00 2001 From: Unrud Date: Sat, 5 Aug 2023 15:23:20 +0200 Subject: [PATCH 50/75] Add support for HID Keyboard LEDs --- src/machine/usb/descriptor/hid.go | 14 ++++++++++++-- src/machine/usb/descriptor/hidreport.go | 7 +++++++ src/machine/usb/hid/hid.go | 6 ++++++ src/machine/usb/hid/keyboard/keyboard.go | 15 +++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/machine/usb/descriptor/hid.go b/src/machine/usb/descriptor/hid.go index 8135c2a608..cdd4fc7e57 100644 --- a/src/machine/usb/descriptor/hid.go +++ b/src/machine/usb/descriptor/hid.go @@ -26,7 +26,7 @@ var interfaceHID = [interfaceTypeLen]byte{ TypeInterface, 0x02, // InterfaceNumber 0x00, // AlternateSetting - 0x01, // NumEndpoints + 0x02, // NumEndpoints 0x03, // InterfaceClass 0x00, // InterfaceSubClass 0x00, // InterfaceProtocol @@ -103,7 +103,7 @@ var classHID = [ClassHIDTypeLen]byte{ 0x00, // CountryCode 0x01, // NumDescriptors 0x22, // ClassType - 0x7E, // ClassLength L + 0x90, // ClassLength L 0x00, // ClassLength H } @@ -128,6 +128,7 @@ var CDCHID = Descriptor{ InterfaceHID.Bytes(), ClassHID.Bytes(), EndpointEP4IN.Bytes(), + EndpointEP5OUT.Bytes(), }), HID: map[uint16][]byte{ 2: Append([][]byte{ @@ -147,6 +148,15 @@ var CDCHID = Descriptor{ HIDReportCount(1), HIDReportSize(8), HIDInputConstVarAbs, + HIDReportCount(3), + HIDReportSize(1), + HIDUsagePageLED, + HIDUsageMinimum(1), + HIDUsageMaximum(3), + HIDOutputDataVarAbs, + HIDReportCount(5), + HIDReportSize(1), + HIDOutputConstVarAbs, HIDReportCount(6), HIDReportSize(8), HIDLogicalMinimum(0), diff --git a/src/machine/usb/descriptor/hidreport.go b/src/machine/usb/descriptor/hidreport.go index 0d90d47c0a..5819f6ad69 100644 --- a/src/machine/usb/descriptor/hidreport.go +++ b/src/machine/usb/descriptor/hidreport.go @@ -13,6 +13,7 @@ const ( hidUnit = 0x65 hidCollection = 0xa1 hidInput = 0x81 + hidOutput = 0x91 hidReportSize = 0x75 hidReportCount = 0x95 hidReportID = 0x85 @@ -121,6 +122,12 @@ var ( // Input (Data, Variable, Relative), 2 position bytes (X & Y) HIDInputDataVarRel = []byte{hidInput, 0x06} + + // Output (Data, Variable, Absolute), Modifier byte + HIDOutputDataVarAbs = []byte{hidOutput, 0x02} + + // Output (Const, Variable, Absolute), Modifier byte + HIDOutputConstVarAbs = []byte{hidOutput, 0x03} ) func HIDReportSize(size int) []byte { diff --git a/src/machine/usb/hid/hid.go b/src/machine/usb/hid/hid.go index f7f1ff66bf..791fd06a66 100644 --- a/src/machine/usb/hid/hid.go +++ b/src/machine/usb/hid/hid.go @@ -36,6 +36,12 @@ func SetHandler(d hidDevicer) { if size == 0 { machine.ConfigureUSBEndpoint(descriptor.CDCHID, []usb.EndpointConfig{ + { + Index: usb.HID_ENDPOINT_OUT, + IsIn: false, + Type: usb.ENDPOINT_TYPE_INTERRUPT, + RxHandler: rxHandler, + }, { Index: usb.HID_ENDPOINT_IN, IsIn: true, diff --git a/src/machine/usb/hid/keyboard/keyboard.go b/src/machine/usb/hid/keyboard/keyboard.go index 237d79419e..9f5f420ed0 100644 --- a/src/machine/usb/hid/keyboard/keyboard.go +++ b/src/machine/usb/hid/keyboard/keyboard.go @@ -92,6 +92,9 @@ func (kb *keyboard) TxHandler() bool { } func (kb *keyboard) RxHandler(b []byte) bool { + if len(b) >= 2 && b[0] == 2 /* ReportID */ { + kb.led = b[1] + } return false } @@ -106,6 +109,18 @@ func (kb *keyboard) tx(b []byte) { } } +func (kb *keyboard) NumLockLed() bool { + return kb.led&1 != 0 +} + +func (kb *keyboard) CapsLockLed() bool { + return kb.led&2 != 0 +} + +func (kb *keyboard) ScrollLockLed() bool { + return kb.led&4 != 0 +} + func (kb *keyboard) ready() bool { return true } From 253dbe335a0be87801b58b9df290c2e4d44a36f8 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 9 Aug 2023 12:01:19 +0200 Subject: [PATCH 51/75] builder: update message for max supported Go version Signed-off-by: deadprogram --- builder/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/config.go b/builder/config.go index 82e8cb94e6..d5c8166c99 100644 --- a/builder/config.go +++ b/builder/config.go @@ -30,7 +30,7 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) { if major != 1 || minor < 18 || minor > 21 { // Note: when this gets updated, also update the Go compatibility matrix: // https://github.com/tinygo-org/tinygo-site/blob/dev/content/docs/reference/go-compat-matrix.md - return nil, fmt.Errorf("requires go version 1.18 through 1.20, got go%d.%d", major, minor) + return nil, fmt.Errorf("requires go version 1.18 through 1.21, got go%d.%d", major, minor) } clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT")) From ab64e215dda90243d8ae93201921b2bf7a6c9eec Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 9 Aug 2023 11:54:26 +0200 Subject: [PATCH 52/75] build: switch GH actions builds to use Go 1.21 final release Signed-off-by: deadprogram --- .github/workflows/build-macos.yml | 4 ++-- .github/workflows/linux.yml | 10 +++++----- .github/workflows/windows.yml | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 2acf285a51..487c22ff3e 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -27,7 +27,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.21.0-rc.4' + go-version: '1.21' cache: true - name: Restore LLVM source cache uses: actions/cache/restore@v3 @@ -126,7 +126,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.21.0-rc.4' + go-version: '1.21' cache: true - name: Build TinyGo run: go install diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 1e809bf84c..13608a8414 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.21rc4-alpine + image: golang:1.21-alpine steps: - name: Install apk dependencies # tar: needed for actions/cache@v3 @@ -135,7 +135,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.21.0-rc.4' + go-version: '1.21' cache: true - name: Install wasmtime run: | @@ -177,7 +177,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.21.0-rc.4' + go-version: '1.21' cache: true - name: Install Node.js uses: actions/setup-node@v3 @@ -290,7 +290,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.21.0-rc.4' + go-version: '1.21' cache: true - name: Restore LLVM source cache uses: actions/cache/restore@v3 @@ -407,7 +407,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.21.0-rc.4' + go-version: '1.21' cache: true - name: Restore LLVM source cache uses: actions/cache/restore@v3 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 606394fc9f..4c4b59fcfe 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -35,7 +35,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.21.0-rc.4' + go-version: '1.21' cache: true - name: Restore cached LLVM source uses: actions/cache/restore@v3 @@ -143,7 +143,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.21.0-rc.4' + go-version: '1.21' cache: true - name: Download TinyGo build uses: actions/download-artifact@v2 @@ -173,7 +173,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.21.0-rc.4' + go-version: '1.21' cache: true - name: Download TinyGo build uses: actions/download-artifact@v2 @@ -209,7 +209,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.21.0-rc.4' + go-version: '1.21' cache: true - name: Download TinyGo build uses: actions/download-artifact@v2 From 59cc7d4c424644cdc4e05e69b0744db1ec5de71e Mon Sep 17 00:00:00 2001 From: deadprogram Date: Fri, 11 Aug 2023 14:47:57 +0200 Subject: [PATCH 53/75] docker: use Go 1.21 for Docker dev container build Signed-off-by: deadprogram --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2a61a221d9..ad3b586d83 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # tinygo-llvm stage obtains the llvm source for TinyGo -FROM golang:1.20 AS tinygo-llvm +FROM golang:1.21 AS tinygo-llvm RUN apt-get update && \ apt-get install -y apt-utils make cmake clang-15 ninja-build From 37a4fa205c76bfeee48a54580c7e0b72c8cd4893 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Thu, 10 Aug 2023 13:31:34 +0200 Subject: [PATCH 54/75] modules: update to go-serial package v1.6.0 Signed-off-by: deadprogram --- go.mod | 4 +--- go.sum | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 238f474e6a..a6d051a852 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/mattn/go-colorable v0.1.8 github.com/mattn/go-tty v0.0.4 github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 - go.bug.st/serial v1.3.5 + go.bug.st/serial v1.6.0 golang.org/x/sys v0.4.0 golang.org/x/tools v0.5.1-0.20230114154351-e035d0c426c8 gopkg.in/yaml.v2 v2.4.0 @@ -31,5 +31,3 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.12 // indirect ) - -replace go.bug.st/serial => github.com/deadprogram/go-serial v0.0.0-20230717164825-4529b3232919 diff --git a/go.sum b/go.sum index 6c682c1cb4..0df04dcff5 100644 --- a/go.sum +++ b/go.sum @@ -48,6 +48,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs= github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +go.bug.st/serial v1.6.0 h1:mAbRGN4cKE2J5gMwsMHC2KQisdLRQssO9WSM+rbZJ8A= +go.bug.st/serial v1.6.0/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE= golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From bfe9ee378fd7dd1ec9b42b8ca0bfc1b3b7d3e79a Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sat, 12 Aug 2023 16:59:53 +0200 Subject: [PATCH 55/75] rp2040: move flash related functions into separate file from C imports for correct LSP. Fixes #3852 Signed-off-by: deadprogram --- src/machine/machine_rp2040_flash.go | 97 +++++++++++++++++++++++++++++ src/machine/machine_rp2040_rom.go | 84 +------------------------ 2 files changed, 100 insertions(+), 81 deletions(-) create mode 100644 src/machine/machine_rp2040_flash.go diff --git a/src/machine/machine_rp2040_flash.go b/src/machine/machine_rp2040_flash.go new file mode 100644 index 0000000000..f3d24e8e7d --- /dev/null +++ b/src/machine/machine_rp2040_flash.go @@ -0,0 +1,97 @@ +//go:build rp2040 + +package machine + +import ( + "bytes" + "unsafe" +) + +// EnterBootloader should perform a system reset in preparation +// to switch to the bootloader to flash new firmware. +func EnterBootloader() { + enterBootloader() +} + +// compile-time check for ensuring we fulfill BlockDevice interface +var _ BlockDevice = flashBlockDevice{} + +var Flash flashBlockDevice + +type flashBlockDevice struct { +} + +// ReadAt reads the given number of bytes from the block device. +func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) { + if readAddress(off) > FlashDataEnd() { + return 0, errFlashCannotReadPastEOF + } + + data := unsafe.Slice((*byte)(unsafe.Pointer(readAddress(off))), len(p)) + copy(p, data) + + return len(p), nil +} + +// WriteAt writes the given number of bytes to the block device. +// Only word (32 bits) length data can be programmed. +// If the length of p is not long enough it will be padded with 0xFF bytes. +// This method assumes that the destination is already erased. +func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { + return f.writeAt(p, off) +} + +// Size returns the number of bytes in this block device. +func (f flashBlockDevice) Size() int64 { + return int64(FlashDataEnd() - FlashDataStart()) +} + +const writeBlockSize = 1 << 8 + +// WriteBlockSize returns the block size in which data can be written to +// memory. It can be used by a client to optimize writes, non-aligned writes +// should always work correctly. +func (f flashBlockDevice) WriteBlockSize() int64 { + return writeBlockSize +} + +const eraseBlockSizeValue = 1 << 12 + +func eraseBlockSize() int64 { + return eraseBlockSizeValue +} + +// EraseBlockSize returns the smallest erasable area on this particular chip +// in bytes. This is used for the block size in EraseBlocks. +func (f flashBlockDevice) EraseBlockSize() int64 { + return eraseBlockSize() +} + +// EraseBlocks erases the given number of blocks. An implementation may +// transparently coalesce ranges of blocks into larger bundles if the chip +// supports this. The start and len parameters are in block numbers, use +// EraseBlockSize to map addresses to blocks. +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) +} + +// return the correct address to be used for reads +func readAddress(off int64) uintptr { + return FlashDataStart() + uintptr(off) +} diff --git a/src/machine/machine_rp2040_rom.go b/src/machine/machine_rp2040_rom.go index b4370c77c7..fdb05bb627 100644 --- a/src/machine/machine_rp2040_rom.go +++ b/src/machine/machine_rp2040_rom.go @@ -3,7 +3,6 @@ package machine import ( - "bytes" "runtime/interrupt" "unsafe" ) @@ -136,40 +135,14 @@ void ram_func flash_erase_blocks(uint32_t offset, size_t count) */ import "C" -// EnterBootloader should perform a system reset in preparation -// to switch to the bootloader to flash new firmware. -func EnterBootloader() { +func enterBootloader() { C.reset_usb_boot(0, 0) } // Flash related code const memoryStart = C.XIP_BASE // memory start for purpose of erase -// compile-time check for ensuring we fulfill BlockDevice interface -var _ BlockDevice = flashBlockDevice{} - -var Flash flashBlockDevice - -type flashBlockDevice struct { -} - -// ReadAt reads the given number of bytes from the block device. -func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) { - if readAddress(off) > FlashDataEnd() { - return 0, errFlashCannotReadPastEOF - } - - data := unsafe.Slice((*byte)(unsafe.Pointer(readAddress(off))), len(p)) - copy(p, data) - - return len(p), nil -} - -// WriteAt writes the given number of bytes to the block device. -// Only word (32 bits) length data can be programmed. -// If the length of p is not long enough it will be padded with 0xFF bytes. -// This method assumes that the destination is already erased. -func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { +func (f flashBlockDevice) writeAt(p []byte, off int64) (n int, err error) { if writeAddress(off)+uintptr(C.XIP_BASE) > FlashDataEnd() { return 0, errFlashCannotWritePastEOF } @@ -190,37 +163,7 @@ func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { return len(padded), nil } -// Size returns the number of bytes in this block device. -func (f flashBlockDevice) Size() int64 { - return int64(FlashDataEnd() - FlashDataStart()) -} - -const writeBlockSize = 1 << 8 - -// WriteBlockSize returns the block size in which data can be written to -// memory. It can be used by a client to optimize writes, non-aligned writes -// should always work correctly. -func (f flashBlockDevice) WriteBlockSize() int64 { - return writeBlockSize -} - -const eraseBlockSizeValue = 1 << 12 - -func eraseBlockSize() int64 { - return eraseBlockSizeValue -} - -// EraseBlockSize returns the smallest erasable area on this particular chip -// in bytes. This is used for the block size in EraseBlocks. -func (f flashBlockDevice) EraseBlockSize() int64 { - return eraseBlockSize() -} - -// EraseBlocks erases the given number of blocks. An implementation may -// transparently coalesce ranges of blocks into larger bundles if the chip -// supports this. The start and len parameters are in block numbers, use -// EraseBlockSize to map addresses to blocks. -func (f flashBlockDevice) EraseBlocks(start, length int64) error { +func (f flashBlockDevice) eraseBlocks(start, length int64) error { address := writeAddress(start * f.EraseBlockSize()) if address+uintptr(C.XIP_BASE) > FlashDataEnd() { return errFlashCannotErasePastEOF @@ -233,24 +176,3 @@ func (f flashBlockDevice) EraseBlocks(start, length 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...) -} - -// return the correct address to be used for write -func writeAddress(off int64) uintptr { - return readAddress(off) - uintptr(C.XIP_BASE) -} - -// return the correct address to be used for reads -func readAddress(off int64) uintptr { - return FlashDataStart() + uintptr(off) -} From 9037bf8bf0fd50bfffd110cbc79ea4a60ef4e781 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 11 Aug 2023 16:02:04 +0200 Subject: [PATCH 56/75] main: add target JSON file in `tinygo info` output It looks like this on my system, for example: { "target": { "llvm-target": "aarch64-unknown-linux", "cpu": "generic", "features": "+neon", "goos": "linux", "goarch": "arm64", "build-tags": [ "linux", "arm64" ], "gc": "precise", "scheduler": "tasks", "linker": "ld.lld", "rtlib": "compiler-rt", "libc": "musl", "default-stack-size": 65536, "ldflags": [ "--gc-sections" ], "extra-files": [ "src/runtime/asm_arm64.S", "src/internal/task/task_stack_arm64.S" ], "gdb": [ "gdb" ], "flash-1200-bps-reset": "false" }, "goroot": "/home/ayke/.cache/tinygo/goroot-23c311bcaa05f188affa3c42310aba343acc82562d5e5f04dea9d5b79ac35f7e", "goos": "linux", "goarch": "arm64", "goarm": "6", ... } This can be very useful while working on the automatically generated target object for example (in my case, GOOS=wasip1). --- compileopts/target.go | 78 +++++++++++++++++++++---------------------- main.go | 18 +++++----- 2 files changed, 49 insertions(+), 47 deletions(-) diff --git a/compileopts/target.go b/compileopts/target.go index 3040f9022f..7545f458c2 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -23,45 +23,45 @@ import ( // https://doc.rust-lang.org/nightly/nightly-rustc/rustc_target/spec/struct.TargetOptions.html // https://github.com/shepmaster/rust-arduino-blink-led-no-core-with-cargo/blob/master/blink/arduino.json type TargetSpec struct { - Inherits []string `json:"inherits"` - Triple string `json:"llvm-target"` - CPU string `json:"cpu"` - ABI string `json:"target-abi"` // rougly equivalent to -mabi= flag - Features string `json:"features"` - GOOS string `json:"goos"` - GOARCH string `json:"goarch"` - BuildTags []string `json:"build-tags"` - GC string `json:"gc"` - Scheduler string `json:"scheduler"` - Serial string `json:"serial"` // which serial output to use (uart, usb, none) - Linker string `json:"linker"` - RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt) - Libc string `json:"libc"` - AutoStackSize *bool `json:"automatic-stack-size"` // Determine stack size automatically at compile time. - DefaultStackSize uint64 `json:"default-stack-size"` // Default stack size if the size couldn't be determined at compile time. - CFlags []string `json:"cflags"` - LDFlags []string `json:"ldflags"` - LinkerScript string `json:"linkerscript"` - ExtraFiles []string `json:"extra-files"` - RP2040BootPatch *bool `json:"rp2040-boot-patch"` // Patch RP2040 2nd stage bootloader checksum - Emulator string `json:"emulator"` - FlashCommand string `json:"flash-command"` - GDB []string `json:"gdb"` - PortReset string `json:"flash-1200-bps-reset"` - SerialPort []string `json:"serial-port"` // serial port IDs in the form "vid:pid" - FlashMethod string `json:"flash-method"` - FlashVolume []string `json:"msd-volume-name"` - FlashFilename string `json:"msd-firmware-name"` - UF2FamilyID string `json:"uf2-family-id"` - BinaryFormat string `json:"binary-format"` - OpenOCDInterface string `json:"openocd-interface"` - OpenOCDTarget string `json:"openocd-target"` - OpenOCDTransport string `json:"openocd-transport"` - OpenOCDCommands []string `json:"openocd-commands"` - OpenOCDVerify *bool `json:"openocd-verify"` // enable verify when flashing with openocd - JLinkDevice string `json:"jlink-device"` - CodeModel string `json:"code-model"` - RelocationModel string `json:"relocation-model"` + Inherits []string `json:"inherits,omitempty"` + Triple string `json:"llvm-target,omitempty"` + CPU string `json:"cpu,omitempty"` + ABI string `json:"target-abi,omitempty"` // rougly equivalent to -mabi= flag + Features string `json:"features,omitempty"` + GOOS string `json:"goos,omitempty"` + GOARCH string `json:"goarch,omitempty"` + BuildTags []string `json:"build-tags,omitempty"` + GC string `json:"gc,omitempty"` + Scheduler string `json:"scheduler,omitempty"` + Serial string `json:"serial,omitempty"` // which serial output to use (uart, usb, none) + Linker string `json:"linker,omitempty"` + RTLib string `json:"rtlib,omitempty"` // compiler runtime library (libgcc, compiler-rt) + Libc string `json:"libc,omitempty"` + AutoStackSize *bool `json:"automatic-stack-size,omitempty"` // Determine stack size automatically at compile time. + DefaultStackSize uint64 `json:"default-stack-size,omitempty"` // Default stack size if the size couldn't be determined at compile time. + CFlags []string `json:"cflags,omitempty"` + LDFlags []string `json:"ldflags,omitempty"` + LinkerScript string `json:"linkerscript,omitempty"` + ExtraFiles []string `json:"extra-files,omitempty"` + RP2040BootPatch *bool `json:"rp2040-boot-patch,omitempty"` // Patch RP2040 2nd stage bootloader checksum + Emulator string `json:"emulator,omitempty"` + FlashCommand string `json:"flash-command,omitempty"` + GDB []string `json:"gdb,omitempty"` + PortReset string `json:"flash-1200-bps-reset,omitempty"` + SerialPort []string `json:"serial-port,omitempty"` // serial port IDs in the form "vid:pid" + FlashMethod string `json:"flash-method,omitempty"` + FlashVolume []string `json:"msd-volume-name,omitempty"` + FlashFilename string `json:"msd-firmware-name,omitempty"` + UF2FamilyID string `json:"uf2-family-id,omitempty"` + BinaryFormat string `json:"binary-format,omitempty"` + OpenOCDInterface string `json:"openocd-interface,omitempty"` + OpenOCDTarget string `json:"openocd-target,omitempty"` + OpenOCDTransport string `json:"openocd-transport,omitempty"` + OpenOCDCommands []string `json:"openocd-commands,omitempty"` + OpenOCDVerify *bool `json:"openocd-verify,omitempty"` // enable verify when flashing with openocd + JLinkDevice string `json:"jlink-device,omitempty"` + CodeModel string `json:"code-model,omitempty"` + RelocationModel string `json:"relocation-model,omitempty"` } // overrideProperties overrides all properties that are set in child into itself using reflection. diff --git a/main.go b/main.go index 7af036f173..a11c930ba4 100644 --- a/main.go +++ b/main.go @@ -1796,15 +1796,17 @@ func main() { } if flagJSON { json, _ := json.MarshalIndent(struct { - GOROOT string `json:"goroot"` - GOOS string `json:"goos"` - GOARCH string `json:"goarch"` - GOARM string `json:"goarm"` - BuildTags []string `json:"build_tags"` - GC string `json:"garbage_collector"` - Scheduler string `json:"scheduler"` - LLVMTriple string `json:"llvm_triple"` + Target *compileopts.TargetSpec `json:"target"` + GOROOT string `json:"goroot"` + GOOS string `json:"goos"` + GOARCH string `json:"goarch"` + GOARM string `json:"goarm"` + BuildTags []string `json:"build_tags"` + GC string `json:"garbage_collector"` + Scheduler string `json:"scheduler"` + LLVMTriple string `json:"llvm_triple"` }{ + Target: config.Target, GOROOT: cachedGOROOT, GOOS: config.GOOS(), GOARCH: config.GOARCH(), From 756cdf44ed5a83a0fd37a63bdf596328c279f4f7 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sat, 12 Aug 2023 12:29:28 +0200 Subject: [PATCH 57/75] loader: merge go.env file which is now required starting in Go 1.21 to correctly get required packages Signed-off-by: deadprogram --- loader/goroot.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/loader/goroot.go b/loader/goroot.go index b6812badc4..c1479b642f 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -207,6 +207,11 @@ func listGorootMergeLinks(goroot, tinygoroot string, overrides map[string]bool) merges[dir] = filepath.Join(goroot, dir) } + // Required starting in Go 1.21 due to https://github.com/golang/go/issues/61928 + if _, err := os.Stat(filepath.Join(goroot, "go.env")); err == nil { + merges["go.env"] = filepath.Join(goroot, "go.env") + } + return merges, nil } From f4375d045213c416fad2d19773e7700b6b02f685 Mon Sep 17 00:00:00 2001 From: Kenneth Bell Date: Sun, 6 Aug 2023 22:15:47 +0100 Subject: [PATCH 58/75] samd51,rp2040,nrf528xx,stm32: implement watchdog --- Makefile | 2 + src/examples/watchdog/main.go | 42 ++++++++++++++++ src/machine/machine_atsamd51.go | 49 +++++++++++++++++++ src/machine/machine_nrf528xx.go | 43 +++++++++++++++++ src/machine/machine_rp2040_clocks.go | 2 +- src/machine/machine_rp2040_watchdog.go | 65 ++++++++++++++++++++----- src/machine/machine_stm32_iwdg.go | 66 ++++++++++++++++++++++++++ src/machine/watchdog.go | 34 +++++++++++++ 8 files changed, 291 insertions(+), 12 deletions(-) create mode 100644 src/examples/watchdog/main.go create mode 100644 src/machine/machine_stm32_iwdg.go create mode 100644 src/machine/watchdog.go diff --git a/Makefile b/Makefile index 1bb84463b0..e515364975 100644 --- a/Makefile +++ b/Makefile @@ -490,6 +490,8 @@ smoketest: @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/i2c-target @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/watchdog + @$(MD5SUM) test.hex # test simulated boards on play.tinygo.org ifneq ($(WASM), 0) $(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1 diff --git a/src/examples/watchdog/main.go b/src/examples/watchdog/main.go new file mode 100644 index 0000000000..7c17c82bb7 --- /dev/null +++ b/src/examples/watchdog/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "fmt" + "machine" + "time" +) + +func main() { + //sleep for 2 secs for console + time.Sleep(2 * time.Second) + + config := machine.WatchdogConfig{ + TimeoutMillis: 1000, + } + + println("configuring watchdog for max 1 second updates") + machine.Watchdog.Configure(config) + + // From this point the watchdog is running and Update must be + // called periodically, per the config + machine.Watchdog.Start() + + // This loop should complete because watchdog update is called + // every 100ms. + start := time.Now() + println("updating watchdog for 3 seconds") + for i := 0; i < 30; i++ { + time.Sleep(100 * time.Millisecond) + machine.Watchdog.Update() + fmt.Printf("alive @ %v\n", time.Now().Sub(start)) + } + + // This loop should cause a watchdog reset after 1s since + // there is no update call. + start = time.Now() + println("entering tight loop") + for { + time.Sleep(100 * time.Millisecond) + fmt.Printf("alive @ %v\n", time.Now().Sub(start)) + } +} diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go index 7c4c4d07c7..8d37f2fa5f 100644 --- a/src/machine/machine_atsamd51.go +++ b/src/machine/machine_atsamd51.go @@ -2299,3 +2299,52 @@ func checkFlashError() error { return nil } + +// Watchdog provides access to the hardware watchdog available +// in the SAMD51. +var Watchdog = &watchdogImpl{} + +const ( + // WatchdogMaxTimeout in milliseconds (16s) + WatchdogMaxTimeout = (16384 * 1000) / 1024 // CYC16384/1024kHz +) + +type watchdogImpl struct{} + +// Configure the watchdog. +// +// This method should not be called after the watchdog is started and on +// some platforms attempting to reconfigure after starting the watchdog +// is explicitly forbidden / will not work. +func (wd *watchdogImpl) Configure(config WatchdogConfig) error { + // 1.024kHz clock + cycles := int((int64(config.TimeoutMillis) * 1024) / 1000) + + // period is expressed as a power-of-two, starting at 8 / 1024ths of a second + period := uint8(0) + cfgCycles := 8 + for cfgCycles < cycles { + period++ + cfgCycles <<= 1 + + if period >= 0xB { + break + } + } + + sam.WDT.CONFIG.Set(period << sam.WDT_CONFIG_PER_Pos) + + return nil +} + +// Starts the watchdog. +func (wd *watchdogImpl) Start() error { + sam.WDT.CTRLA.SetBits(sam.WDT_CTRLA_ENABLE) + return nil +} + +// Update the watchdog, indicating that `source` is healthy. +func (wd *watchdogImpl) Update() { + // 0xA5 = magic value (see datasheet) + sam.WDT.CLEAR.Set(0xA5) +} diff --git a/src/machine/machine_nrf528xx.go b/src/machine/machine_nrf528xx.go index 019a66cf1b..f8937aec06 100644 --- a/src/machine/machine_nrf528xx.go +++ b/src/machine/machine_nrf528xx.go @@ -214,3 +214,46 @@ func twisError(val uint32) error { return errI2CBusError } + +var ( + Watchdog = &watchdogImpl{} +) + +const ( + // WatchdogMaxTimeout in milliseconds (approx 36h) + WatchdogMaxTimeout = (0xffffffff * 1000) / 32768 +) + +type watchdogImpl struct { +} + +// Configure the watchdog. +// +// This method should not be called after the watchdog is started and on +// some platforms attempting to reconfigure after starting the watchdog +// is explicitly forbidden / will not work. +func (wd *watchdogImpl) Configure(config WatchdogConfig) error { + // 32.768kHz counter + crv := int32((int64(config.TimeoutMillis) * 32768) / 1000) + nrf.WDT.CRV.Set(uint32(crv)) + + // One source + nrf.WDT.RREN.Set(0x1) + + // Run during sleep + nrf.WDT.CONFIG.Set(nrf.WDT_CONFIG_SLEEP_Run) + + return nil +} + +// Starts the watchdog. +func (wd *watchdogImpl) Start() error { + nrf.WDT.TASKS_START.Set(nrf.WDT_TASKS_START_TASKS_START) + return nil +} + +// Update the watchdog, indicating that `source` is healthy. +func (wd *watchdogImpl) Update() { + // 0x6E524635 = magic value from datasheet + nrf.WDT.RR[0].Set(0x6E524635) +} diff --git a/src/machine/machine_rp2040_clocks.go b/src/machine/machine_rp2040_clocks.go index 093731049d..57dfa68b0b 100644 --- a/src/machine/machine_rp2040_clocks.go +++ b/src/machine/machine_rp2040_clocks.go @@ -182,7 +182,7 @@ func (clk *clock) configure(src, auxsrc, srcFreq, freq uint32) { // Must be called before any other clock function. func (clks *clocksType) init() { // Start the watchdog tick - watchdog.startTick(xoscFreq) + Watchdog.startTick(xoscFreq) // Disable resus that may be enabled from previous software clks.resus.ctrl.Set(0) diff --git a/src/machine/machine_rp2040_watchdog.go b/src/machine/machine_rp2040_watchdog.go index 6e9fb874f8..a67df80ca8 100644 --- a/src/machine/machine_rp2040_watchdog.go +++ b/src/machine/machine_rp2040_watchdog.go @@ -4,24 +4,67 @@ package machine import ( "device/rp" - "runtime/volatile" - "unsafe" ) -type watchdogType struct { - ctrl volatile.Register32 - load volatile.Register32 - reason volatile.Register32 - scratch [8]volatile.Register32 - tick volatile.Register32 +// Watchdog provides access to the hardware watchdog available +// in the RP2040. +var Watchdog = &watchdogImpl{} + +const ( + // WatchdogMaxTimeout in milliseconds (approx 8.3s). + // + // Nominal 1us per watchdog tick, 24-bit counter, + // but due to errata two ticks consumed per 1us. + // See: Errata RP2040-E1 + WatchdogMaxTimeout = (rp.WATCHDOG_LOAD_LOAD_Msk / 1000) / 2 +) + +type watchdogImpl struct { + // The value to reset the counter to on each Update + loadValue uint32 } -var watchdog = (*watchdogType)(unsafe.Pointer(rp.WATCHDOG)) +// Configure the watchdog. +// +// This method should not be called after the watchdog is started and on +// some platforms attempting to reconfigure after starting the watchdog +// is explicitly forbidden / will not work. +func (wd *watchdogImpl) Configure(config WatchdogConfig) error { + // x2 due to errata RP2040-E1 + wd.loadValue = config.TimeoutMillis * 1000 * 2 + if wd.loadValue > rp.WATCHDOG_LOAD_LOAD_Msk { + wd.loadValue = rp.WATCHDOG_LOAD_LOAD_Msk + } + + rp.WATCHDOG.CTRL.ClearBits(rp.WATCHDOG_CTRL_ENABLE) + + // Reset everything apart from ROSC and XOSC + rp.PSM.WDSEL.Set(0x0001ffff &^ (rp.PSM_WDSEL_ROSC | rp.PSM_WDSEL_XOSC)) + + // Pause watchdog during debug + rp.WATCHDOG.CTRL.SetBits(rp.WATCHDOG_CTRL_PAUSE_DBG0 | rp.WATCHDOG_CTRL_PAUSE_DBG1 | rp.WATCHDOG_CTRL_PAUSE_JTAG) + + // Load initial counter + rp.WATCHDOG.LOAD.Set(wd.loadValue) + + return nil +} + +// Starts the watchdog. +func (wd *watchdogImpl) Start() error { + rp.WATCHDOG.CTRL.SetBits(rp.WATCHDOG_CTRL_ENABLE) + return nil +} + +// Update the watchdog, indicating that the app is healthy. +func (wd *watchdogImpl) Update() { + rp.WATCHDOG.LOAD.Set(wd.loadValue) +} // startTick starts the watchdog tick. // cycles needs to be a divider that when applied to the xosc input, // produces a 1MHz clock. So if the xosc frequency is 12MHz, // this will need to be 12. -func (wd *watchdogType) startTick(cycles uint32) { - wd.tick.Set(cycles | rp.WATCHDOG_TICK_ENABLE) +func (wd *watchdogImpl) startTick(cycles uint32) { + rp.WATCHDOG.TICK.Set(cycles | rp.WATCHDOG_TICK_ENABLE) } diff --git a/src/machine/machine_stm32_iwdg.go b/src/machine/machine_stm32_iwdg.go new file mode 100644 index 0000000000..1139d54619 --- /dev/null +++ b/src/machine/machine_stm32_iwdg.go @@ -0,0 +1,66 @@ +//go:build stm32 + +package machine + +import "device/stm32" + +var ( + Watchdog = &watchdogImpl{} +) + +const ( + // WatchdogMaxTimeout in milliseconds (32.768s) + // + // Timeout is based on 12-bit counter with /256 divider on + // 32.768kHz clock. See 21.3.3 of RM0090 for table. + WatchdogMaxTimeout = ((0xfff + 1) * 256 * 1024) / 32768 +) + +const ( + // Enable access to PR, RLR and WINR registers (0x5555) + iwdgKeyEnable = 0x5555 + // Reset the watchdog value (0xAAAA) + iwdgKeyReset = 0xaaaa + // Start the watchdog (0xCCCC) + iwdgKeyStart = 0xcccc + // Divide by 256 + iwdgDiv256 = 6 +) + +type watchdogImpl struct { +} + +// Configure the watchdog. +// +// This method should not be called after the watchdog is started and on +// some platforms attempting to reconfigure after starting the watchdog +// is explicitly forbidden / will not work. +func (wd *watchdogImpl) Configure(config WatchdogConfig) error { + + // Enable configuration of IWDG + stm32.IWDG.KR.Set(iwdgKeyEnable) + + // Unconditionally divide by /256 since we don't really need accuracy + // less than 8ms + stm32.IWDG.PR.Set(iwdgDiv256) + + timeout := config.TimeoutMillis + if timeout > WatchdogMaxTimeout { + timeout = WatchdogMaxTimeout + } + + // Set reload value based on /256 divider + stm32.IWDG.RLR.Set(((config.TimeoutMillis*32768 + (256 * 1024) - 1) / (256 * 1024)) - 1) + return nil +} + +// Starts the watchdog. +func (wd *watchdogImpl) Start() error { + stm32.IWDG.KR.Set(iwdgKeyStart) + return nil +} + +// Update the watchdog, indicating that `source` is healthy. +func (wd *watchdogImpl) Update() { + stm32.IWDG.KR.Set(iwdgKeyReset) +} diff --git a/src/machine/watchdog.go b/src/machine/watchdog.go new file mode 100644 index 0000000000..d1516350d7 --- /dev/null +++ b/src/machine/watchdog.go @@ -0,0 +1,34 @@ +//go:build nrf52840 || nrf52833 || rp2040 || atsamd51 || atsame5x || stm32 + +package machine + +// WatchdogConfig holds configuration for the watchdog timer. +type WatchdogConfig struct { + // The timeout (in milliseconds) before the watchdog fires. + // + // If the requested timeout exceeds `MaxTimeout` it will be rounded + // down. + TimeoutMillis uint32 +} + +// watchdog must be implemented by any platform supporting watchdog functionality +type watchdog interface { + // Configure the watchdog. + // + // This method should not be called after the watchdog is started and on + // some platforms attempting to reconfigure after starting the watchdog + // is explicitly forbidden / will not work. + Configure(config WatchdogConfig) error + + // Starts the watchdog. + Start() error + + // Update the watchdog, indicating that the app is healthy. + Update() +} + +// Ensure required public symbols var exists and meets interface spec +var _ = watchdog(Watchdog) + +// Ensure required public constants exist +const _ = WatchdogMaxTimeout From a545f17d2ea55807de4b97cf4f52f01d5d4f1815 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 10 Aug 2023 22:36:35 +0200 Subject: [PATCH 59/75] wasm: add support for GOOS=wasip1 This adds true GOOS=wasip1 support in addition to our existing -target=wasi support. The old support for WASI isn't removed, but should be treated as deprecated and will likely be removed eventually to reduce the test burden. --- .github/workflows/linux.yml | 1 + Makefile | 4 +++ builder/builder_test.go | 1 + compileopts/target.go | 32 ++++++++++++++++++++- main_test.go | 16 +++++++++-- src/os/dir_unix.go | 2 +- src/os/dir_wasi.go | 2 +- src/os/env_unix_test.go | 2 +- src/os/exec_posix.go | 2 +- src/os/file_other.go | 2 +- src/os/file_unix.go | 2 +- src/os/getpagesize_test.go | 2 +- src/os/is_wasi_no_test.go | 2 +- src/os/is_wasi_test.go | 2 +- src/os/os_anyos_test.go | 3 +- src/os/os_chmod_test.go | 2 +- src/os/os_symlink_test.go | 2 +- src/os/read_test.go | 2 +- src/os/removeall_noat.go | 2 +- src/os/removeall_other.go | 2 +- src/os/{stat_linux.go => stat_linuxlike.go} | 6 +++- src/os/stat_other.go | 2 +- src/os/stat_unix.go | 2 +- src/os/tempfile_test.go | 2 +- src/os/types_unix.go | 2 +- src/runtime/env.go | 2 +- src/runtime/env_unix.go | 2 +- src/runtime/os_wasip1.go | 10 +++++++ src/runtime/runtime_wasm_js.go | 2 +- src/runtime/runtime_wasm_js_scheduler.go | 2 +- src/runtime/runtime_wasm_wasi.go | 2 +- src/syscall/errno_other.go | 2 +- src/syscall/file_emulated.go | 2 +- src/syscall/file_hosted.go | 2 +- src/syscall/proc_emulated.go | 2 +- src/syscall/proc_hosted.go | 2 +- src/syscall/syscall_libc.go | 2 +- src/syscall/syscall_libc_wasi.go | 2 +- src/testing/is_wasi_no_test.go | 2 +- src/testing/is_wasi_test.go | 2 +- 40 files changed, 99 insertions(+), 38 deletions(-) rename src/os/{stat_linux.go => stat_linuxlike.go} (82%) create mode 100644 src/runtime/os_wasip1.go diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 13608a8414..c5745137ea 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -153,6 +153,7 @@ jobs: tar -C ~/lib -xf tinygo.linux-amd64.tar.gz ln -s ~/lib/tinygo/bin/tinygo ~/go/bin/tinygo - run: make tinygo-test-wasi-fast + - run: make tinygo-test-wasip1-fast - run: make smoketest assert-test-linux: # Run all tests that can run on Linux, with LLVM assertions enabled to catch diff --git a/Makefile b/Makefile index e515364975..5d870f3f05 100644 --- a/Makefile +++ b/Makefile @@ -417,8 +417,12 @@ tinygo-bench-fast: # Same thing, except for wasi rather than the current platform. tinygo-test-wasi: $(TINYGO) test -target wasi $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi +tinygo-test-wasip1: + GOOS=wasip1 GOARCH=wasm $(TINYGO) test $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi tinygo-test-wasi-fast: $(TINYGO) test -target wasi $(TEST_PACKAGES_FAST) ./tests/runtime_wasi +tinygo-test-wasip1-fast: + GOOS=wasip1 GOARCH=wasm $(TINYGO) test $(TEST_PACKAGES_FAST) ./tests/runtime_wasi tinygo-bench-wasi: $(TINYGO) test -target wasi -bench . $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) tinygo-bench-wasi-fast: diff --git a/builder/builder_test.go b/builder/builder_test.go index f8bdd51341..03a33b9d49 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -60,6 +60,7 @@ 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 7545f458c2..f83464b274 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -191,12 +191,15 @@ func LoadTarget(options *Options) (*TargetSpec, error) { default: return nil, fmt.Errorf("invalid GOARM=%s, must be 5, 6, or 7", options.GOARM) } + case "wasm": + llvmarch = "wasm32" default: llvmarch = options.GOARCH } llvmvendor := "unknown" llvmos := options.GOOS - if llvmos == "darwin" { + switch llvmos { + case "darwin": // Use macosx* instead of darwin, otherwise darwin/arm64 will refer // to iOS! llvmos = "macosx10.12.0" @@ -207,6 +210,8 @@ func LoadTarget(options *Options) (*TargetSpec, error) { llvmos = "macosx11.0.0" } llvmvendor = "apple" + case "wasip1": + llvmos = "wasi" } // Target triples (which actually have four components, but are called // triples for historical reasons) have the form: @@ -277,6 +282,15 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { case "arm64": spec.CPU = "generic" spec.Features = "+neon" + case "wasm": + spec.CPU = "generic" + spec.Features = "+bulk-memory,+nontrapping-fptoint,+sign-ext" + spec.BuildTags = append(spec.BuildTags, "tinygo.wasm") + spec.CFlags = append(spec.CFlags, + "-mbulk-memory", + "-mnontrapping-fptoint", + "-msign-ext", + ) } if goos == "darwin" { spec.Linker = "ld.lld" @@ -320,6 +334,22 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { "--no-insert-timestamp", "--no-dynamicbase", ) + } else if goos == "wasip1" { + spec.GC = "" // use default GC + spec.Scheduler = "asyncify" + spec.Linker = "wasm-ld" + spec.RTLib = "compiler-rt" + spec.Libc = "wasi-libc" + spec.DefaultStackSize = 1024 * 16 // 16kB + spec.LDFlags = append(spec.LDFlags, + "--stack-first", + "--no-demangle", + ) + spec.Emulator = "wasmtime --mapdir=/tmp::{tmpDir} {}" + spec.ExtraFiles = append(spec.ExtraFiles, + "src/runtime/asm_tinygowasm.S", + "src/internal/task/task_asyncify_wasm.S", + ) } else { spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie } diff --git a/main_test.go b/main_test.go index 32d13f9103..ae7aed564d 100644 --- a/main_test.go +++ b/main_test.go @@ -34,6 +34,16 @@ var supportedLinuxArches = map[string]string{ "X86Linux": "linux/386", "ARMLinux": "linux/arm/6", "ARM64Linux": "linux/arm64", + "WASIp1": "wasip1/wasm", +} + +func init() { + major, _, _ := goenv.GetGorootVersion() + if major < 21 { + // Go 1.20 backwards compatibility. + // Should be removed once we drop support for Go 1.20. + delete(supportedLinuxArches, "WASIp1") + } } var sema = make(chan struct{}, runtime.NumCPU()) @@ -176,6 +186,8 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { t.Fatal("failed to load target spec:", err) } + isWebAssembly := options.Target == "wasi" || options.Target == "wasm" || (options.Target == "" && options.GOARCH == "wasm") + for _, name := range tests { if options.GOOS == "linux" && (options.GOARCH == "arm" || options.GOARCH == "386") { switch name { @@ -226,7 +238,7 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { runTest("env.go", options, t, []string{"first", "second"}, []string{"ENV1=VALUE1", "ENV2=VALUE2"}) }) } - if options.Target == "wasi" || options.Target == "wasm" { + if isWebAssembly { t.Run("alias.go-scheduler-none", func(t *testing.T) { t.Parallel() options := compileopts.Options(options) @@ -246,7 +258,7 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { runTest("rand.go", options, t, nil, nil) }) } - if options.Target != "wasi" && options.Target != "wasm" { + if !isWebAssembly { // The recover() builtin isn't supported yet on WebAssembly and Windows. t.Run("recover.go", func(t *testing.T) { t.Parallel() diff --git a/src/os/dir_unix.go b/src/os/dir_unix.go index f290ea38e7..baacdd68dc 100644 --- a/src/os/dir_unix.go +++ b/src/os/dir_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build linux && !baremetal && !wasi +//go:build linux && !baremetal && !wasi && !wasip1 package os diff --git a/src/os/dir_wasi.go b/src/os/dir_wasi.go index 60a941f24f..23be3950ee 100644 --- a/src/os/dir_wasi.go +++ b/src/os/dir_wasi.go @@ -6,7 +6,7 @@ // fairly similar: we use fdopendir, fdclosedir, and readdir from wasi-libc in // a similar way that the darwin code uses functions from libc. -//go:build wasi +//go:build wasi || wasip1 package os diff --git a/src/os/env_unix_test.go b/src/os/env_unix_test.go index b5f6473e14..034f481544 100644 --- a/src/os/env_unix_test.go +++ b/src/os/env_unix_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build darwin || linux +//go:build darwin || linux || wasip1 package os_test diff --git a/src/os/exec_posix.go b/src/os/exec_posix.go index 151520cca7..3ccb6963bb 100644 --- a/src/os/exec_posix.go +++ b/src/os/exec_posix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build aix || darwin || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris || windows +//go:build aix || darwin || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris || wasip1 || windows package os diff --git a/src/os/file_other.go b/src/os/file_other.go index 7e5833d353..d093e3d184 100644 --- a/src/os/file_other.go +++ b/src/os/file_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || (wasm && !wasi) +//go:build baremetal || (wasm && !wasi && !wasip1) package os diff --git a/src/os/file_unix.go b/src/os/file_unix.go index 6314db8fae..665fb0937e 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal) +//go:build darwin || (linux && !baremetal) || wasip1 // target wasi sets GOOS=linux and thus the +linux build tag, // even though it doesn't show up in "tinygo info target -wasi" diff --git a/src/os/getpagesize_test.go b/src/os/getpagesize_test.go index 705cfd47d8..80475552be 100644 --- a/src/os/getpagesize_test.go +++ b/src/os/getpagesize_test.go @@ -1,4 +1,4 @@ -//go:build windows || darwin || (linux && !baremetal) +//go:build windows || darwin || (linux && !baremetal) || wasip1 package os_test diff --git a/src/os/is_wasi_no_test.go b/src/os/is_wasi_no_test.go index a8d04b674a..aa7745085b 100644 --- a/src/os/is_wasi_no_test.go +++ b/src/os/is_wasi_no_test.go @@ -1,4 +1,4 @@ -//go:build !wasi +//go:build !wasi && !wasip1 package os_test diff --git a/src/os/is_wasi_test.go b/src/os/is_wasi_test.go index 551e000752..619b1cb64f 100644 --- a/src/os/is_wasi_test.go +++ b/src/os/is_wasi_test.go @@ -1,4 +1,4 @@ -//go:build wasi +//go:build wasi || wasip1 package os_test diff --git a/src/os/os_anyos_test.go b/src/os/os_anyos_test.go index 76bf4f368a..44606e163b 100644 --- a/src/os/os_anyos_test.go +++ b/src/os/os_anyos_test.go @@ -1,4 +1,4 @@ -//go:build windows || darwin || (linux && !baremetal) +//go:build windows || darwin || (linux && !baremetal) || wasip1 package os_test @@ -23,7 +23,6 @@ var dot = []string{ "os_test.go", "types.go", "stat_darwin.go", - "stat_linux.go", } func randomName() string { diff --git a/src/os/os_chmod_test.go b/src/os/os_chmod_test.go index af54815d84..911438d954 100644 --- a/src/os/os_chmod_test.go +++ b/src/os/os_chmod_test.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !js && !wasi +//go:build !baremetal && !js && !wasi && !wasip1 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/os_symlink_test.go b/src/os/os_symlink_test.go index f231f8d9d5..f252116f5a 100644 --- a/src/os/os_symlink_test.go +++ b/src/os/os_symlink_test.go @@ -1,4 +1,4 @@ -//go:build !windows && !baremetal && !js && !wasi +//go:build !windows && !baremetal && !js && !wasi && !wasip1 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/read_test.go b/src/os/read_test.go index 6915dfaf91..e037b23498 100644 --- a/src/os/read_test.go +++ b/src/os/read_test.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !js && !wasi +//go:build !baremetal && !js && !wasi && !wasip1 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/removeall_noat.go b/src/os/removeall_noat.go index 03339ae3bb..ae945c2497 100644 --- a/src/os/removeall_noat.go +++ b/src/os/removeall_noat.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !baremetal && !js && !wasi +//go:build !baremetal && !js && !wasi && !wasip1 package os diff --git a/src/os/removeall_other.go b/src/os/removeall_other.go index a388a6c222..ec055a9875 100644 --- a/src/os/removeall_other.go +++ b/src/os/removeall_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || js || wasi +//go:build baremetal || js || wasi || wasip1 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/stat_linux.go b/src/os/stat_linuxlike.go similarity index 82% rename from src/os/stat_linux.go rename to src/os/stat_linuxlike.go index d407c5c7ec..f2ff8a5f61 100644 --- a/src/os/stat_linux.go +++ b/src/os/stat_linuxlike.go @@ -1,9 +1,13 @@ -//go:build linux && !baremetal +//go:build (linux && !baremetal) || wasip1 // 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. +// Note: this file is used for both Linux and WASI. +// Eventually it might be better to spit it up, and make the syscall constants +// match the typical WASI constants instead of the Linux-equivalents used here. + package os import ( diff --git a/src/os/stat_other.go b/src/os/stat_other.go index 162313b115..ff1bc37745 100644 --- a/src/os/stat_other.go +++ b/src/os/stat_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || (wasm && !wasi) +//go:build baremetal || (wasm && !wasi && !wasip1) // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/stat_unix.go b/src/os/stat_unix.go index 4667d96a70..54b2bb4857 100644 --- a/src/os/stat_unix.go +++ b/src/os/stat_unix.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal) +//go:build darwin || (linux && !baremetal) || wasip1 // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/tempfile_test.go b/src/os/tempfile_test.go index f25390d6c2..4b7416f4e0 100644 --- a/src/os/tempfile_test.go +++ b/src/os/tempfile_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !baremetal && !js && !wasi +//go:build !baremetal && !js && !wasi && !wasip1 package os_test diff --git a/src/os/types_unix.go b/src/os/types_unix.go index 68a4e628ad..943fc00f52 100644 --- a/src/os/types_unix.go +++ b/src/os/types_unix.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal) +//go:build darwin || (linux && !baremetal) || wasip1 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/runtime/env.go b/src/runtime/env.go index dceb6bd189..72232e2f58 100644 --- a/src/runtime/env.go +++ b/src/runtime/env.go @@ -1,4 +1,4 @@ -//go:build linux || darwin || windows +//go:build linux || darwin || windows || wasip1 package runtime diff --git a/src/runtime/env_unix.go b/src/runtime/env_unix.go index e205c14d6f..42dfd5158f 100644 --- a/src/runtime/env_unix.go +++ b/src/runtime/env_unix.go @@ -1,4 +1,4 @@ -//go:build linux || darwin +//go:build linux || darwin || wasip1 package runtime diff --git a/src/runtime/os_wasip1.go b/src/runtime/os_wasip1.go new file mode 100644 index 0000000000..bc233ffe85 --- /dev/null +++ b/src/runtime/os_wasip1.go @@ -0,0 +1,10 @@ +//go:build wasip1 + +package runtime + +// The actual GOOS=wasip1, as newly added in the Go 1.21 toolchain. +// Previously we supported -target=wasi, but that was essentially faked by using +// linux/arm instead because that was the closest thing that was already +// supported in the Go standard library. + +const GOOS = "wasip1" diff --git a/src/runtime/runtime_wasm_js.go b/src/runtime/runtime_wasm_js.go index 443ed9e2ea..18ca44abec 100644 --- a/src/runtime/runtime_wasm_js.go +++ b/src/runtime/runtime_wasm_js.go @@ -1,4 +1,4 @@ -//go:build wasm && !wasi +//go:build wasm && !wasi && !wasip1 package runtime diff --git a/src/runtime/runtime_wasm_js_scheduler.go b/src/runtime/runtime_wasm_js_scheduler.go index 8f8823fef2..fc599a2a82 100644 --- a/src/runtime/runtime_wasm_js_scheduler.go +++ b/src/runtime/runtime_wasm_js_scheduler.go @@ -1,4 +1,4 @@ -//go:build wasm && !wasi && !scheduler.none +//go:build wasm && !wasi && !scheduler.none && !wasip1 package runtime diff --git a/src/runtime/runtime_wasm_wasi.go b/src/runtime/runtime_wasm_wasi.go index aec27e4c81..f258039ae6 100644 --- a/src/runtime/runtime_wasm_wasi.go +++ b/src/runtime/runtime_wasm_wasi.go @@ -1,4 +1,4 @@ -//go:build tinygo.wasm && wasi +//go:build tinygo.wasm && (wasi || wasip1) package runtime diff --git a/src/syscall/errno_other.go b/src/syscall/errno_other.go index 19822a1c6c..a001096525 100644 --- a/src/syscall/errno_other.go +++ b/src/syscall/errno_other.go @@ -1,4 +1,4 @@ -//go:build !wasi && !darwin +//go:build !wasi && !wasip1 && !darwin package syscall diff --git a/src/syscall/file_emulated.go b/src/syscall/file_emulated.go index f12d74e58e..5ed57f13b4 100644 --- a/src/syscall/file_emulated.go +++ b/src/syscall/file_emulated.go @@ -1,4 +1,4 @@ -//go:build baremetal || wasm +//go:build baremetal || (wasm && !wasip1) // This file emulates some file-related functions that are only available // under a real operating system. diff --git a/src/syscall/file_hosted.go b/src/syscall/file_hosted.go index 553322f02c..d9198a779d 100644 --- a/src/syscall/file_hosted.go +++ b/src/syscall/file_hosted.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !wasm +//go:build !(baremetal || (wasm && !wasip1)) // This file assumes there is a libc available that runs on a real operating // system. diff --git a/src/syscall/proc_emulated.go b/src/syscall/proc_emulated.go index 46570f5304..d8e7ff7a92 100644 --- a/src/syscall/proc_emulated.go +++ b/src/syscall/proc_emulated.go @@ -1,4 +1,4 @@ -//go:build baremetal || wasi || wasm +//go:build baremetal || tinygo.wasm // 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 ab35762d06..05c509e6ff 100644 --- a/src/syscall/proc_hosted.go +++ b/src/syscall/proc_hosted.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !wasi && !wasm +//go:build !baremetal && !tinygo.wasm // This file assumes there is a libc available that runs on a real operating // system. diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index ea0a000d18..7a13245b6d 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -1,4 +1,4 @@ -//go:build darwin || nintendoswitch || wasi +//go:build darwin || nintendoswitch || wasi || wasip1 package syscall diff --git a/src/syscall/syscall_libc_wasi.go b/src/syscall/syscall_libc_wasi.go index a3bd3a4872..5e6a231dff 100644 --- a/src/syscall/syscall_libc_wasi.go +++ b/src/syscall/syscall_libc_wasi.go @@ -1,4 +1,4 @@ -//go:build wasi +//go:build wasi || wasip1 package syscall diff --git a/src/testing/is_wasi_no_test.go b/src/testing/is_wasi_no_test.go index 86fab391eb..630467ec0b 100644 --- a/src/testing/is_wasi_no_test.go +++ b/src/testing/is_wasi_no_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // -//go:build !wasi +//go:build !wasi && !wasip1 package testing_test diff --git a/src/testing/is_wasi_test.go b/src/testing/is_wasi_test.go index e50af901b2..e20e15fc04 100644 --- a/src/testing/is_wasi_test.go +++ b/src/testing/is_wasi_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // -//go:build wasi +//go:build wasi || wasip1 package testing_test From 3e2471d934f7bf7f6738342cd2f63bec80a50fbf Mon Sep 17 00:00:00 2001 From: Pierre Constantineau Date: Sat, 19 Aug 2023 02:06:17 -0700 Subject: [PATCH 60/75] adding new uf2 target for PCA10056 (#3765) targets: adding new uf2 target for PCA10056 --- targets/pca10056-s140v6-uf2.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 targets/pca10056-s140v6-uf2.json diff --git a/targets/pca10056-s140v6-uf2.json b/targets/pca10056-s140v6-uf2.json new file mode 100644 index 0000000000..341a620489 --- /dev/null +++ b/targets/pca10056-s140v6-uf2.json @@ -0,0 +1,6 @@ +{ + "inherits": ["nrf52840", "nrf52840-s140v6-uf2"], + "build-tags": ["pca10056"], + "serial-port": ["239a:0x0029"], + "msd-volume-name": ["NRF52BOOT"] +} From e3bc6da9e4d37c0cfc71a47ff30ed7274253bbc7 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sat, 19 Aug 2023 09:20:33 +0200 Subject: [PATCH 61/75] make: add task to check NodeJS version before running tests Signed-off-by: deadprogram --- Makefile | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5d870f3f05..f6c052706b 100644 --- a/Makefile +++ b/Makefile @@ -267,12 +267,22 @@ lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a: @if [ ! -e lib/wasi-libc/Makefile ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi cd lib/wasi-libc && make -j4 EXTRA_CFLAGS="-O2 -g -DNDEBUG -mnontrapping-fptoint -msign-ext" MALLOC_IMPL=none CC=$(CLANG) AR=$(LLVM_AR) NM=$(LLVM_NM) +# Check for Node.js used during WASM tests. +NODEJS_VERSION := $(word 1,$(subst ., ,$(shell node -v | cut -c 2-))) +MIN_NODEJS_VERSION=16 + +.PHONY: check-nodejs-version +check-nodejs-version: +ifeq (, $(shell which node)) + @echo "Install NodeJS version 16+ to run tests."; exit 1; +endif + @if [ $(NODEJS_VERSION) -lt $(MIN_NODEJS_VERSION) ]; then echo "Install NodeJS version 16+ to run tests."; exit 1; fi # Build the Go compiler. tinygo: @if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " make llvm-source"; echo " make $(LLVM_BUILDDIR)"; exit 1; fi CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" -ldflags="-X github.com/tinygo-org/tinygo/goenv.GitSha1=`git rev-parse --short HEAD`" . -test: wasi-libc +test: wasi-libc check-nodejs-version CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=20m -buildmode exe -tags "byollvm osusergo" ./builder ./cgo ./compileopts ./compiler ./interp ./transform . # Standard library packages that pass tests on darwin, linux, wasi, and windows, but take over a minute in wasi From 806498f099835492ba18e525b446e31a26d3dce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pertti=20Erkkil=C3=A4?= Date: Thu, 24 Aug 2023 14:15:18 +0300 Subject: [PATCH 62/75] nRF52: set SPI TX/RX lengths even data is empty. Fixes #3868 (#3877) machine/hrf: Set SPI TX/RX lengths even data is empty. Fixes #3868 --- src/machine/machine_nrf52xxx.go | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/machine/machine_nrf52xxx.go b/src/machine/machine_nrf52xxx.go index c4605d5820..e35cb02010 100644 --- a/src/machine/machine_nrf52xxx.go +++ b/src/machine/machine_nrf52xxx.go @@ -288,24 +288,27 @@ func (spi SPI) Tx(w, r []byte) error { // supported. for len(r) != 0 || len(w) != 0 { // Prepare the SPI transfer: set the DMA pointers and lengths. - if len(r) != 0 { - spi.Bus.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&r[0])))) - n := uint32(len(r)) - if n > 255 { - n = 255 + // read buffer + nr := uint32(len(r)) + if nr > 0 { + if nr > 255 { + nr = 255 } - spi.Bus.RXD.MAXCNT.Set(n) - r = r[n:] + spi.Bus.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&r[0])))) + r = r[nr:] } - if len(w) != 0 { - spi.Bus.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&w[0])))) - n := uint32(len(w)) - if n > 255 { - n = 255 + spi.Bus.RXD.MAXCNT.Set(nr) + + // write buffer + nw := uint32(len(w)) + if nw > 0 { + if nw > 255 { + nw = 255 } - spi.Bus.TXD.MAXCNT.Set(n) - w = w[n:] + spi.Bus.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&w[0])))) + w = w[nw:] } + spi.Bus.TXD.MAXCNT.Set(nw) // Do the transfer. // Note: this can be improved by not waiting until the transfer is From a158d3194fab405c08fa8b819a35b6d2f8597f90 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Fri, 25 Aug 2023 10:24:48 +0200 Subject: [PATCH 63/75] all: update version for 0.29 release 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 d234fb3ded..da58358a58 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.29.0-dev" +const Version = "0.29.0" var ( // This variable is set at build time using -ldflags parameters. From dc449882ad09c60c11cef7c35914d5fbfe22a88e Mon Sep 17 00:00:00 2001 From: deadprogram Date: Fri, 25 Aug 2023 10:25:19 +0200 Subject: [PATCH 64/75] docs: update CHANGELOG for release 0.29 Signed-off-by: deadprogram --- CHANGELOG.md | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48016fd04b..2474a44a6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,74 @@ +0.29.0 +--- + +* **general** + - Go 1.21 support + - use https for renesas submodule #3856 + - ci: rename release-double-zipped to something more useful + - ci: update Node.js from version 14 to version 16 + - ci: switch GH actions builds to use Go 1.21 final release + - docker: update clang to version 15 + - docker: use Go 1.21 for Docker dev container build + - `main`: add target JSON file in `tinygo info` output + - `main`: improve detection of filesystems + - `main`: use `go env` instead of doing all detection manually + - make: add make task to generate Renesas device wrappers + - make: add task to check NodeJS version before running tests + - add submodule for Renesas SVD file mirror repo + - update to go-serial package v1.6.0 + - `testing`: add Testing function + - `tools/gen-device-svd`: small changes needed for Renesas MCUs +* **compiler** + - `builder`: update message for max supported Go version + - `compiler,reflect`: NumMethods reports exported methods only + - `compiler`: add compiler-rt and wasm symbols to table + - `compiler`: add compiler-rt to wasm.json + - `compiler`: add min and max builtin support + - `compiler`: implement clear builtin for maps + - `compiler`: implement clear builtin for slices + - `compiler`: improve panic message when a runtime call is unavailable + - `compiler`: update .ll test output + - `loader`: merge go.env file which is now required starting in Go 1.21 to correctly get required packages +* **standard library** + - `os`: define ErrNoDeadline + - `reflect`: Add FieldByNameFunc + - `reflect`: add SetZero + - `reflect`: fix iterating over maps with interface{} keys + - `reflect`: implement Value.Grow + - `reflect`: remove unecessary heap allocations + - `reflect`: use .key() instead of a type assert + - `sync`: add implementation from upstream Go for OnceFunc, OnceValue, and OnceValues +* **targets** + - `machine`: UART refactor (#3832) + - `machine/avr`: pin change interrupt + - `machine/macropad_rp2040`: add machine.BUTTON + - `machine/nrf`: add I2C timeout + - `machine/nrf`: wait for stop condition after reading from the I2C bus + - `machine/nRF52`: set SPI TX/RX lengths even data is empty. Fixes #3868 (#3877) + - `machine/rp2040`: add missing suffix to CMD_READ_STATUS + - `machine/rp2040`: add NoPin support + - `machine/rp2040`: move flash related functions into separate file from C imports for correct - LSP. Fixes #3852 + - `machine/rp2040`: wait for 1000 us after flash reset to avoid issues with busy USB bus + - `machine/samd51,rp2040,nrf528xx,stm32`: implement watchdog + - `machine/samd51`: fix i2cTimeout was decreasing due to cache activation + - `machine/usb`: Add support for HID Keyboard LEDs + - `machine/usb`: allow USB Endpoint settings to be changed externally + - `machine/usb`: refactor endpoint configuration + - `machine/usb`: remove usbDescriptorConfig + - `machine/usb/hid,joystick`: fix hidreport (3) (#3802) + - `machine/usb/hid`: add RxHandler interface + - `machine/usb/hid`: rename Handler() to TxHandler() + - `wasi`: allow zero inodes when reading directories + - `wasm`: add support for GOOS=wasip1 + - `wasm`: fix functions exported through //export + - `wasm`: remove i64 workaround, use BigInt instead + - `example`: adjust time offset + - `example`: simplify pininterrupt +* **boards** + - `targets`: add AKIZUKI DENSHI AE-RP2040 + - `targets`: adding new uf2 target for PCA10056 (#3765) + + 0.28.0 --- From 42dddd9c10392b991d93a0ede96ccb2e9985eda8 Mon Sep 17 00:00:00 2001 From: Tyler Rockwood Date: Tue, 20 Jun 2023 11:50:41 -0500 Subject: [PATCH 65/75] Repackage tinygo as an RPK plugin something quick and hacky without help args Signed-off-by: Tyler Rockwood --- main.go | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/main.go b/main.go index a11c930ba4..5d8157766e 100644 --- a/main.go +++ b/main.go @@ -1405,7 +1405,130 @@ func getListOfPackages(pkgs []string, options *compileopts.Options) ([]string, e return pkgNames, nil } +// This is a pure addition to upstream tinygo. +// +// It provides a hardcoded `build` command and autocomplete support for rpk func main() { + if len(os.Args) > 1 { + // Early command processing, before commands are interpreted by the Go flag + // library. + switch os.Args[1] { + case "clang", "ld.lld", "wasm-ld": + err := builder.RunTool(os.Args[1], os.Args[2:]...) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + os.Exit(0) + } + } + + command := "build" + + opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z") + gc := flag.String("gc", "conservative", "garbage collector to use (none, leaking, conservative)") + panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)") + scheduler := flag.String("scheduler", "none", "which scheduler to use (none, tasks, asyncify)") + work := flag.Bool("work", false, "print the name of the temporary build directory and do not delete this directory on exit") + interpTimeout := flag.Duration("interp-timeout", 180*time.Second, "interp optimization pass timeout") + var tags buildutil.TagsFlag + flag.Var(&tags, "tags", "a space-separated list of extra build tags") + target := flag.String("target", "wasi", "chip/board name or JSON target specification file") + var stackSize uint64 + flag.Func("stack-size", "goroutine stack size (if unknown at compile time)", func(s string) error { + size, err := bytesize.Parse(s) + stackSize = uint64(size) + return err + }) + printSize := flag.String("size", "", "print sizes (none, short, full)") + 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") + parallelism := flag.Int("p", runtime.GOMAXPROCS(0), "the number of build jobs that can run in parallel") + nodebug := flag.Bool("no-debug", false, "strip debug information") + programmer := flag.String("programmer", "", "which hardware programmer to use") + ldflags := flag.String("ldflags", "", "Go link tool compatible ldflags") + llvmFeatures := flag.String("llvm-features", "", "comma separated LLVM features to enable") + flagJSON := flag.Bool("json", false, "print data in JSON format") + outpath := flag.String("o", "", "output filename") + // strip the .rpk.managed-tinygo prefix + flag.CommandLine.Parse(os.Args[1:]) + globalVarValues, err := parseGoLinkFlag(*ldflags) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + var printAllocs *regexp.Regexp + if *printAllocsString != "" { + printAllocs, err = regexp.Compile(*printAllocsString) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + } + options := &compileopts.Options{ + GOOS: goenv.Get("GOOS"), + GOARCH: goenv.Get("GOARCH"), + GOARM: goenv.Get("GOARM"), + Target: *target, + StackSize: stackSize, + Opt: *opt, + GC: *gc, + PanicStrategy: *panicStrategy, + Scheduler: *scheduler, + Serial: "", + Work: *work, + InterpTimeout: *interpTimeout, + PrintIR: false, + DumpSSA: false, + VerifyIR: false, + SkipDWARF: false, + Semaphore: make(chan struct{}, *parallelism), + Debug: !*nodebug, + PrintSizes: *printSize, + PrintStacks: *printStacks, + PrintAllocs: printAllocs, + Tags: []string(tags), + TestConfig: compileopts.TestConfig{}, + GlobalValues: globalVarValues, + Programmer: *programmer, + OpenOCDCommands: nil, + LLVMFeatures: *llvmFeatures, + PrintJSON: *flagJSON, + Monitor: false, + BaudRate: 115200, + Timeout: 20 * time.Second, + } + if *printCommands { + options.PrintCommands = printCommand + } + err = options.Verify() + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + usage(command) + os.Exit(1) + } + pkgName := "." + if flag.NArg() == 1 { + pkgName = filepath.ToSlash(flag.Arg(0)) + } else if flag.NArg() > 1 { + fmt.Fprintln(os.Stderr, "build only accepts a single positional argument: package name, but multiple were specified") + usage(command) + os.Exit(1) + } + if options.Target == "" && filepath.Ext(*outpath) == ".wasm" { + options.Target = "wasm" + } + + err = Build(pkgName, *outpath, options) + handleCompilerError(err) + fmt.Println("build successful") + fmt.Println("deploy your wasm function to a topic:") + fmt.Println("\trpk wasm deploy") +} + +func upstreamMain() { if len(os.Args) < 2 { fmt.Fprintln(os.Stderr, "No command-line arguments supplied.") usage("") From 2442b64d1867a56015d0bb757261146a6a700a8e Mon Sep 17 00:00:00 2001 From: Tyler Rockwood Date: Wed, 21 Jun 2023 10:53:36 -0500 Subject: [PATCH 66/75] Add autocomplete commands Signed-off-by: Tyler Rockwood --- main.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/main.go b/main.go index 5d8157766e..5a45d07d6c 100644 --- a/main.go +++ b/main.go @@ -1405,6 +1405,24 @@ func getListOfPackages(pkgs []string, options *compileopts.Options) ([]string, e return pkgNames, nil } +func printAutocompleteHelp() { + type pluginHelp struct { + Path string `json:"path,omitempty"` + Short string `json:"short,omitempty"` + Long string `json:"long,omitempty"` + Example string `json:"example,omitempty"` + Args []string `json:"args,omitempty"` + } + buildHelp := pluginHelp{ + Path: "transform_build_tinygo", + Short: "compile a tinygo module in the current directory", + } + b, err := json.Marshal(&[]pluginHelp{buildHelp}) + if err != nil { + } + fmt.Fprintln(os.Stdout, string(b)) +} + // This is a pure addition to upstream tinygo. // // It provides a hardcoded `build` command and autocomplete support for rpk @@ -1451,8 +1469,15 @@ func main() { llvmFeatures := flag.String("llvm-features", "", "comma separated LLVM features to enable") flagJSON := flag.Bool("json", false, "print data in JSON format") outpath := flag.String("o", "", "output filename") + helpAutocomplete := flag.Bool("help-autocomplete", false, "output complete help for rpk") // strip the .rpk.managed-tinygo prefix flag.CommandLine.Parse(os.Args[1:]) + + if *helpAutocomplete { + printAutocompleteHelp() + os.Exit(0) + } + globalVarValues, err := parseGoLinkFlag(*ldflags) if err != nil { fmt.Fprintln(os.Stderr, err) From 287a2443863d2a592f03d1815521d7e8bfd42230 Mon Sep 17 00:00:00 2001 From: Tyler Rockwood Date: Thu, 22 Jun 2023 16:15:20 -0500 Subject: [PATCH 67/75] Smaller release tar Signed-off-by: Tyler Rockwood --- Makefile | 118 +++++++++++++++++++++++++++---------------------------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/Makefile b/Makefile index f6c052706b..6403902363 100644 --- a/Makefile +++ b/Makefile @@ -795,77 +795,77 @@ 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/clang/include - @mkdir -p build/release/tinygo/lib/CMSIS/CMSIS - @mkdir -p build/release/tinygo/lib/macos-minimal-sdk - @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common - @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults - @mkdir -p build/release/tinygo/lib/musl/arch - @mkdir -p build/release/tinygo/lib/musl/crt - @mkdir -p build/release/tinygo/lib/musl/src - @mkdir -p build/release/tinygo/lib/nrfx - @mkdir -p build/release/tinygo/lib/picolibc/newlib/libc - @mkdir -p build/release/tinygo/lib/picolibc/newlib/libm + # @mkdir -p build/release/tinygo/lib/CMSIS/CMSIS + # @mkdir -p build/release/tinygo/lib/macos-minimal-sdk + # @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common + # @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults + # @mkdir -p build/release/tinygo/lib/musl/arch + # @mkdir -p build/release/tinygo/lib/musl/crt + # @mkdir -p build/release/tinygo/lib/musl/src + # @mkdir -p build/release/tinygo/lib/nrfx + # @mkdir -p build/release/tinygo/lib/picolibc/newlib/libc + # @mkdir -p build/release/tinygo/lib/picolibc/newlib/libm @mkdir -p build/release/tinygo/lib/wasi-libc - @mkdir -p build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0 - @mkdir -p build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus - @mkdir -p build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4 + # @mkdir -p build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0 + # @mkdir -p build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus + # @mkdir -p build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4 @echo copying source files @cp -p build/tinygo$(EXE) build/release/tinygo/bin ifneq ($(USE_SYSTEM_BINARYEN),1) @cp -p build/wasm-opt$(EXE) build/release/tinygo/bin endif @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 - @cp -rp lib/macos-minimal-sdk/* build/release/tinygo/lib/macos-minimal-sdk - @cp -rp lib/musl/arch/aarch64 build/release/tinygo/lib/musl/arch - @cp -rp lib/musl/arch/arm build/release/tinygo/lib/musl/arch - @cp -rp lib/musl/arch/generic build/release/tinygo/lib/musl/arch - @cp -rp lib/musl/arch/i386 build/release/tinygo/lib/musl/arch - @cp -rp lib/musl/arch/x86_64 build/release/tinygo/lib/musl/arch - @cp -rp lib/musl/crt/crt1.c build/release/tinygo/lib/musl/crt - @cp -rp lib/musl/COPYRIGHT build/release/tinygo/lib/musl - @cp -rp lib/musl/include build/release/tinygo/lib/musl - @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/include build/release/tinygo/lib/musl/src - @cp -rp lib/musl/src/internal build/release/tinygo/lib/musl/src - @cp -rp lib/musl/src/legacy build/release/tinygo/lib/musl/src - @cp -rp lib/musl/src/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/signal build/release/tinygo/lib/musl/src - @cp -rp lib/musl/src/stdio build/release/tinygo/lib/musl/src - @cp -rp lib/musl/src/string build/release/tinygo/lib/musl/src - @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/mingw-w64/mingw-w64-crt/def-include build/release/tinygo/lib/mingw-w64/mingw-w64-crt - @cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/api-ms-win-crt-* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common - @cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/kernel32.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common - @cp -rp lib/mingw-w64/mingw-w64-headers/crt/ build/release/tinygo/lib/mingw-w64/mingw-w64-headers - @cp -rp lib/mingw-w64/mingw-w64-headers/defaults/include build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults - @cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx - @cp -rp lib/picolibc/newlib/libc/ctype build/release/tinygo/lib/picolibc/newlib/libc - @cp -rp lib/picolibc/newlib/libc/include build/release/tinygo/lib/picolibc/newlib/libc - @cp -rp lib/picolibc/newlib/libc/locale build/release/tinygo/lib/picolibc/newlib/libc - @cp -rp lib/picolibc/newlib/libc/string build/release/tinygo/lib/picolibc/newlib/libc - @cp -rp lib/picolibc/newlib/libc/tinystdio build/release/tinygo/lib/picolibc/newlib/libc - @cp -rp lib/picolibc/newlib/libm/common build/release/tinygo/lib/picolibc/newlib/libm - @cp -rp lib/picolibc/newlib/libm/math build/release/tinygo/lib/picolibc/newlib/libm - @cp -rp lib/picolibc-stdio.c build/release/tinygo/lib + # @cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS + # @cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS + # @cp -rp lib/macos-minimal-sdk/* build/release/tinygo/lib/macos-minimal-sdk + # @cp -rp lib/musl/arch/aarch64 build/release/tinygo/lib/musl/arch + # @cp -rp lib/musl/arch/arm build/release/tinygo/lib/musl/arch + # @cp -rp lib/musl/arch/generic build/release/tinygo/lib/musl/arch + # @cp -rp lib/musl/arch/i386 build/release/tinygo/lib/musl/arch + # @cp -rp lib/musl/arch/x86_64 build/release/tinygo/lib/musl/arch + # @cp -rp lib/musl/crt/crt1.c build/release/tinygo/lib/musl/crt + # @cp -rp lib/musl/COPYRIGHT build/release/tinygo/lib/musl + # @cp -rp lib/musl/include build/release/tinygo/lib/musl + # @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/include build/release/tinygo/lib/musl/src + # @cp -rp lib/musl/src/internal build/release/tinygo/lib/musl/src + # @cp -rp lib/musl/src/legacy build/release/tinygo/lib/musl/src + # @cp -rp lib/musl/src/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/signal build/release/tinygo/lib/musl/src + # @cp -rp lib/musl/src/stdio build/release/tinygo/lib/musl/src + # @cp -rp lib/musl/src/string build/release/tinygo/lib/musl/src + # @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/mingw-w64/mingw-w64-crt/def-include build/release/tinygo/lib/mingw-w64/mingw-w64-crt + # @cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/api-ms-win-crt-* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common + # @cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/kernel32.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common + # @cp -rp lib/mingw-w64/mingw-w64-headers/crt/ build/release/tinygo/lib/mingw-w64/mingw-w64-headers + # @cp -rp lib/mingw-w64/mingw-w64-headers/defaults/include build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults + # @cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx + # @cp -rp lib/picolibc/newlib/libc/ctype build/release/tinygo/lib/picolibc/newlib/libc + # @cp -rp lib/picolibc/newlib/libc/include build/release/tinygo/lib/picolibc/newlib/libc + # @cp -rp lib/picolibc/newlib/libc/locale build/release/tinygo/lib/picolibc/newlib/libc + # @cp -rp lib/picolibc/newlib/libc/string build/release/tinygo/lib/picolibc/newlib/libc + # @cp -rp lib/picolibc/newlib/libc/tinystdio build/release/tinygo/lib/picolibc/newlib/libc + # @cp -rp lib/picolibc/newlib/libm/common build/release/tinygo/lib/picolibc/newlib/libm + # @cp -rp lib/picolibc/newlib/libm/math build/release/tinygo/lib/picolibc/newlib/libm + # @cp -rp lib/picolibc-stdio.c build/release/tinygo/lib @cp -rp lib/wasi-libc/sysroot build/release/tinygo/lib/wasi-libc/sysroot @cp -rp llvm-project/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt-builtins @cp -rp llvm-project/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt-builtins @cp -rp src build/release/tinygo/src @cp -rp targets build/release/tinygo/targets - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m0 -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0/compiler-rt compiler-rt - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m0plus -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus/compiler-rt compiler-rt - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m4 -o build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4/compiler-rt compiler-rt - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m0 -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0/picolibc picolibc - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m0plus -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus/picolibc picolibc - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m4 -o build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4/picolibc picolibc + # ./build/release/tinygo/bin/tinygo build-library -target=cortex-m0 -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0/compiler-rt compiler-rt + # ./build/release/tinygo/bin/tinygo build-library -target=cortex-m0plus -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus/compiler-rt compiler-rt + # ./build/release/tinygo/bin/tinygo build-library -target=cortex-m4 -o build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4/compiler-rt compiler-rt + # ./build/release/tinygo/bin/tinygo build-library -target=cortex-m0 -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0/picolibc picolibc + # ./build/release/tinygo/bin/tinygo build-library -target=cortex-m0plus -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus/picolibc picolibc + # ./build/release/tinygo/bin/tinygo build-library -target=cortex-m4 -o build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4/picolibc picolibc release: tar -czf build/release.tar.gz -C build/release tinygo From 81564295b21606e47e8128094ff78c311bdedf00 Mon Sep 17 00:00:00 2001 From: Tyler Rockwood Date: Thu, 22 Jun 2023 22:21:44 -0500 Subject: [PATCH 68/75] Resolve symlinks when looking for TINYGOROOT On darwin, os.Executable doesn't resolve symlinks by default AFAIK, and linux does, so this normalizes the behavior to always resolve symlinks, so I can download tinygo and symlink it somewhere else. See: https://pkg.go.dev/os#Executable Signed-off-by: Tyler Rockwood --- goenv/goenv.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/goenv/goenv.go b/goenv/goenv.go index f6a32502a6..7bf52392ee 100644 --- a/goenv/goenv.go +++ b/goenv/goenv.go @@ -257,6 +257,10 @@ func sourceDir() string { // Very unlikely. Bail out if it happens. panic("could not get executable path: " + err.Error()) } + resolved, err := filepath.EvalSymlinks(path) + if err == nil { + path = resolved + } root = filepath.Dir(filepath.Dir(path)) if isSourceDir(root) { return root From 25f6083675530c7c0e9a6cef2e89cc01b96d9835 Mon Sep 17 00:00:00 2001 From: Tyler Rockwood Date: Fri, 14 Jul 2023 14:30:10 -0500 Subject: [PATCH 69/75] Update build command Signed-off-by: Tyler Rockwood --- main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 5a45d07d6c..073bd6d00c 100644 --- a/main.go +++ b/main.go @@ -1549,8 +1549,8 @@ func main() { err = Build(pkgName, *outpath, options) handleCompilerError(err) fmt.Println("build successful") - fmt.Println("deploy your wasm function to a topic:") - fmt.Println("\trpk wasm deploy") + fmt.Println("deploy your transform to a topic:") + fmt.Println("\trpk transform deploy") } func upstreamMain() { From c32883753f1f9740f7d36c1b0235824d19fcea64 Mon Sep 17 00:00:00 2001 From: Tyler Rockwood Date: Fri, 4 Aug 2023 10:55:21 -0500 Subject: [PATCH 70/75] Package binarygen on macos Signed-off-by: Tyler Rockwood --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6403902363..f213713d42 100644 --- a/Makefile +++ b/Makefile @@ -134,7 +134,7 @@ else ifeq ($(shell uname -s),Darwin) CGO_LDFLAGS += -lxar - USE_SYSTEM_BINARYEN ?= 1 + # USE_SYSTEM_BINARYEN ?= 1 else ifeq ($(shell uname -s),FreeBSD) MD5SUM = md5 From 75a95597fe46d8fc864946995fa4dc514aa1613c Mon Sep 17 00:00:00 2001 From: Tyler Rockwood Date: Fri, 4 Aug 2023 10:55:40 -0500 Subject: [PATCH 71/75] Add help text after builds Signed-off-by: Tyler Rockwood --- main.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main.go b/main.go index 073bd6d00c..0d8e40eb73 100644 --- a/main.go +++ b/main.go @@ -1739,6 +1739,9 @@ func upstreamMain() { err := Build(pkgName, outpath, options) handleCompilerError(err) + fmt.Println("build successful") + fmt.Println("deploy your transform to a topic:") + fmt.Println("\trpk transform deploy") case "build-library": // Note: this command is only meant to be used while making a release! if outpath == "" { From 7aa9b69c72a31cc89b034983ea2d2e081da07949 Mon Sep 17 00:00:00 2001 From: Tyler Rockwood Date: Tue, 20 Jun 2023 11:50:41 -0500 Subject: [PATCH 72/75] Repackage tinygo as an RPK plugin something quick and hacky without help args Signed-off-by: Tyler Rockwood --- main.go | 148 -------------------------------------------------------- 1 file changed, 148 deletions(-) diff --git a/main.go b/main.go index 0d8e40eb73..a84c7aed3c 100644 --- a/main.go +++ b/main.go @@ -1405,155 +1405,7 @@ func getListOfPackages(pkgs []string, options *compileopts.Options) ([]string, e return pkgNames, nil } -func printAutocompleteHelp() { - type pluginHelp struct { - Path string `json:"path,omitempty"` - Short string `json:"short,omitempty"` - Long string `json:"long,omitempty"` - Example string `json:"example,omitempty"` - Args []string `json:"args,omitempty"` - } - buildHelp := pluginHelp{ - Path: "transform_build_tinygo", - Short: "compile a tinygo module in the current directory", - } - b, err := json.Marshal(&[]pluginHelp{buildHelp}) - if err != nil { - } - fmt.Fprintln(os.Stdout, string(b)) -} - -// This is a pure addition to upstream tinygo. -// -// It provides a hardcoded `build` command and autocomplete support for rpk func main() { - if len(os.Args) > 1 { - // Early command processing, before commands are interpreted by the Go flag - // library. - switch os.Args[1] { - case "clang", "ld.lld", "wasm-ld": - err := builder.RunTool(os.Args[1], os.Args[2:]...) - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - os.Exit(0) - } - } - - command := "build" - - opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z") - gc := flag.String("gc", "conservative", "garbage collector to use (none, leaking, conservative)") - panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)") - scheduler := flag.String("scheduler", "none", "which scheduler to use (none, tasks, asyncify)") - work := flag.Bool("work", false, "print the name of the temporary build directory and do not delete this directory on exit") - interpTimeout := flag.Duration("interp-timeout", 180*time.Second, "interp optimization pass timeout") - var tags buildutil.TagsFlag - flag.Var(&tags, "tags", "a space-separated list of extra build tags") - target := flag.String("target", "wasi", "chip/board name or JSON target specification file") - var stackSize uint64 - flag.Func("stack-size", "goroutine stack size (if unknown at compile time)", func(s string) error { - size, err := bytesize.Parse(s) - stackSize = uint64(size) - return err - }) - printSize := flag.String("size", "", "print sizes (none, short, full)") - 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") - parallelism := flag.Int("p", runtime.GOMAXPROCS(0), "the number of build jobs that can run in parallel") - nodebug := flag.Bool("no-debug", false, "strip debug information") - programmer := flag.String("programmer", "", "which hardware programmer to use") - ldflags := flag.String("ldflags", "", "Go link tool compatible ldflags") - llvmFeatures := flag.String("llvm-features", "", "comma separated LLVM features to enable") - flagJSON := flag.Bool("json", false, "print data in JSON format") - outpath := flag.String("o", "", "output filename") - helpAutocomplete := flag.Bool("help-autocomplete", false, "output complete help for rpk") - // strip the .rpk.managed-tinygo prefix - flag.CommandLine.Parse(os.Args[1:]) - - if *helpAutocomplete { - printAutocompleteHelp() - os.Exit(0) - } - - globalVarValues, err := parseGoLinkFlag(*ldflags) - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - - var printAllocs *regexp.Regexp - if *printAllocsString != "" { - printAllocs, err = regexp.Compile(*printAllocsString) - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - } - options := &compileopts.Options{ - GOOS: goenv.Get("GOOS"), - GOARCH: goenv.Get("GOARCH"), - GOARM: goenv.Get("GOARM"), - Target: *target, - StackSize: stackSize, - Opt: *opt, - GC: *gc, - PanicStrategy: *panicStrategy, - Scheduler: *scheduler, - Serial: "", - Work: *work, - InterpTimeout: *interpTimeout, - PrintIR: false, - DumpSSA: false, - VerifyIR: false, - SkipDWARF: false, - Semaphore: make(chan struct{}, *parallelism), - Debug: !*nodebug, - PrintSizes: *printSize, - PrintStacks: *printStacks, - PrintAllocs: printAllocs, - Tags: []string(tags), - TestConfig: compileopts.TestConfig{}, - GlobalValues: globalVarValues, - Programmer: *programmer, - OpenOCDCommands: nil, - LLVMFeatures: *llvmFeatures, - PrintJSON: *flagJSON, - Monitor: false, - BaudRate: 115200, - Timeout: 20 * time.Second, - } - if *printCommands { - options.PrintCommands = printCommand - } - err = options.Verify() - if err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - usage(command) - os.Exit(1) - } - pkgName := "." - if flag.NArg() == 1 { - pkgName = filepath.ToSlash(flag.Arg(0)) - } else if flag.NArg() > 1 { - fmt.Fprintln(os.Stderr, "build only accepts a single positional argument: package name, but multiple were specified") - usage(command) - os.Exit(1) - } - if options.Target == "" && filepath.Ext(*outpath) == ".wasm" { - options.Target = "wasm" - } - - err = Build(pkgName, *outpath, options) - handleCompilerError(err) - fmt.Println("build successful") - fmt.Println("deploy your transform to a topic:") - fmt.Println("\trpk transform deploy") -} - -func upstreamMain() { if len(os.Args) < 2 { fmt.Fprintln(os.Stderr, "No command-line arguments supplied.") usage("") From a7fdcea66438e1d288f4fb831dfb47c79d25274c Mon Sep 17 00:00:00 2001 From: Tyler Rockwood Date: Fri, 4 Aug 2023 11:01:35 -0500 Subject: [PATCH 73/75] Revert "Resolve symlinks when looking for TINYGOROOT" This reverts commit 82f306fd32420358c4b8bc07cde8c0ac9ae798d5. --- goenv/goenv.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/goenv/goenv.go b/goenv/goenv.go index 7bf52392ee..f6a32502a6 100644 --- a/goenv/goenv.go +++ b/goenv/goenv.go @@ -257,10 +257,6 @@ func sourceDir() string { // Very unlikely. Bail out if it happens. panic("could not get executable path: " + err.Error()) } - resolved, err := filepath.EvalSymlinks(path) - if err == nil { - path = resolved - } root = filepath.Dir(filepath.Dir(path)) if isSourceDir(root) { return root From 302cbde39efcad64cc5dcd2d9de985366a09c774 Mon Sep 17 00:00:00 2001 From: Tyler Rockwood Date: Fri, 4 Aug 2023 11:01:49 -0500 Subject: [PATCH 74/75] Revert "Add autocomplete commands" This reverts commit 06cda88bc60c9ef3c92b2bb43eddd9ec2b730d85. --- main.go | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/main.go b/main.go index a84c7aed3c..7a0507a675 100644 --- a/main.go +++ b/main.go @@ -1406,6 +1406,127 @@ func getListOfPackages(pkgs []string, options *compileopts.Options) ([]string, e } func main() { + if len(os.Args) > 1 { + // Early command processing, before commands are interpreted by the Go flag + // library. + switch os.Args[1] { + case "clang", "ld.lld", "wasm-ld": + err := builder.RunTool(os.Args[1], os.Args[2:]...) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + os.Exit(0) + } + } + + command := "build" + + opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z") + gc := flag.String("gc", "conservative", "garbage collector to use (none, leaking, conservative)") + panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)") + scheduler := flag.String("scheduler", "none", "which scheduler to use (none, tasks, asyncify)") + work := flag.Bool("work", false, "print the name of the temporary build directory and do not delete this directory on exit") + interpTimeout := flag.Duration("interp-timeout", 180*time.Second, "interp optimization pass timeout") + var tags buildutil.TagsFlag + flag.Var(&tags, "tags", "a space-separated list of extra build tags") + target := flag.String("target", "wasi", "chip/board name or JSON target specification file") + var stackSize uint64 + flag.Func("stack-size", "goroutine stack size (if unknown at compile time)", func(s string) error { + size, err := bytesize.Parse(s) + stackSize = uint64(size) + return err + }) + printSize := flag.String("size", "", "print sizes (none, short, full)") + 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") + parallelism := flag.Int("p", runtime.GOMAXPROCS(0), "the number of build jobs that can run in parallel") + nodebug := flag.Bool("no-debug", false, "strip debug information") + programmer := flag.String("programmer", "", "which hardware programmer to use") + ldflags := flag.String("ldflags", "", "Go link tool compatible ldflags") + llvmFeatures := flag.String("llvm-features", "", "comma separated LLVM features to enable") + flagJSON := flag.Bool("json", false, "print data in JSON format") + outpath := flag.String("o", "", "output filename") + // strip the .rpk.managed-tinygo prefix + flag.CommandLine.Parse(os.Args[1:]) + globalVarValues, err := parseGoLinkFlag(*ldflags) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + var printAllocs *regexp.Regexp + if *printAllocsString != "" { + printAllocs, err = regexp.Compile(*printAllocsString) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + } + options := &compileopts.Options{ + GOOS: goenv.Get("GOOS"), + GOARCH: goenv.Get("GOARCH"), + GOARM: goenv.Get("GOARM"), + Target: *target, + StackSize: stackSize, + Opt: *opt, + GC: *gc, + PanicStrategy: *panicStrategy, + Scheduler: *scheduler, + Serial: "", + Work: *work, + InterpTimeout: *interpTimeout, + PrintIR: false, + DumpSSA: false, + VerifyIR: false, + SkipDWARF: false, + Semaphore: make(chan struct{}, *parallelism), + Debug: !*nodebug, + PrintSizes: *printSize, + PrintStacks: *printStacks, + PrintAllocs: printAllocs, + Tags: []string(tags), + TestConfig: compileopts.TestConfig{}, + GlobalValues: globalVarValues, + Programmer: *programmer, + OpenOCDCommands: nil, + LLVMFeatures: *llvmFeatures, + PrintJSON: *flagJSON, + Monitor: false, + BaudRate: 115200, + Timeout: 20 * time.Second, + } + if *printCommands { + options.PrintCommands = printCommand + } + err = options.Verify() + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + usage(command) + os.Exit(1) + } + pkgName := "." + if flag.NArg() == 1 { + pkgName = filepath.ToSlash(flag.Arg(0)) + } else if flag.NArg() > 1 { + fmt.Fprintln(os.Stderr, "build only accepts a single positional argument: package name, but multiple were specified") + usage(command) + os.Exit(1) + } + if options.Target == "" && filepath.Ext(*outpath) == ".wasm" { + options.Target = "wasm" + } + + err = Build(pkgName, *outpath, options) + handleCompilerError(err) + fmt.Println("build successful") + fmt.Println("deploy your wasm function to a topic:") + fmt.Println("\trpk wasm deploy") +} + +func upstreamMain() { +>>>>>>> c6c68bbf (Revert "Add autocomplete commands") if len(os.Args) < 2 { fmt.Fprintln(os.Stderr, "No command-line arguments supplied.") usage("") From 201eb39dec71938786a1c56da7a5a7809703c055 Mon Sep 17 00:00:00 2001 From: Tyler Rockwood Date: Fri, 4 Aug 2023 11:01:59 -0500 Subject: [PATCH 75/75] Revert "Repackage tinygo as an RPK plugin" This reverts commit 3a32210199f91d41199a405afa3360727e8d9799. --- main.go | 121 -------------------------------------------------------- 1 file changed, 121 deletions(-) diff --git a/main.go b/main.go index 7a0507a675..a84c7aed3c 100644 --- a/main.go +++ b/main.go @@ -1406,127 +1406,6 @@ func getListOfPackages(pkgs []string, options *compileopts.Options) ([]string, e } func main() { - if len(os.Args) > 1 { - // Early command processing, before commands are interpreted by the Go flag - // library. - switch os.Args[1] { - case "clang", "ld.lld", "wasm-ld": - err := builder.RunTool(os.Args[1], os.Args[2:]...) - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - os.Exit(0) - } - } - - command := "build" - - opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z") - gc := flag.String("gc", "conservative", "garbage collector to use (none, leaking, conservative)") - panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)") - scheduler := flag.String("scheduler", "none", "which scheduler to use (none, tasks, asyncify)") - work := flag.Bool("work", false, "print the name of the temporary build directory and do not delete this directory on exit") - interpTimeout := flag.Duration("interp-timeout", 180*time.Second, "interp optimization pass timeout") - var tags buildutil.TagsFlag - flag.Var(&tags, "tags", "a space-separated list of extra build tags") - target := flag.String("target", "wasi", "chip/board name or JSON target specification file") - var stackSize uint64 - flag.Func("stack-size", "goroutine stack size (if unknown at compile time)", func(s string) error { - size, err := bytesize.Parse(s) - stackSize = uint64(size) - return err - }) - printSize := flag.String("size", "", "print sizes (none, short, full)") - 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") - parallelism := flag.Int("p", runtime.GOMAXPROCS(0), "the number of build jobs that can run in parallel") - nodebug := flag.Bool("no-debug", false, "strip debug information") - programmer := flag.String("programmer", "", "which hardware programmer to use") - ldflags := flag.String("ldflags", "", "Go link tool compatible ldflags") - llvmFeatures := flag.String("llvm-features", "", "comma separated LLVM features to enable") - flagJSON := flag.Bool("json", false, "print data in JSON format") - outpath := flag.String("o", "", "output filename") - // strip the .rpk.managed-tinygo prefix - flag.CommandLine.Parse(os.Args[1:]) - globalVarValues, err := parseGoLinkFlag(*ldflags) - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - - var printAllocs *regexp.Regexp - if *printAllocsString != "" { - printAllocs, err = regexp.Compile(*printAllocsString) - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - } - options := &compileopts.Options{ - GOOS: goenv.Get("GOOS"), - GOARCH: goenv.Get("GOARCH"), - GOARM: goenv.Get("GOARM"), - Target: *target, - StackSize: stackSize, - Opt: *opt, - GC: *gc, - PanicStrategy: *panicStrategy, - Scheduler: *scheduler, - Serial: "", - Work: *work, - InterpTimeout: *interpTimeout, - PrintIR: false, - DumpSSA: false, - VerifyIR: false, - SkipDWARF: false, - Semaphore: make(chan struct{}, *parallelism), - Debug: !*nodebug, - PrintSizes: *printSize, - PrintStacks: *printStacks, - PrintAllocs: printAllocs, - Tags: []string(tags), - TestConfig: compileopts.TestConfig{}, - GlobalValues: globalVarValues, - Programmer: *programmer, - OpenOCDCommands: nil, - LLVMFeatures: *llvmFeatures, - PrintJSON: *flagJSON, - Monitor: false, - BaudRate: 115200, - Timeout: 20 * time.Second, - } - if *printCommands { - options.PrintCommands = printCommand - } - err = options.Verify() - if err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - usage(command) - os.Exit(1) - } - pkgName := "." - if flag.NArg() == 1 { - pkgName = filepath.ToSlash(flag.Arg(0)) - } else if flag.NArg() > 1 { - fmt.Fprintln(os.Stderr, "build only accepts a single positional argument: package name, but multiple were specified") - usage(command) - os.Exit(1) - } - if options.Target == "" && filepath.Ext(*outpath) == ".wasm" { - options.Target = "wasm" - } - - err = Build(pkgName, *outpath, options) - handleCompilerError(err) - fmt.Println("build successful") - fmt.Println("deploy your wasm function to a topic:") - fmt.Println("\trpk wasm deploy") -} - -func upstreamMain() { ->>>>>>> c6c68bbf (Revert "Add autocomplete commands") if len(os.Args) < 2 { fmt.Fprintln(os.Stderr, "No command-line arguments supplied.") usage("")