diff --git a/frontend/public/go-format.wasm b/frontend/public/go-format.wasm new file mode 100644 index 0000000..7804946 Binary files /dev/null and b/frontend/public/go-format.wasm differ diff --git a/frontend/public/go.wasm b/frontend/public/go.wasm deleted file mode 100644 index 10a967e..0000000 Binary files a/frontend/public/go.wasm and /dev/null differ diff --git a/frontend/public/wasm_exec.js b/frontend/public/wasm_exec.js deleted file mode 100644 index bc6f210..0000000 --- a/frontend/public/wasm_exec.js +++ /dev/null @@ -1,561 +0,0 @@ -// Copyright 2018 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. - -"use strict"; - -(() => { - const enosys = () => { - const err = new Error("not implemented"); - err.code = "ENOSYS"; - return err; - }; - - if (!globalThis.fs) { - let outputBuf = ""; - globalThis.fs = { - constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused - writeSync(fd, buf) { - outputBuf += decoder.decode(buf); - const nl = outputBuf.lastIndexOf("\n"); - if (nl != -1) { - console.log(outputBuf.substring(0, nl)); - outputBuf = outputBuf.substring(nl + 1); - } - return buf.length; - }, - write(fd, buf, offset, length, position, callback) { - if (offset !== 0 || length !== buf.length || position !== null) { - callback(enosys()); - return; - } - const n = this.writeSync(fd, buf); - callback(null, n); - }, - chmod(path, mode, callback) { callback(enosys()); }, - chown(path, uid, gid, callback) { callback(enosys()); }, - close(fd, callback) { callback(enosys()); }, - fchmod(fd, mode, callback) { callback(enosys()); }, - fchown(fd, uid, gid, callback) { callback(enosys()); }, - fstat(fd, callback) { callback(enosys()); }, - fsync(fd, callback) { callback(null); }, - ftruncate(fd, length, callback) { callback(enosys()); }, - lchown(path, uid, gid, callback) { callback(enosys()); }, - link(path, link, callback) { callback(enosys()); }, - lstat(path, callback) { callback(enosys()); }, - mkdir(path, perm, callback) { callback(enosys()); }, - open(path, flags, mode, callback) { callback(enosys()); }, - read(fd, buffer, offset, length, position, callback) { callback(enosys()); }, - readdir(path, callback) { callback(enosys()); }, - readlink(path, callback) { callback(enosys()); }, - rename(from, to, callback) { callback(enosys()); }, - rmdir(path, callback) { callback(enosys()); }, - stat(path, callback) { callback(enosys()); }, - symlink(path, link, callback) { callback(enosys()); }, - truncate(path, length, callback) { callback(enosys()); }, - unlink(path, callback) { callback(enosys()); }, - utimes(path, atime, mtime, callback) { callback(enosys()); }, - }; - } - - if (!globalThis.process) { - globalThis.process = { - getuid() { return -1; }, - getgid() { return -1; }, - geteuid() { return -1; }, - getegid() { return -1; }, - getgroups() { throw enosys(); }, - pid: -1, - ppid: -1, - umask() { throw enosys(); }, - cwd() { throw enosys(); }, - chdir() { throw enosys(); }, - } - } - - if (!globalThis.crypto) { - throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)"); - } - - if (!globalThis.performance) { - throw new Error("globalThis.performance is not available, polyfill required (performance.now only)"); - } - - if (!globalThis.TextEncoder) { - throw new Error("globalThis.TextEncoder is not available, polyfill required"); - } - - if (!globalThis.TextDecoder) { - throw new Error("globalThis.TextDecoder is not available, polyfill required"); - } - - const encoder = new TextEncoder("utf-8"); - const decoder = new TextDecoder("utf-8"); - - globalThis.Go = class { - constructor() { - this.argv = ["js"]; - this.env = {}; - this.exit = (code) => { - if (code !== 0) { - console.warn("exit code:", code); - } - }; - this._exitPromise = new Promise((resolve) => { - this._resolveExitPromise = resolve; - }); - this._pendingEvent = null; - this._scheduledTimeouts = new Map(); - this._nextCallbackTimeoutID = 1; - - const setInt64 = (addr, v) => { - this.mem.setUint32(addr + 0, v, true); - 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); - return low + high * 4294967296; - } - - const loadValue = (addr) => { - const f = this.mem.getFloat64(addr, true); - if (f === 0) { - return undefined; - } - if (!isNaN(f)) { - return f; - } - - const id = this.mem.getUint32(addr, true); - return this._values[id]; - } - - const storeValue = (addr, v) => { - const nanHead = 0x7FF80000; - - if (typeof v === "number" && v !== 0) { - if (isNaN(v)) { - this.mem.setUint32(addr + 4, nanHead, true); - this.mem.setUint32(addr, 0, true); - return; - } - this.mem.setFloat64(addr, v, true); - return; - } - - if (v === undefined) { - this.mem.setFloat64(addr, 0, true); - return; - } - - let id = this._ids.get(v); - if (id === undefined) { - id = this._idPool.pop(); - if (id === undefined) { - id = this._values.length; - } - this._values[id] = v; - this._goRefCounts[id] = 0; - this._ids.set(v, id); - } - this._goRefCounts[id]++; - let typeFlag = 0; - switch (typeof v) { - case "object": - if (v !== null) { - typeFlag = 1; - } - break; - case "string": - typeFlag = 2; - break; - case "symbol": - typeFlag = 3; - break; - case "function": - typeFlag = 4; - break; - } - this.mem.setUint32(addr + 4, nanHead | typeFlag, true); - this.mem.setUint32(addr, id, true); - } - - const loadSlice = (addr) => { - const array = getInt64(addr + 0); - const len = getInt64(addr + 8); - return new Uint8Array(this._inst.exports.mem.buffer, array, len); - } - - const loadSliceOfValues = (addr) => { - const array = getInt64(addr + 0); - const len = getInt64(addr + 8); - const a = new Array(len); - for (let i = 0; i < len; i++) { - a[i] = loadValue(array + i * 8); - } - return a; - } - - const loadString = (addr) => { - const saddr = getInt64(addr + 0); - const len = getInt64(addr + 8); - return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); - } - - const timeOrigin = Date.now() - performance.now(); - this.importObject = { - _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). - // This changes the SP, thus we have to update the SP used by the imported function. - - // func wasmExit(code int32) - "runtime.wasmExit": (sp) => { - sp >>>= 0; - const code = this.mem.getInt32(sp + 8, true); - this.exited = true; - delete this._inst; - delete this._values; - delete this._goRefCounts; - delete this._ids; - delete this._idPool; - this.exit(code); - }, - - // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) - "runtime.wasmWrite": (sp) => { - sp >>>= 0; - const fd = getInt64(sp + 8); - const p = getInt64(sp + 16); - const n = this.mem.getInt32(sp + 24, true); - fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); - }, - - // func resetMemoryDataView() - "runtime.resetMemoryDataView": (sp) => { - sp >>>= 0; - this.mem = new DataView(this._inst.exports.mem.buffer); - }, - - // func nanotime1() int64 - "runtime.nanotime1": (sp) => { - sp >>>= 0; - setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); - }, - - // func walltime() (sec int64, nsec int32) - "runtime.walltime": (sp) => { - sp >>>= 0; - const msec = (new Date).getTime(); - setInt64(sp + 8, msec / 1000); - this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); - }, - - // func scheduleTimeoutEvent(delay int64) int32 - "runtime.scheduleTimeoutEvent": (sp) => { - sp >>>= 0; - const id = this._nextCallbackTimeoutID; - this._nextCallbackTimeoutID++; - this._scheduledTimeouts.set(id, setTimeout( - () => { - this._resume(); - while (this._scheduledTimeouts.has(id)) { - // for some reason Go failed to register the timeout event, log and try again - // (temporary workaround for https://github.com/golang/go/issues/28975) - console.warn("scheduleTimeoutEvent: missed timeout event"); - this._resume(); - } - }, - getInt64(sp + 8), - )); - this.mem.setInt32(sp + 16, id, true); - }, - - // func clearTimeoutEvent(id int32) - "runtime.clearTimeoutEvent": (sp) => { - sp >>>= 0; - const id = this.mem.getInt32(sp + 8, true); - clearTimeout(this._scheduledTimeouts.get(id)); - this._scheduledTimeouts.delete(id); - }, - - // func getRandomData(r []byte) - "runtime.getRandomData": (sp) => { - sp >>>= 0; - crypto.getRandomValues(loadSlice(sp + 8)); - }, - - // func finalizeRef(v ref) - "syscall/js.finalizeRef": (sp) => { - sp >>>= 0; - const id = this.mem.getUint32(sp + 8, true); - this._goRefCounts[id]--; - if (this._goRefCounts[id] === 0) { - const v = this._values[id]; - this._values[id] = null; - this._ids.delete(v); - this._idPool.push(id); - } - }, - - // func stringVal(value string) ref - "syscall/js.stringVal": (sp) => { - sp >>>= 0; - storeValue(sp + 24, loadString(sp + 8)); - }, - - // func valueGet(v ref, p string) ref - "syscall/js.valueGet": (sp) => { - sp >>>= 0; - const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); - sp = this._inst.exports.getsp() >>> 0; // see comment above - storeValue(sp + 32, result); - }, - - // func valueSet(v ref, p string, x ref) - "syscall/js.valueSet": (sp) => { - sp >>>= 0; - Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); - }, - - // func valueDelete(v ref, p string) - "syscall/js.valueDelete": (sp) => { - sp >>>= 0; - Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); - }, - - // func valueIndex(v ref, i int) ref - "syscall/js.valueIndex": (sp) => { - sp >>>= 0; - storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); - }, - - // valueSetIndex(v ref, i int, x ref) - "syscall/js.valueSetIndex": (sp) => { - sp >>>= 0; - Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); - }, - - // func valueCall(v ref, m string, args []ref) (ref, bool) - "syscall/js.valueCall": (sp) => { - sp >>>= 0; - try { - const v = loadValue(sp + 8); - const m = Reflect.get(v, loadString(sp + 16)); - const args = loadSliceOfValues(sp + 32); - const result = Reflect.apply(m, v, args); - sp = this._inst.exports.getsp() >>> 0; // see comment above - storeValue(sp + 56, result); - this.mem.setUint8(sp + 64, 1); - } catch (err) { - sp = this._inst.exports.getsp() >>> 0; // see comment above - storeValue(sp + 56, err); - this.mem.setUint8(sp + 64, 0); - } - }, - - // func valueInvoke(v ref, args []ref) (ref, bool) - "syscall/js.valueInvoke": (sp) => { - sp >>>= 0; - try { - const v = loadValue(sp + 8); - const args = loadSliceOfValues(sp + 16); - const result = Reflect.apply(v, undefined, args); - sp = this._inst.exports.getsp() >>> 0; // see comment above - storeValue(sp + 40, result); - this.mem.setUint8(sp + 48, 1); - } catch (err) { - sp = this._inst.exports.getsp() >>> 0; // see comment above - storeValue(sp + 40, err); - this.mem.setUint8(sp + 48, 0); - } - }, - - // func valueNew(v ref, args []ref) (ref, bool) - "syscall/js.valueNew": (sp) => { - sp >>>= 0; - try { - const v = loadValue(sp + 8); - const args = loadSliceOfValues(sp + 16); - const result = Reflect.construct(v, args); - sp = this._inst.exports.getsp() >>> 0; // see comment above - storeValue(sp + 40, result); - this.mem.setUint8(sp + 48, 1); - } catch (err) { - sp = this._inst.exports.getsp() >>> 0; // see comment above - storeValue(sp + 40, err); - this.mem.setUint8(sp + 48, 0); - } - }, - - // func valueLength(v ref) int - "syscall/js.valueLength": (sp) => { - sp >>>= 0; - setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); - }, - - // valuePrepareString(v ref) (ref, int) - "syscall/js.valuePrepareString": (sp) => { - sp >>>= 0; - const str = encoder.encode(String(loadValue(sp + 8))); - storeValue(sp + 16, str); - setInt64(sp + 24, str.length); - }, - - // valueLoadString(v ref, b []byte) - "syscall/js.valueLoadString": (sp) => { - sp >>>= 0; - const str = loadValue(sp + 8); - loadSlice(sp + 16).set(str); - }, - - // func valueInstanceOf(v ref, t ref) bool - "syscall/js.valueInstanceOf": (sp) => { - sp >>>= 0; - this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0); - }, - - // func copyBytesToGo(dst []byte, src ref) (int, bool) - "syscall/js.copyBytesToGo": (sp) => { - sp >>>= 0; - const dst = loadSlice(sp + 8); - const src = loadValue(sp + 32); - if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { - this.mem.setUint8(sp + 48, 0); - return; - } - const toCopy = src.subarray(0, dst.length); - dst.set(toCopy); - setInt64(sp + 40, toCopy.length); - this.mem.setUint8(sp + 48, 1); - }, - - // func copyBytesToJS(dst ref, src []byte) (int, bool) - "syscall/js.copyBytesToJS": (sp) => { - sp >>>= 0; - const dst = loadValue(sp + 8); - const src = loadSlice(sp + 16); - if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { - this.mem.setUint8(sp + 48, 0); - return; - } - const toCopy = src.subarray(0, dst.length); - dst.set(toCopy); - setInt64(sp + 40, toCopy.length); - this.mem.setUint8(sp + 48, 1); - }, - - "debug": (value) => { - console.log(value); - }, - } - }; - } - - async run(instance) { - if (!(instance instanceof WebAssembly.Instance)) { - throw new Error("Go.run: WebAssembly.Instance expected"); - } - this._inst = instance; - this.mem = new DataView(this._inst.exports.mem.buffer); - this._values = [ // JS values that Go currently has references to, indexed by reference id - NaN, - 0, - null, - true, - false, - globalThis, - this, - ]; - this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id - this._ids = new Map([ // mapping from JS values to reference ids - [0, 1], - [null, 2], - [true, 3], - [false, 4], - [globalThis, 5], - [this, 6], - ]); - this._idPool = []; // unused ids that have been garbage collected - this.exited = false; // whether the Go program has exited - - // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. - let offset = 4096; - - const strPtr = (str) => { - const ptr = offset; - const bytes = encoder.encode(str + "\0"); - new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes); - offset += bytes.length; - if (offset % 8 !== 0) { - offset += 8 - (offset % 8); - } - return ptr; - }; - - const argc = this.argv.length; - - const argvPtrs = []; - this.argv.forEach((arg) => { - argvPtrs.push(strPtr(arg)); - }); - argvPtrs.push(0); - - const keys = Object.keys(this.env).sort(); - keys.forEach((key) => { - argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); - }); - argvPtrs.push(0); - - const argv = offset; - argvPtrs.forEach((ptr) => { - this.mem.setUint32(offset, ptr, true); - this.mem.setUint32(offset + 4, 0, true); - offset += 8; - }); - - // The linker guarantees global data starts from at least wasmMinDataAddr. - // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr. - const wasmMinDataAddr = 4096 + 8192; - if (offset >= wasmMinDataAddr) { - throw new Error("total length of command line and environment variables exceeds limit"); - } - - this._inst.exports.run(argc, argv); - if (this.exited) { - this._resolveExitPromise(); - } - await this._exitPromise; - } - - _resume() { - if (this.exited) { - throw new Error("Go program has already exited"); - } - this._inst.exports.resume(); - if (this.exited) { - this._resolveExitPromise(); - } - } - - _makeFuncWrapper(id) { - const go = this; - return function () { - const event = { id: id, this: this, args: arguments }; - go._pendingEvent = event; - go._resume(); - return event.result; - }; - } - } -})(); diff --git a/frontend/src/common/prettier/plugins/go/build-tinygo.bat b/frontend/src/common/prettier/plugins/go/build-tinygo.bat new file mode 100644 index 0000000..5b43904 --- /dev/null +++ b/frontend/src/common/prettier/plugins/go/build-tinygo.bat @@ -0,0 +1,42 @@ +@echo off +rem Build script for Go Prettier Plugin WASM using TinyGo +rem This script compiles the Go code to WebAssembly for browser environment + +echo Building Go Prettier Plugin WASM with TinyGo... + +rem Check if TinyGo is available +tinygo version >nul 2>&1 +if errorlevel 1 ( + echo TinyGo not found! Please install TinyGo first. + echo Visit: https://tinygo.org/getting-started/install/ + pause + exit /b 1 +) + +rem Display TinyGo version +echo Using TinyGo version: +tinygo version + +rem Build the WASM file using TinyGo +echo Compiling main.go to go.wasm with TinyGo... +tinygo build -o go-format.wasm -target wasm main.go +if errorlevel 1 ( + echo Build failed! + pause + exit /b 1 +) + +echo Build successful! + +rem Show file size (Windows version) +for %%A in (go.wasm) do echo WASM file size: %%~zA bytes + +rem Copy to public directory for browser access +if exist "..\..\..\..\..\public" ( + copy go.wasm ..\..\..\..\..\public\go.wasm > nul + echo Copied to public directory + del go.wasm + echo Cleaned up local WASM file +) + +echo Go Prettier Plugin WASM (TinyGo) is ready! \ No newline at end of file diff --git a/frontend/src/common/prettier/plugins/go/build-tinygo.sh b/frontend/src/common/prettier/plugins/go/build-tinygo.sh new file mode 100644 index 0000000..fc3a007 --- /dev/null +++ b/frontend/src/common/prettier/plugins/go/build-tinygo.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Build script for Go Prettier Plugin WASM using TinyGo +# This script compiles the Go code to WebAssembly for browser environment + +echo "Building Go Prettier Plugin WASM with TinyGo..." + +# Check if TinyGo is available +if ! command -v tinygo &> /dev/null; then + echo "TinyGo not found! Please install TinyGo first." + echo "Visit: https://tinygo.org/getting-started/install/" + exit 1 +fi + +# Display TinyGo version +echo "Using TinyGo version: $(tinygo version)" + +# Build the WASM file using TinyGo +echo "Compiling main.go to go.wasm with TinyGo..." +tinygo build -o go-format.wasm -target wasm main.go + +if [ $? -ne 0 ]; then + echo "Build failed!" + exit 1 +fi + +echo "Build successful!" +echo "WASM file size: $(du -h go-format.wasm | cut -f1)" + +# Copy to public directory for browser access +if [ -d "../../../../../public" ]; then + cp go-format.wasm ../../../../../public/go-format.wasm + echo "Copied to public directory" + rm go-format.wasm + echo "Cleaned up local WASM file" +fi + +echo "Go Prettier Plugin WASM (TinyGo) is ready!" diff --git a/frontend/src/common/prettier/plugins/go/build.bat b/frontend/src/common/prettier/plugins/go/build.bat index da4161e..f85bc27 100644 --- a/frontend/src/common/prettier/plugins/go/build.bat +++ b/frontend/src/common/prettier/plugins/go/build.bat @@ -1,32 +1,43 @@ @echo off -rem Build script for Go Prettier Plugin WASM -rem This script compiles the Go code to WebAssembly +rem Build script for Go Prettier Plugin WASM using native Go +rem This script compiles the Go code to WebAssembly for browser environment -echo 🔨 Building Go Prettier Plugin WASM... +echo Building Go Prettier Plugin WASM with native Go... -rem Set WASM build environment +rem Check if Go is available +go version >nul 2>&1 +if %ERRORLEVEL% NEQ 0 ( + echo Go not found! Please install Go 1.21+ first. + echo Visit: https://golang.org/dl/ + pause + exit /b 1 +) + +rem Set WASM build environment for browser (js/wasm) set GOOS=js set GOARCH=wasm -rem Build the WASM file -echo Compiling main.go to go.wasm... -go build -o go.wasm main.go +rem Build the WASM file using native Go +echo Compiling main.go to go.wasm with Go... +go build -o go-format.wasm main.go if %ERRORLEVEL% EQU 0 ( - echo ✅ Build successful! + echo Build successful! rem Show file size (Windows version) - for %%A in (go.wasm) do echo 📊 WASM file size: %%~zA bytes + for %%A in (go.wasm) do echo WASM file size: %%~zA bytes rem Copy to public directory for browser access if exist "..\..\..\..\..\public" ( copy go.wasm ..\..\..\..\..\public\go.wasm > nul - echo 📋 Copied to public directory + echo Copied to public directory + del go.wasm + echo Cleaned up local WASM file ) - echo 🎉 Go Prettier Plugin WASM is ready! + echo Go Prettier Plugin WASM is ready! ) else ( - echo ❌ Build failed! + echo Build failed! pause exit /b 1 -) +) \ No newline at end of file diff --git a/frontend/src/common/prettier/plugins/go/build.sh b/frontend/src/common/prettier/plugins/go/build.sh index 0c04e6c..c6c6fc3 100644 --- a/frontend/src/common/prettier/plugins/go/build.sh +++ b/frontend/src/common/prettier/plugins/go/build.sh @@ -1,30 +1,42 @@ #!/bin/bash -# Build script for Go Prettier Plugin WASM -# This script compiles the Go code to WebAssembly +# Build script for Go Prettier Plugin WASM using native Go +# This script compiles the Go code to WebAssembly for browser environment -echo "🔨 Building Go Prettier Plugin WASM..." +echo "Building Go Prettier Plugin WASM with native Go..." -# Set WASM build environment +# Check if Go is available +if ! command -v go &> /dev/null; then + echo "Go not found! Please install Go 1.21+ first." + echo "Visit: https://golang.org/dl/" + exit 1 +fi + +# Display Go version +echo "Using Go version: $(go version)" + +# Set WASM build environment for browser (js/wasm) export GOOS=js export GOARCH=wasm -# Build the WASM file -echo "Compiling main.go to go.wasm..." -go build -o go.wasm main.go +# Build the WASM file using native Go +echo "Compiling main.go to go.wasm with Go..." +go build -o go-format.wasm main.go if [ $? -eq 0 ]; then - echo "✅ Build successful!" - echo "📊 WASM file size: $(du -h go.wasm | cut -f1)" + echo "Build successful!" + echo "WASM file size: $(du -h go-format.wasm | cut -f1)" # Copy to public directory for browser access if [ -d "../../../../../public" ]; then - cp go.wasm ../../../../../public/go.wasm - echo "📋 Copied to public directory" + cp go-format.wasm ../../../../../public/go-format.wasm + echo "Copied to public directory" + rm go-format.wasm + echo "Cleaned up local WASM file" fi - echo "🎉 Go Prettier Plugin WASM is ready!" + echo "Go Prettier Plugin WASM is ready!" else - echo "❌ Build failed!" + echo "Build failed!" exit 1 -fi +fi \ No newline at end of file diff --git a/frontend/src/common/prettier/plugins/go/go.mjs b/frontend/src/common/prettier/plugins/go/go.mjs index 18ebd57..fdf4037 100644 --- a/frontend/src/common/prettier/plugins/go/go.mjs +++ b/frontend/src/common/prettier/plugins/go/go.mjs @@ -1,244 +1,142 @@ /** - * Go Prettier Plugin - Universal Implementation - * WebAssembly-based Go code formatter for Prettier - * Supports both Node.js and Browser environments + * @fileoverview Go Prettier Format Plugin + * A Prettier plugin for formatting Go code using WebAssembly. + * This plugin leverages Go's native formatting capabilities through WASM. */ +import "./wasm_exec.js" +/** @type {Promise|null} */ +let initializePromise; -let initializePromise = null; - -// Environment detection -const isNode = () => { - return typeof process !== 'undefined' && - process.versions != null && - process.versions.node != null; -}; - -const isBrowser = () => { - return typeof window !== 'undefined' && - typeof document !== 'undefined'; -}; - -// Node.js WASM loading -const loadWasmNode = async () => { - try { - const fs = await import('fs'); - const path = await import('path'); - const { fileURLToPath } = await import('url'); - - const __filename = fileURLToPath(import.meta.url); - const __dirname = path.dirname(__filename); - const wasmPath = path.join(__dirname, 'go.wasm'); - - return fs.readFileSync(wasmPath); - } catch (error) { - console.error('Node.js WASM loading failed:', error); - throw error; - } -}; - -// Browser WASM loading -const loadWasmBrowser = async () => { - try { - const response = await fetch('/go.wasm'); - if (!response.ok) { - throw new Error(`Failed to load WASM file: ${response.status} ${response.statusText}`); - } - return await response.arrayBuffer(); - } catch (error) { - console.error('Browser WASM loading failed:', error); - throw error; - } -}; - -// Node.js Go runtime initialization -const initGoRuntimeNode = async () => { - if (globalThis.Go) return; - - try { - // Dynamic import of wasm_exec.js for Node.js - const { createRequire } = await import('module'); - const require = createRequire(import.meta.url); - const path = await import('path'); - const { fileURLToPath } = await import('url'); - - const __filename = fileURLToPath(import.meta.url); - const __dirname = path.dirname(__filename); - - // Load wasm_exec.js - const wasmExecPath = path.join(__dirname, 'wasm_exec.js'); - require(wasmExecPath); - - if (!globalThis.Go) { - throw new Error('Go WASM runtime not available after loading wasm_exec.js'); - } - } catch (error) { - console.error('Node.js Go runtime initialization failed:', error); - throw error; - } -}; - -// Browser Go runtime initialization -const initGoRuntimeBrowser = async () => { - // 总是重新初始化,因为可能存在版本不兼容问题 - try { - // 移除旧的 Go 运行时 - delete globalThis.Go; - - // 动态导入本地的 wasm_exec.js 内容 - const wasmExecResponse = await fetch('/wasm_exec.js'); - if (!wasmExecResponse.ok) { - throw new Error(`Failed to fetch wasm_exec.js: ${wasmExecResponse.status}`); - } - - const wasmExecCode = await wasmExecResponse.text(); - - // 在全局作用域中执行 wasm_exec.js 代码 - const script = document.createElement('script'); - script.textContent = wasmExecCode; - document.head.appendChild(script); - - // 等待一小段时间确保脚本执行完成 - await new Promise(resolve => setTimeout(resolve, 100)); - - if (!globalThis.Go) { - throw new Error('Go WASM runtime not available after executing wasm_exec.js'); - } - - console.log('Go runtime initialized successfully'); - } catch (error) { - console.error('Browser Go runtime initialization failed:', error); - throw error; - } -}; - -// Universal initialization -const initialize = async () => { - if (initializePromise) return initializePromise; - - initializePromise = (async () => { - let wasmBuffer; - - console.log('Starting Go WASM initialization...'); - - // Environment-specific initialization - if (isNode()) { - console.log('Initializing for Node.js environment'); - await initGoRuntimeNode(); - wasmBuffer = await loadWasmNode(); - } else if (isBrowser()) { - console.log('Initializing for Browser environment'); - await initGoRuntimeBrowser(); - wasmBuffer = await loadWasmBrowser(); - } else { - throw new Error('Unsupported environment: neither Node.js nor Browser detected'); - } - - console.log('Creating Go instance...'); - const go = new globalThis.Go(); - - // 详细检查 importObject - console.log('Go import object keys:', Object.keys(go.importObject)); - if (go.importObject.gojs) { - console.log('gojs import keys:', Object.keys(go.importObject.gojs)); - console.log('scheduleTimeoutEvent type:', typeof go.importObject.gojs['runtime.scheduleTimeoutEvent']); - } - - console.log('Instantiating WebAssembly module...'); - - try { - const { instance } = await WebAssembly.instantiate(wasmBuffer, go.importObject); - console.log('WebAssembly instantiation successful'); - - console.log('Running Go program...'); - // Run Go program (don't await as it's a long-running service) - go.run(instance).catch(err => { - console.error('Go WASM program exit error:', err); - }); - } catch (instantiateError) { - console.error('WebAssembly instantiation failed:', instantiateError); - console.error('Error details:', { - message: instantiateError.message, - name: instantiateError.name, - stack: instantiateError.stack - }); - throw instantiateError; - } - - // Wait for Go program to initialize and expose formatGo function - console.log('Waiting for formatGo function to be available...'); - let retries = 0; - const maxRetries = 20; // 增加重试次数 - - while (typeof globalThis.formatGo !== 'function' && retries < maxRetries) { - await new Promise(resolve => setTimeout(resolve, 200)); // 增加等待时间 - retries++; - if (retries % 5 === 0) { - console.log(`Waiting for formatGo function... (${retries}/${maxRetries})`); - } - } - - if (typeof globalThis.formatGo !== 'function') { - throw new Error('Go WASM module not properly initialized - formatGo function not available after 20 retries'); - } - - console.log('Go WASM initialization completed successfully'); - })(); - +/** + * Initializes the Go WebAssembly module for formatting Go code. + * This function sets up the WASM runtime and makes the formatGo function + * available on the global object. + * + * @async + * @function initialize + * @returns {Promise} A promise that resolves when the WASM module is ready + * @throws {Error} If the WASM file cannot be loaded or instantiated + */ +function initialize() { + if (initializePromise) { return initializePromise; -}; + } -export const languages = [ - { - name: "Go", - parsers: ["go-format"], - extensions: [".go"], - vscodeLanguageIds: ["go"], - }, + initializePromise = (async () => { + + const go = new TinyGo(); + + // Load WASM file from browser + const response = await fetch('/go-format.wasm'); + if (!response.ok) { + throw new Error(`Failed to load WASM file: ${response.status} ${response.statusText}`); + } + const wasmBuffer = await response.arrayBuffer(); + + const { instance } = await WebAssembly.instantiate( + wasmBuffer, + go.importObject + ); + + // go.run returns a promise that resolves when the go program exits. + // Since our program is a long-running service (it exposes a function and waits), + // we don't await it. + go.run(instance); + + // The `formatGo` function is now available on the global object. + })(); + + return initializePromise; +} + +/** + * Prettier language configuration for Go. + * Defines the language settings, file extensions, and parser mappings. + * + * @type {Array} + * @property {string} name - The display name of the language + * @property {string[]} parsers - Array of parser names for this language + * @property {string[]} extensions - File extensions associated with this language + * @property {string[]} vscodeLanguageIds - VSCode language identifier mappings + */ +const languages = [ + { + name: "Go", + parsers: ["go-format"], + extensions: [".go"], + vscodeLanguageIds: ["go"], + }, ]; -export const parsers = { - "go-format": { - parse: (text) => text, - astFormat: "go-format", - locStart: (node) => 0, - locEnd: (node) => node.length, +/** + * Prettier parser configuration for Go. + * Defines how Go source code should be parsed and processed. + * + * @type {Object} + * @property {Object} go-format - Go language parser configuration + * @property {Function} go-format.parse - Parser function that returns the input text as-is + * @property {string} go-format.astFormat - AST format identifier for the printer + * @property {Function} go-format.locStart - Function to get the start location of a node + * @property {Function} go-format.locEnd - Function to get the end location of a node + */ +const parsers = { + "go-format": { + /** + * Parse Go source code. For this plugin, we pass through the text as-is + * since the actual formatting is handled by the Go WASM module. + * + * @param {string} text - The Go source code to parse + * @returns {string} The input text unchanged + */ + parse: (text) => text, + astFormat: "go-format", + // These are required for Prettier to work + /** + * Get the start location of a node in the source code. + * + * @param {string} node - The node (in this case, the source text) + * @returns {number} Always returns 0 as we treat the entire text as one node + */ + locStart: (node) => 0, + /** + * Get the end location of a node in the source code. + * + * @param {string} node - The node (in this case, the source text) + * @returns {number} The length of the text + */ + locEnd: (node) => node.length, + }, +}; + +/** + * Prettier printer configuration for Go. + * Defines how the parsed Go AST should be formatted back to text. + * + * @type {Object} + * @property {Object} go-format - Go formatting printer configuration + * @property {Function} go-format.print - Async function that formats Go code + */ +const printers = { + "go-format": { + /** + * Format Go source code using the WebAssembly Go formatter. + * This function initializes the WASM module if needed and calls the + * global formatGo function exposed by the Go program. + * + * @async + * @param {Object} path - Prettier's path object containing the source code + * @param {Function} path.getValue - Function to get the current node value + * @returns {Promise} The formatted Go source code + * @throws {Error} If the WASM module fails to initialize or format the code + */ + print: async (path) => { + // The WASM module must be initialized before we can format. + await initialize(); + const text = path.getValue(); + // The `formatGo` function is exposed on the global object by our Go program. + return globalThis.formatGo(text); }, + }, }; -export const printers = { - "go-format": { - print: (path) => { - const text = path.getValue(); - - if (typeof globalThis.formatGo !== 'function') { - // 如果 formatGo 函数不可用,尝试初始化 - initialize().then(() => { - // 初始化完成后,formatGo 应该可用 - }).catch(err => { - console.error('Go WASM initialization failed:', err); - }); - - // 如果还是不可用,返回原始文本 - return text; - } - - try { - return globalThis.formatGo(text); - } catch (error) { - console.error('Go formatting failed:', error); - // 返回原始文本而不是抛出错误 - return text; - } - }, - }, -}; - -// Export initialize function for manual initialization -export { initialize }; - -// Default export for Prettier plugin compatibility -export default { - languages, - parsers, - printers -}; +export default { languages, parsers, printers, initialize }; \ No newline at end of file diff --git a/frontend/src/common/prettier/plugins/go/go.wasm b/frontend/src/common/prettier/plugins/go/go.wasm deleted file mode 100644 index 10a967e..0000000 Binary files a/frontend/src/common/prettier/plugins/go/go.wasm and /dev/null differ diff --git a/frontend/src/common/prettier/plugins/go/main.go b/frontend/src/common/prettier/plugins/go/main.go index 3496cd9..44ed814 100644 --- a/frontend/src/common/prettier/plugins/go/main.go +++ b/frontend/src/common/prettier/plugins/go/main.go @@ -4,8 +4,8 @@ // functionality for the Prettier plugin. This package exposes the formatGo function // to JavaScript, enabling web-based Go code formatting using Go's built-in format package. // -// The module is designed to be compiled to WebAssembly and loaded in Node.js -// environments as part of the go-prettier-format plugin. +// The module is designed to be compiled to WebAssembly using native Go (GOOS=js GOARCH=wasm) +// and loaded in browser environments as part of the Go Prettier plugin. package main import ( diff --git a/frontend/src/common/prettier/plugins/go/wasm_exec.js b/frontend/src/common/prettier/plugins/go/wasm_exec.js index bc6f210..aad5ef4 100644 --- a/frontend/src/common/prettier/plugins/go/wasm_exec.js +++ b/frontend/src/common/prettier/plugins/go/wasm_exec.js @@ -1,26 +1,53 @@ // Copyright 2018 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. - -"use strict"; +// +// This file has been modified for use by the TinyGo compiler. (() => { + // Map multiple JavaScript environments to a single common API, + // preferring web standards over Node.js API. + // + // Environments considered: + // - Browsers + // - Node.js + // - Electron + // - Parcel + + if (typeof global !== "undefined") { + // global already exists + } else if (typeof window !== "undefined") { + window.global = window; + } else if (typeof self !== "undefined") { + self.global = self; + } else { + throw new Error("cannot export Go (neither global, window nor self is defined)"); + } + + if (!global.require && typeof require !== "undefined") { + global.require = require; + } + + if (!global.fs && global.require) { + global.fs = require("node:fs"); + } + const enosys = () => { const err = new Error("not implemented"); err.code = "ENOSYS"; return err; }; - if (!globalThis.fs) { + if (!global.fs) { let outputBuf = ""; - globalThis.fs = { + global.fs = { constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused writeSync(fd, buf) { outputBuf += decoder.decode(buf); const nl = outputBuf.lastIndexOf("\n"); if (nl != -1) { - console.log(outputBuf.substring(0, nl)); - outputBuf = outputBuf.substring(nl + 1); + console.log(outputBuf.substr(0, nl)); + outputBuf = outputBuf.substr(nl + 1); } return buf.length; }, @@ -58,8 +85,8 @@ }; } - if (!globalThis.process) { - globalThis.process = { + if (!global.process) { + global.process = { getuid() { return -1; }, getgid() { return -1; }, geteuid() { return -1; }, @@ -73,58 +100,53 @@ } } - if (!globalThis.crypto) { - throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)"); + if (!global.crypto) { + const nodeCrypto = require("node:crypto"); + global.crypto = { + getRandomValues(b) { + nodeCrypto.randomFillSync(b); + }, + }; } - if (!globalThis.performance) { - throw new Error("globalThis.performance is not available, polyfill required (performance.now only)"); + if (!global.performance) { + global.performance = { + now() { + const [sec, nsec] = process.hrtime(); + return sec * 1000 + nsec / 1000000; + }, + }; } - if (!globalThis.TextEncoder) { - throw new Error("globalThis.TextEncoder is not available, polyfill required"); + if (!global.TextEncoder) { + global.TextEncoder = require("node:util").TextEncoder; } - if (!globalThis.TextDecoder) { - throw new Error("globalThis.TextDecoder is not available, polyfill required"); + if (!global.TextDecoder) { + global.TextDecoder = require("node:util").TextDecoder; } + // End of polyfills for common API. + const encoder = new TextEncoder("utf-8"); const decoder = new TextDecoder("utf-8"); + let reinterpretBuf = new DataView(new ArrayBuffer(8)); + var logLine = []; + const wasmExit = {}; // thrown to exit via proc_exit (not an error) - globalThis.Go = class { + global.TinyGo = class { constructor() { - this.argv = ["js"]; - this.env = {}; - this.exit = (code) => { - if (code !== 0) { - console.warn("exit code:", code); - } - }; - this._exitPromise = new Promise((resolve) => { - this._resolveExitPromise = resolve; - }); - this._pendingEvent = null; - this._scheduledTimeouts = new Map(); + this._callbackTimeouts = new Map(); this._nextCallbackTimeoutID = 1; - const setInt64 = (addr, v) => { - this.mem.setUint32(addr + 0, v, true); - this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true); + const mem = () => { + // The buffer may change when requesting more memory. + return new DataView(this._inst.exports.memory.buffer); } - 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); - return low + high * 4294967296; - } - - const loadValue = (addr) => { - const f = this.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; } @@ -132,69 +154,77 @@ return f; } - const id = this.mem.getUint32(addr, true); + const id = v_ref & 0xffffffffn; return this._values[id]; } - const storeValue = (addr, v) => { - const nanHead = 0x7FF80000; - if (typeof v === "number" && v !== 0) { + 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)) { - this.mem.setUint32(addr + 4, nanHead, true); - this.mem.setUint32(addr, 0, true); - return; + return nanHead << 32n; } - this.mem.setFloat64(addr, v, true); - return; + if (v === 0) { + return (nanHead << 32n) | 1n; + } + reinterpretBuf.setFloat64(0, v, true); + return reinterpretBuf.getBigInt64(0, true); } - if (v === undefined) { - this.mem.setFloat64(addr, 0, true); - return; + switch (v) { + case undefined: + return 0n; + case null: + return (nanHead << 32n) | 2n; + case true: + return (nanHead << 32n) | 3n; + case false: + 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 = 0; + let typeFlag = 1n; switch (typeof v) { - case "object": - if (v !== null) { - typeFlag = 1; - } - break; case "string": - typeFlag = 2; + typeFlag = 2n; break; case "symbol": - typeFlag = 3; + typeFlag = 3n; break; case "function": - typeFlag = 4; + typeFlag = 4n; break; } - this.mem.setUint32(addr + 4, nanHead | typeFlag, true); - this.mem.setUint32(addr, id, true); + return id | ((nanHead | typeFlag) << 32n); } - const loadSlice = (addr) => { - const array = getInt64(addr + 0); - const len = getInt64(addr + 8); - return new Uint8Array(this._inst.exports.mem.buffer, array, len); + const storeValue = (addr, v) => { + let v_ref = boxValue(v); + mem().setBigUint64(addr, v_ref, true); } - const loadSliceOfValues = (addr) => { - const array = getInt64(addr + 0); - const len = getInt64(addr + 8); + const loadSlice = (array, len, cap) => { + return new Uint8Array(this._inst.exports.memory.buffer, array, len); + } + + const loadSliceOfValues = (array, len, cap) => { const a = new Array(len); for (let i = 0; i < len; i++) { a[i] = loadValue(array + i * 8); @@ -202,347 +232,287 @@ return a; } - const loadString = (addr) => { - const saddr = getInt64(addr + 0); - const len = getInt64(addr + 8); - return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); + const loadString = (ptr, len) => { + return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr, len)); } const timeOrigin = Date.now() - performance.now(); this.importObject = { - _gotest: { - add: (a, b) => a + b, + wasi_snapshot_preview1: { + // https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_write + fd_write: function(fd, iovs_ptr, iovs_len, nwritten_ptr) { + let nwritten = 0; + if (fd == 1) { + for (let iovs_i=0; iovs_i 0, // dummy + fd_fdstat_get: () => 0, // dummy + fd_seek: () => 0, // dummy + proc_exit: (code) => { + this.exited = true; + this.exitCode = code; + this._resolveExitPromise(); + throw wasmExit; + }, + random_get: (bufPtr, bufLen) => { + crypto.getRandomValues(loadSlice(bufPtr, bufLen)); + return 0; + }, }, 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). - // This changes the SP, thus we have to update the SP used by the imported function. - - // func wasmExit(code int32) - "runtime.wasmExit": (sp) => { - sp >>>= 0; - const code = this.mem.getInt32(sp + 8, true); - this.exited = true; - delete this._inst; - delete this._values; - delete this._goRefCounts; - delete this._ids; - delete this._idPool; - this.exit(code); + // func ticks() int64 + "runtime.ticks": () => { + return BigInt((timeOrigin + performance.now()) * 1e6); }, - // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) - "runtime.wasmWrite": (sp) => { - sp >>>= 0; - const fd = getInt64(sp + 8); - const p = getInt64(sp + 16); - const n = this.mem.getInt32(sp + 24, true); - fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); - }, - - // func resetMemoryDataView() - "runtime.resetMemoryDataView": (sp) => { - sp >>>= 0; - this.mem = new DataView(this._inst.exports.mem.buffer); - }, - - // func nanotime1() int64 - "runtime.nanotime1": (sp) => { - sp >>>= 0; - setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); - }, - - // func walltime() (sec int64, nsec int32) - "runtime.walltime": (sp) => { - sp >>>= 0; - const msec = (new Date).getTime(); - setInt64(sp + 8, msec / 1000); - this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); - }, - - // func scheduleTimeoutEvent(delay int64) int32 - "runtime.scheduleTimeoutEvent": (sp) => { - sp >>>= 0; - const id = this._nextCallbackTimeoutID; - this._nextCallbackTimeoutID++; - this._scheduledTimeouts.set(id, setTimeout( - () => { - this._resume(); - while (this._scheduledTimeouts.has(id)) { - // for some reason Go failed to register the timeout event, log and try again - // (temporary workaround for https://github.com/golang/go/issues/28975) - console.warn("scheduleTimeoutEvent: missed timeout event"); - this._resume(); - } - }, - getInt64(sp + 8), - )); - this.mem.setInt32(sp + 16, id, true); - }, - - // func clearTimeoutEvent(id int32) - "runtime.clearTimeoutEvent": (sp) => { - sp >>>= 0; - const id = this.mem.getInt32(sp + 8, true); - clearTimeout(this._scheduledTimeouts.get(id)); - this._scheduledTimeouts.delete(id); - }, - - // func getRandomData(r []byte) - "runtime.getRandomData": (sp) => { - sp >>>= 0; - crypto.getRandomValues(loadSlice(sp + 8)); + // func sleepTicks(timeout int64) + "runtime.sleepTicks": (timeout) => { + // Do not sleep, only reactivate scheduler after the given timeout. + setTimeout(() => { + if (this.exited) return; + try { + this._inst.exports.go_scheduler(); + } catch (e) { + if (e !== wasmExit) throw e; + } + }, Number(timeout)/1e6); }, // func finalizeRef(v ref) - "syscall/js.finalizeRef": (sp) => { - sp >>>= 0; - const id = this.mem.getUint32(sp + 8, true); - this._goRefCounts[id]--; - if (this._goRefCounts[id] === 0) { - const v = this._values[id]; - this._values[id] = null; - this._ids.delete(v); - this._idPool.push(id); + "syscall/js.finalizeRef": (v_ref) => { + // Note: TinyGo does not support finalizers so this is only called + // for one specific case, by js.go:jsString. and can/might leak memory. + const id = v_ref & 0xffffffffn; + if (this._goRefCounts?.[id] !== undefined) { + this._goRefCounts[id]--; + if (this._goRefCounts[id] === 0) { + const v = this._values[id]; + this._values[id] = null; + this._ids.delete(v); + this._idPool.push(id); + } + } else { + console.error("syscall/js.finalizeRef: unknown id", id); } }, // func stringVal(value string) ref - "syscall/js.stringVal": (sp) => { - sp >>>= 0; - storeValue(sp + 24, loadString(sp + 8)); + "syscall/js.stringVal": (value_ptr, value_len) => { + value_ptr >>>= 0; + const s = loadString(value_ptr, value_len); + return boxValue(s); }, // func valueGet(v ref, p string) ref - "syscall/js.valueGet": (sp) => { - sp >>>= 0; - const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); - sp = this._inst.exports.getsp() >>> 0; // see comment above - storeValue(sp + 32, result); + "syscall/js.valueGet": (v_ref, p_ptr, p_len) => { + let prop = loadString(p_ptr, p_len); + 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": (sp) => { - sp >>>= 0; - Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); + "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 = unboxValue(x_ref); + Reflect.set(v, p, x); }, // func valueDelete(v ref, p string) - "syscall/js.valueDelete": (sp) => { - sp >>>= 0; - Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); + "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": (sp) => { - sp >>>= 0; - storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); + "syscall/js.valueIndex": (v_ref, i) => { + return boxValue(Reflect.get(unboxValue(v_ref), i)); }, // valueSetIndex(v ref, i int, x ref) - "syscall/js.valueSetIndex": (sp) => { - sp >>>= 0; - Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); + "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": (sp) => { - sp >>>= 0; + "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 { - const v = loadValue(sp + 8); - const m = Reflect.get(v, loadString(sp + 16)); - const args = loadSliceOfValues(sp + 32); - const result = Reflect.apply(m, v, args); - sp = this._inst.exports.getsp() >>> 0; // see comment above - storeValue(sp + 56, result); - this.mem.setUint8(sp + 64, 1); + const m = Reflect.get(v, name); + storeValue(ret_addr, Reflect.apply(m, v, args)); + mem().setUint8(ret_addr + 8, 1); } catch (err) { - sp = this._inst.exports.getsp() >>> 0; // see comment above - storeValue(sp + 56, err); - this.mem.setUint8(sp + 64, 0); + storeValue(ret_addr, err); + mem().setUint8(ret_addr + 8, 0); } }, // func valueInvoke(v ref, args []ref) (ref, bool) - "syscall/js.valueInvoke": (sp) => { - sp >>>= 0; + "syscall/js.valueInvoke": (ret_addr, v_ref, args_ptr, args_len, args_cap) => { try { - const v = loadValue(sp + 8); - const args = loadSliceOfValues(sp + 16); - const result = Reflect.apply(v, undefined, args); - sp = this._inst.exports.getsp() >>> 0; // see comment above - storeValue(sp + 40, result); - this.mem.setUint8(sp + 48, 1); + 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); } catch (err) { - sp = this._inst.exports.getsp() >>> 0; // see comment above - storeValue(sp + 40, err); - this.mem.setUint8(sp + 48, 0); + storeValue(ret_addr, err); + mem().setUint8(ret_addr + 8, 0); } }, // func valueNew(v ref, args []ref) (ref, bool) - "syscall/js.valueNew": (sp) => { - sp >>>= 0; + "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 { - const v = loadValue(sp + 8); - const args = loadSliceOfValues(sp + 16); - const result = Reflect.construct(v, args); - sp = this._inst.exports.getsp() >>> 0; // see comment above - storeValue(sp + 40, result); - this.mem.setUint8(sp + 48, 1); + storeValue(ret_addr, Reflect.construct(v, args)); + mem().setUint8(ret_addr + 8, 1); } catch (err) { - sp = this._inst.exports.getsp() >>> 0; // see comment above - storeValue(sp + 40, err); - this.mem.setUint8(sp + 48, 0); + storeValue(ret_addr, err); + mem().setUint8(ret_addr+ 8, 0); } }, // func valueLength(v ref) int - "syscall/js.valueLength": (sp) => { - sp >>>= 0; - setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); + "syscall/js.valueLength": (v_ref) => { + return unboxValue(v_ref).length; }, // valuePrepareString(v ref) (ref, int) - "syscall/js.valuePrepareString": (sp) => { - sp >>>= 0; - const str = encoder.encode(String(loadValue(sp + 8))); - storeValue(sp + 16, str); - setInt64(sp + 24, str.length); + "syscall/js.valuePrepareString": (ret_addr, v_ref) => { + const s = String(unboxValue(v_ref)); + const str = encoder.encode(s); + storeValue(ret_addr, str); + mem().setInt32(ret_addr + 8, str.length, true); }, // valueLoadString(v ref, b []byte) - "syscall/js.valueLoadString": (sp) => { - sp >>>= 0; - const str = loadValue(sp + 8); - loadSlice(sp + 16).set(str); + "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": (sp) => { - sp >>>= 0; - this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0); + "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": (sp) => { - sp >>>= 0; - const dst = loadSlice(sp + 8); - const src = loadValue(sp + 32); + "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 = unboxValue(src_ref); if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { - this.mem.setUint8(sp + 48, 0); + mem().setUint8(returned_status_addr, 0); // Return "not ok" status return; } const toCopy = src.subarray(0, dst.length); dst.set(toCopy); - setInt64(sp + 40, toCopy.length); - this.mem.setUint8(sp + 48, 1); + mem().setUint32(num_bytes_copied_addr, toCopy.length, true); + mem().setUint8(returned_status_addr, 1); // Return "ok" status }, - // func copyBytesToJS(dst ref, src []byte) (int, bool) - "syscall/js.copyBytesToJS": (sp) => { - sp >>>= 0; - const dst = loadValue(sp + 8); - const src = loadSlice(sp + 16); + // 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, 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 = unboxValue(dst_ref); + const src = loadSlice(src_addr, src_len); if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { - this.mem.setUint8(sp + 48, 0); + mem().setUint8(returned_status_addr, 0); // Return "not ok" status return; } const toCopy = src.subarray(0, dst.length); dst.set(toCopy); - setInt64(sp + 40, toCopy.length); - this.mem.setUint8(sp + 48, 1); - }, - - "debug": (value) => { - console.log(value); + 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) { - if (!(instance instanceof WebAssembly.Instance)) { - throw new Error("Go.run: WebAssembly.Instance expected"); - } this._inst = instance; - this.mem = new DataView(this._inst.exports.mem.buffer); this._values = [ // JS values that Go currently has references to, indexed by reference id NaN, 0, null, true, false, - globalThis, + global, this, ]; - this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id - this._ids = new Map([ // mapping from JS values to reference ids - [0, 1], - [null, 2], - [true, 3], - [false, 4], - [globalThis, 5], - [this, 6], - ]); - this._idPool = []; // unused ids that have been garbage collected - this.exited = false; // whether the Go program has exited + this._goRefCounts = []; // number of references that Go has to a JS value, indexed by reference id + this._ids = new Map(); // mapping from JS values to reference ids + this._idPool = []; // unused ids that have been garbage collected + this.exited = false; // whether the Go program has exited + this.exitCode = 0; - // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. - let offset = 4096; + if (this._inst.exports._start) { + let exitPromise = new Promise((resolve, reject) => { + this._resolveExitPromise = resolve; + }); - const strPtr = (str) => { - const ptr = offset; - const bytes = encoder.encode(str + "\0"); - new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes); - offset += bytes.length; - if (offset % 8 !== 0) { - offset += 8 - (offset % 8); + // Run program, but catch the wasmExit exception that's thrown + // to return back here. + try { + this._inst.exports._start(); + } catch (e) { + if (e !== wasmExit) throw e; } - return ptr; - }; - const argc = this.argv.length; - - const argvPtrs = []; - this.argv.forEach((arg) => { - argvPtrs.push(strPtr(arg)); - }); - argvPtrs.push(0); - - const keys = Object.keys(this.env).sort(); - keys.forEach((key) => { - argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); - }); - argvPtrs.push(0); - - const argv = offset; - argvPtrs.forEach((ptr) => { - this.mem.setUint32(offset, ptr, true); - this.mem.setUint32(offset + 4, 0, true); - offset += 8; - }); - - // The linker guarantees global data starts from at least wasmMinDataAddr. - // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr. - const wasmMinDataAddr = 4096 + 8192; - if (offset >= wasmMinDataAddr) { - throw new Error("total length of command line and environment variables exceeds limit"); + await exitPromise; + return this.exitCode; + } else { + this._inst.exports._initialize(); } - - this._inst.exports.run(argc, argv); - if (this.exited) { - this._resolveExitPromise(); - } - await this._exitPromise; } _resume() { if (this.exited) { throw new Error("Go program has already exited"); } - this._inst.exports.resume(); + try { + this._inst.exports.resume(); + } catch (e) { + if (e !== wasmExit) throw e; + } if (this.exited) { this._resolveExitPromise(); } @@ -558,4 +528,26 @@ }; } } + + if ( + global.require && + global.require.main === module && + global.process && + global.process.versions && + !global.process.versions.electron + ) { + if (process.argv.length != 3) { + console.error("usage: go_js_wasm_exec [wasm binary] [arguments]"); + process.exit(1); + } + + const go = new Go(); + WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then(async (result) => { + let exitCode = await go.run(result.instance); + process.exit(exitCode); + }).catch((err) => { + console.error(err); + process.exit(1); + }); + } })(); diff --git a/go.mod b/go.mod index 17377e8..b3b6c3e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module voidraft -go 1.24.4 +go 1.25 require ( github.com/creativeprojects/go-selfupdate v1.5.0