diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index 1da794c..9c2998c 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -7,7 +7,7 @@ jobs: ci: strategy: matrix: - go-version: [ 1.18, 1.19 ] + go-version: [ 1.21.3 ] os: [ ubuntu-latest ] runs-on: ${{ matrix.os }} steps: diff --git a/README.md b/README.md index aa4cc7f..72f0f89 100644 --- a/README.md +++ b/README.md @@ -67,14 +67,15 @@ First, please install the following tools: * Node.js (and npm) * [wrangler](https://developers.cloudflare.com/workers/wrangler/) - You can install it by running `npm install -g wrangler`. -* tinygo +* tinygo 0.29.0 or later +* [gonew](https://pkg.go.dev/golang.org/x/tools/cmd/gonew) + - You can install it by running `go install golang.org/x/tools/cmd/gonew@latest` After installation, please run the following commands. ```console -wrangler generate my-app syumai/workers/_templates/cloudflare/worker-tinygo +gonew github.com/syumai/workers/_templates/cloudflare/worker-tinygo your.module/my-app # e.g. github.com/syumai/my-app cd my-app -go mod init go mod tidy make dev # start running dev server curl http://localhost:8787/hello # outputs "Hello!" diff --git a/_templates/cloudflare/pages-tinygo/Makefile b/_templates/cloudflare/pages-tinygo/Makefile index 1d89456..50e0822 100644 --- a/_templates/cloudflare/pages-tinygo/Makefile +++ b/_templates/cloudflare/pages-tinygo/Makefile @@ -4,7 +4,7 @@ dev: .PHONY: build build: - go run github.com/syumai/workers/cmd/workers-assets-gen@latest + go run github.com/syumai/workers/cmd/workers-assets-gen@v0.18.0 tinygo build -o ./build/app.wasm -target wasm -no-debug ./... .PHONY: deploy diff --git a/_templates/cloudflare/pages-tinygo/README.md b/_templates/cloudflare/pages-tinygo/README.md index 73de321..56b4e42 100644 --- a/_templates/cloudflare/pages-tinygo/README.md +++ b/_templates/cloudflare/pages-tinygo/README.md @@ -12,14 +12,22 @@ - Node.js - [wrangler](https://developers.cloudflare.com/workers/wrangler/) - just run `npm install -g wrangler` -- tinygo +* tinygo 0.29.0 or later ## Getting Started +* If not already installed, please install the [gonew](https://pkg.go.dev/golang.org/x/tools/cmd/gonew) command. + +```console +go install golang.org/x/tools/cmd/gonew@latest +``` + +* Create a new project using this template. + - Second argument passed to `gonew` is a module path of your new app. + ```console -wrangler generate my-app syumai/workers/_templates/cloudflare/pages-tinygo +gonew github.com/syumai/workers/_templates/cloudflare/pages-tinygo your.module/my-app # e.g. github.com/syumai/my-app cd my-app -go mod init go mod tidy make dev # start running dev server curl http://localhost:8787/api/hello # outputs "Hello, Pages Functions!" diff --git a/_templates/cloudflare/pages-tinygo/go.mod b/_templates/cloudflare/pages-tinygo/go.mod index 35458cc..f6d3c4f 100644 --- a/_templates/cloudflare/pages-tinygo/go.mod +++ b/_templates/cloudflare/pages-tinygo/go.mod @@ -1,4 +1,4 @@ -module github.com/syumai/workers/_examples/pages-functions +module github.com/syumai/workers/_examples/pages-tinygo go 1.18 diff --git a/_templates/cloudflare/worker-go/Makefile b/_templates/cloudflare/worker-go/Makefile index f2d3941..089b51d 100644 --- a/_templates/cloudflare/worker-go/Makefile +++ b/_templates/cloudflare/worker-go/Makefile @@ -4,7 +4,7 @@ dev: .PHONY: build build: - go run github.com/syumai/workers/cmd/workers-assets-gen@latest -mode=go + go run github.com/syumai/workers/cmd/workers-assets-gen@v0.18.0 -mode=go GOOS=js GOARCH=wasm go build -o ./build/app.wasm . .PHONY: deploy diff --git a/_templates/cloudflare/worker-go/README.md b/_templates/cloudflare/worker-go/README.md index bb95224..5e8933b 100644 --- a/_templates/cloudflare/worker-go/README.md +++ b/_templates/cloudflare/worker-go/README.md @@ -17,14 +17,22 @@ - Node.js - [wrangler](https://developers.cloudflare.com/workers/wrangler/) - just run `npm install -g wrangler` -- Go +- Go 1.21.0 or later ## Getting Started +* If not already installed, please install the [gonew](https://pkg.go.dev/golang.org/x/tools/cmd/gonew) command. + +```console +go install golang.org/x/tools/cmd/gonew@latest +``` + +* Create a new project using this template. + - Second argument passed to `gonew` is a module path of your new app. + ```console -wrangler generate my-app syumai/workers/_templates/cloudflare/worker-go +gonew github.com/syumai/workers/_templates/cloudflare/worker-go your.module/my-app # e.g. github.com/syumai/my-app cd my-app -go mod init go mod tidy make dev # start running dev server curl http://localhost:8787/hello # outputs "Hello!" diff --git a/_templates/cloudflare/worker-go/go.mod b/_templates/cloudflare/worker-go/go.mod new file mode 100644 index 0000000..762980b --- /dev/null +++ b/_templates/cloudflare/worker-go/go.mod @@ -0,0 +1,3 @@ +module github.com/syumai/workers/_templates/cloudflare/worker-go + +go 1.21.1 diff --git a/_templates/cloudflare/worker-tinygo/Makefile b/_templates/cloudflare/worker-tinygo/Makefile index a91dc57..580e61e 100644 --- a/_templates/cloudflare/worker-tinygo/Makefile +++ b/_templates/cloudflare/worker-tinygo/Makefile @@ -4,7 +4,7 @@ dev: .PHONY: build build: - go run github.com/syumai/workers/cmd/workers-assets-gen@latest + go run github.com/syumai/workers/cmd/workers-assets-gen@v0.18.0 tinygo build -o ./build/app.wasm -target wasm -no-debug ./... .PHONY: deploy diff --git a/_templates/cloudflare/worker-tinygo/README.md b/_templates/cloudflare/worker-tinygo/README.md index 0333a2b..af1557c 100644 --- a/_templates/cloudflare/worker-tinygo/README.md +++ b/_templates/cloudflare/worker-tinygo/README.md @@ -12,14 +12,22 @@ - Node.js - [wrangler](https://developers.cloudflare.com/workers/wrangler/) - just run `npm install -g wrangler` -- tinygo +- tinygo 0.29.0 or later ## Getting Started +* If not already installed, please install the [gonew](https://pkg.go.dev/golang.org/x/tools/cmd/gonew) command. + +```console +go install golang.org/x/tools/cmd/gonew@latest +``` + +* Create a new project using this template. + - Second argument passed to `gonew` is a module path of your new app. + ```console -wrangler generate my-app syumai/workers/_templates/cloudflare/worker-tinygo +gonew github.com/syumai/workers/_templates/cloudflare/worker-tinygo your.module/my-app # e.g. github.com/syumai/my-app cd my-app -go mod init go mod tidy make dev # start running dev server curl http://localhost:8787/hello # outputs "Hello!" diff --git a/_templates/cloudflare/worker-tinygo/go.mod b/_templates/cloudflare/worker-tinygo/go.mod new file mode 100644 index 0000000..d8848fb --- /dev/null +++ b/_templates/cloudflare/worker-tinygo/go.mod @@ -0,0 +1,3 @@ +module github.com/syumai/workers/_templates/cloudflare/worker-tinygo + +go 1.21.1 diff --git a/cmd/workers-assets-gen/assets/wasm_exec_go.js b/cmd/workers-assets-gen/assets/wasm_exec_go.js index e0a4919..e818734 100644 --- a/cmd/workers-assets-gen/assets/wasm_exec_go.js +++ b/cmd/workers-assets-gen/assets/wasm_exec_go.js @@ -19,8 +19,8 @@ outputBuf += decoder.decode(buf); const nl = outputBuf.lastIndexOf("\n"); if (nl != -1) { - console.log(outputBuf.substr(0, nl)); - outputBuf = outputBuf.substr(nl + 1); + console.log(outputBuf.substring(0, nl)); + outputBuf = outputBuf.substring(nl + 1); } return buf.length; }, @@ -113,6 +113,10 @@ this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true); } + const setInt32 = (addr, v) => { + this.mem.setUint32(addr + 0, v, true); + } + const getInt64 = (addr) => { const low = this.mem.getUint32(addr + 0, true); const high = this.mem.getInt32(addr + 4, true); @@ -206,7 +210,10 @@ const timeOrigin = Date.now() - performance.now(); this.importObject = { - go: { + _gotest: { + add: (a, b) => a + b, + }, + gojs: { // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). @@ -269,7 +276,7 @@ this._resume(); } }, - getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early + getInt64(sp + 8), )); this.mem.setInt32(sp + 16, id, true); }, diff --git a/cmd/workers-assets-gen/assets/wasm_exec_tinygo.js b/cmd/workers-assets-gen/assets/wasm_exec_tinygo.js index 3920013..019e3f7 100644 --- a/cmd/workers-assets-gen/assets/wasm_exec_tinygo.js +++ b/cmd/workers-assets-gen/assets/wasm_exec_tinygo.js @@ -30,7 +30,7 @@ } */ - /* + /* if (!global.fs && global.require) { global.fs = require("fs"); } @@ -138,6 +138,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 { @@ -150,19 +151,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; } @@ -170,71 +161,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) => { @@ -302,7 +292,7 @@ return 0; }, }, - env: { + gojs: { // func ticks() float64 "runtime.ticks": () => { return timeOrigin + performance.now(); @@ -315,54 +305,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 { @@ -376,9 +366,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); @@ -389,8 +379,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)); @@ -402,66 +392,70 @@ }, // 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 }, } }; + + // Go 1.20 uses 'env'. Go 1.21 uses 'gojs'. + // For compatibility, we use both as long as Go 1.20 is supported. + this.importObject.env = this.importObject.gojs; } async run(instance) { diff --git a/go.mod b/go.mod index c357410..47ee94a 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/syumai/workers -go 1.18 +go 1.21.3