From 97ec428acab34d84845f31f1ce731236c8f127b9 Mon Sep 17 00:00:00 2001 From: Ugric Date: Sat, 25 Mar 2023 00:45:13 +0000 Subject: [PATCH] make wasm work --- build | 2 +- build.bat | 4 +- run | 2 - run.bat | 5 - src/error.go | 8 +- src/import.go | 188 +++++++------ src/main.go | 31 ++- src/parseImport.go | 1 + src/translate.go | 1 - src/wasm.go | 71 +++++ wasm/argon_wasm.js | 680 +++++++++++++++++++++++++++++++++++++++++++++ wasm/index.html | 106 +++++++ 12 files changed, 984 insertions(+), 115 deletions(-) delete mode 100755 run delete mode 100644 run.bat create mode 100644 src/wasm.go create mode 100644 wasm/argon_wasm.js create mode 100644 wasm/index.html diff --git a/build b/build index c9fb28a..bf55fad 100755 --- a/build +++ b/build @@ -1 +1 @@ -go build -o bin/argon ./src \ No newline at end of file +GOOS=js GOARCH=wasm go build -o wasm/bin/argon.wasm ./src \ No newline at end of file diff --git a/build.bat b/build.bat index 9af9c18..a36fb7b 100644 --- a/build.bat +++ b/build.bat @@ -1,2 +1,4 @@ @echo off -go build -o bin/argon.exe ./src \ No newline at end of file +set GOOS=js +set GOARCH=wasm +go build -o wasm/bin/argon.wasm ./src \ No newline at end of file diff --git a/run b/run deleted file mode 100755 index 0994fe7..0000000 --- a/run +++ /dev/null @@ -1,2 +0,0 @@ -# run the go run command passing the path to the main.go file, with the working directory set to the bin folder. pass in the arguments -go run ./src $@ \ No newline at end of file diff --git a/run.bat b/run.bat deleted file mode 100644 index bbe3f9c..0000000 --- a/run.bat +++ /dev/null @@ -1,5 +0,0 @@ -@echo off - -:: run the go run command passing the path to the main.go file, with the working directory set to the bin folder. pass in the arguments - -go run ./src %* \ No newline at end of file diff --git a/src/error.go b/src/error.go index ff41ed3..3517784 100644 --- a/src/error.go +++ b/src/error.go @@ -2,6 +2,8 @@ package main import ( "fmt" + + "github.com/jwalton/go-supportscolor" ) type ArErr struct { @@ -19,5 +21,9 @@ func panicErr(err ArErr) { fmt.Println(" " + err.code) fmt.Println() } - fmt.Printf("\x1b[%dm%s\x1b[0m", 91, fmt.Sprint(err.TYPE, ": ", err.message, "\n")) + if supportscolor.Stdout().SupportsColor { + fmt.Printf("\x1b[%dm%s\x1b[0m", 91, fmt.Sprint(err.TYPE, ": ", err.message, "\n")) + } else { + fmt.Println(fmt.Sprint(err.TYPE, ": ", err.message)) + } } diff --git a/src/import.go b/src/import.go index 385fe8d..3254422 100644 --- a/src/import.go +++ b/src/import.go @@ -5,7 +5,6 @@ import ( "errors" "log" "os" - "path/filepath" ) var imported = make(map[string]ArObject) @@ -48,101 +47,108 @@ func readFile(path string) []UNPARSEcode { } func importMod(realpath string, origin string, main bool) (ArObject, ArErr) { - extention := filepath.Ext(realpath) - path := realpath - if extention == "" { - path += ".ar" + return ArObject{}, ArErr{ + TYPE: "Import Error", + message: "importing in WASM is currently not supported", + EXISTS: true, } - ex, err := os.Getwd() - if err != nil { - return ArObject{}, ArErr{TYPE: "Import Error", message: "Could not get working directory", EXISTS: true} - } - exc, err := os.Executable() - if err != nil { - return ArObject{}, ArErr{TYPE: "Import Error", message: "Could not get executable", EXISTS: true} - } - executable := filepath.Dir(exc) - isABS := filepath.IsAbs(path) - var pathsToTest []string - if isABS { - pathsToTest = []string{ - filepath.Join(path), - filepath.Join(realpath, "init.ar"), + /* + extention := filepath.Ext(realpath) + path := realpath + if extention == "" { + path += ".ar" } - } else { - pathsToTest = []string{ - filepath.Join(origin, realpath, "init.ar"), - filepath.Join(origin, path), - filepath.Join(origin, "modules", path), - filepath.Join(origin, "modules", realpath, "init.ar"), - filepath.Join(ex, path), - filepath.Join(ex, "modules", realpath, "init.ar"), - filepath.Join(ex, "modules", path), - filepath.Join(executable, "modules", realpath, "init.ar"), - filepath.Join(executable, "modules", path), + ex, err := os.Getwd() + if err != nil { + return ArObject{}, ArErr{TYPE: "Import Error", message: "Could not get working directory", EXISTS: true} } - } - - var p string - var found bool - for _, p = range pathsToTest { - if FileExists(p) { - found = true - break + exc, err := os.Executable() + if err != nil { + return ArObject{}, ArErr{TYPE: "Import Error", message: "Could not get executable", EXISTS: true} + } + executable := filepath.Dir(exc) + isABS := filepath.IsAbs(path) + var pathsToTest []string + if isABS { + pathsToTest = []string{ + filepath.Join(path), + filepath.Join(realpath, "init.ar"), + } + } else { + pathsToTest = []string{ + filepath.Join(origin, realpath, "init.ar"), + filepath.Join(origin, path), + filepath.Join(origin, "modules", path), + filepath.Join(origin, "modules", realpath, "init.ar"), + filepath.Join(ex, path), + filepath.Join(ex, "modules", realpath, "init.ar"), + filepath.Join(ex, "modules", path), + filepath.Join(executable, "modules", realpath, "init.ar"), + filepath.Join(executable, "modules", path), + } } - } - if !found { - return ArObject{}, ArErr{TYPE: "Import Error", message: "File does not exist: " + realpath, EXISTS: true} - } else if importing[p] { - return ArObject{}, ArErr{TYPE: "Import Error", message: "Circular import: " + realpath, EXISTS: true} - } else if _, ok := imported[p]; ok { - return imported[p], ArErr{} - } - importing[p] = true - codelines := readFile(p) + var p string + var found bool + for _, p = range pathsToTest { + if FileExists(p) { + found = true + break + } + } - translated, translationerr := translate(codelines) - if translationerr.EXISTS { - return ArObject{}, translationerr - } - ArgsArArray := []any{} - withoutarfile := []string{} - if len(Args) > 1 { - withoutarfile = Args[1:] - } - for _, arg := range withoutarfile { - ArgsArArray = append(ArgsArArray, arg) - } - global := newscope() - localvars := Map(anymap{ - "program": Map(anymap{ - "args": ArArray(ArgsArArray), - "origin": origin, - "import": builtinFunc{"import", func(args ...any) (any, ArErr) { - if len(args) != 1 { - return nil, ArErr{"Import Error", "Invalid number of arguments", 0, realpath, "", true} - } - if _, ok := args[0].(string); !ok { - return nil, ArErr{"Import Error", "Invalid argument type", 0, realpath, "", true} - } - return importMod(args[0].(string), filepath.Dir(p), false) - }}, - "cwd": ex, - "exc": exc, - "file": Map(anymap{ - "name": filepath.Base(p), - "path": p, + if !found { + return ArObject{}, ArErr{TYPE: "Import Error", message: "File does not exist: " + realpath, EXISTS: true} + } else if importing[p] { + return ArObject{}, ArErr{TYPE: "Import Error", message: "Circular import: " + realpath, EXISTS: true} + } else if _, ok := imported[p]; ok { + return imported[p], ArErr{} + } + importing[p] = true + codelines := readFile(p) + + translated, translationerr := translate(codelines) + if translationerr.EXISTS { + return ArObject{}, translationerr + } + ArgsArArray := []any{} + withoutarfile := []string{} + if len(Args) > 1 { + withoutarfile = Args[1:] + } + for _, arg := range withoutarfile { + ArgsArArray = append(ArgsArArray, arg) + } + global := newscope() + localvars := Map(anymap{ + "program": Map(anymap{ + "args": ArArray(ArgsArArray), + "origin": origin, + "import": builtinFunc{"import", func(args ...any) (any, ArErr) { + if len(args) != 1 { + return nil, ArErr{"Import Error", "Invalid number of arguments", 0, realpath, "", true} + } + if _, ok := args[0].(string); !ok { + return nil, ArErr{"Import Error", "Invalid argument type", 0, realpath, "", true} + } + return importMod(args[0].(string), filepath.Dir(p), false) + }}, + "cwd": ex, + "exc": exc, + "file": Map(anymap{ + "name": filepath.Base(p), + "path": p, + }), + "main": main, + "scope": global, }), - "main": main, - "scope": global, - }), - }) - _, runimeErr := ThrowOnNonLoop(run(translated, stack{vars, localvars, global})) - importing[p] = false - if runimeErr.EXISTS { - return ArObject{}, runimeErr - } - imported[p] = global - return global, ArErr{} + }) + _, runimeErr := ThrowOnNonLoop(run(translated, stack{vars, localvars, global})) + importing[p] = false + if runimeErr.EXISTS { + return ArObject{}, runimeErr + } + imported[p] = global + return global, ArErr{} + */ } diff --git a/src/main.go b/src/main.go index 1084ea9..8b1c2a8 100644 --- a/src/main.go +++ b/src/main.go @@ -2,6 +2,7 @@ package main import ( "os" + "syscall/js" ) // args without the program path @@ -17,17 +18,21 @@ func newscope() ArObject { } func main() { - ex, e := os.Getwd() - if e != nil { - panic(e) - } - if len(Args) == 0 { - shell() - os.Exit(0) - } - _, err := importMod(Args[0], ex, true) - if err.EXISTS { - panicErr(err) - os.Exit(1) - } + c := make(chan ArObject) + obj := js.Global().Get("Object").New() + obj.Set("eval", js.FuncOf(func(this js.Value, args []js.Value) interface{} { + code := "" + if len(args) > 0 { + code = args[0].String() + } + val, err := wasmRun(code) + if err.EXISTS { + panicErr(err) + return js.Null() + } + + return js.ValueOf(argonToJsValid(val)) + })) + js.Global().Set("Ar", obj) + <-c } diff --git a/src/parseImport.go b/src/parseImport.go index f54fa48..324318e 100644 --- a/src/parseImport.go +++ b/src/parseImport.go @@ -44,6 +44,7 @@ func parseGenericImport(code UNPARSEcode, index int, codeline []UNPARSEcode) (Ar } func runImport(importOBJ ArImport, stack stack, stacklevel int) (any, ArErr) { + return nil, ArErr{"Import Error", "importing in WASM is currently not supported", importOBJ.line, importOBJ.path, importOBJ.code, true} val, err := runVal(importOBJ.filePath, stack, stacklevel+1) val = ArValidToAny(val) if err.EXISTS { diff --git a/src/translate.go b/src/translate.go index ba69431..e6d13a2 100644 --- a/src/translate.go +++ b/src/translate.go @@ -44,7 +44,6 @@ func translateVal(code UNPARSEcode, index int, codelines []UNPARSEcode, isLine i return parseGenericImport(code, index, codelines) } } - if isLine >= 1 { if isDoWrap(code) { return parseDoWrap(code, index, codelines) diff --git a/src/wasm.go b/src/wasm.go new file mode 100644 index 0000000..0e14842 --- /dev/null +++ b/src/wasm.go @@ -0,0 +1,71 @@ +package main + +import ( + "strings" + "syscall/js" +) + +func argonToJsValid(argon any) any { + switch x := argon.(type) { + case number: + f, _ := x.Float64() + return f + case ArObject: + if x.TYPE == "array" { + arr := js.Global().Get("Array").New() + for i, v := range x.obj["__value__"].([]any) { + arr.SetIndex(i, argonToJsValid(v)) + } + return arr + } else if x.TYPE == "string" { + return x.obj["__value__"].(string) + } + + obj := js.Global().Get("Object").New() + for k, v := range x.obj { + obj.Set(anyToArgon(k, false, false, 3, 0, false, 0), argonToJsValid(v)) + } + return obj + case bool, string: + return x + default: + return nil + } +} + +func wasmRun(code string) (any, ArErr) { + lines := strings.Split(code, "\n") + codelines := []UNPARSEcode{} + for i := 0; i < len(lines); i++ { + codelines = append(codelines, UNPARSEcode{ + lines[i], + lines[i], + i + 1, + "", + }) + } + + translated, translationerr := translate(codelines) + if translationerr.EXISTS { + return nil, translationerr + } + global := newscope() + localvars := Map(anymap{ + "program": Map(anymap{ + "args": []any{}, + "origin": "", + "import": builtinFunc{"import", func(args ...any) (any, ArErr) { + return nil, ArErr{"Import Error", "Cannot Import in WASM", 0, "", "", true} + }}, + "cwd": "", + "exc": "", + "file": Map(anymap{ + "name": "", + "path": "", + }), + "main": true, + "scope": global, + }), + }) + return ThrowOnNonLoop(run(translated, stack{vars, localvars, global})) +} diff --git a/wasm/argon_wasm.js b/wasm/argon_wasm.js new file mode 100644 index 0000000..0db04ac --- /dev/null +++ b/wasm/argon_wasm.js @@ -0,0 +1,680 @@ +window.ArgonWASMRuntime = async (config = {}) => { + const term = config.console || console; + if (typeof global !== "undefined") { + } 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) { + const fs = require("fs"); + if ( + typeof fs === "object" && + fs !== null && + Object.keys(fs).length !== 0 + ) { + global.fs = fs; + } + } + const enosys = () => { + const err = new Error("not implemented"); + err.code = "ENOSYS"; + return err; + }; + if (!global.fs) { + let outputBuf = ""; + 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) { + term.log(outputBuf.slice(0, nl)); + outputBuf = outputBuf.slice(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 (!global.process) { + global.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 (!global.crypto && global.require) { + const nodeCrypto = require("crypto"); + global.crypto = { + getRandomValues(b) { + nodeCrypto.randomFillSync(b); + }, + }; + } + if (!global.crypto) { + throw new Error( + "global.crypto is not available, polyfill required (getRandomValues only)" + ); + } + if (!global.performance) { + global.performance = { + now() { + const [sec, nsec] = process.hrtime(); + return sec * 1000 + nsec / 1000000; + }, + }; + } + if (!global.TextEncoder && global.require) { + global.TextEncoder = require("util").TextEncoder; + } + if (!global.TextEncoder) { + throw new Error( + "global.TextEncoder is not available, polyfill required" + ); + } + if (!global.TextDecoder && global.require) { + global.TextDecoder = require("util").TextDecoder; + } + if (!global.TextDecoder) { + throw new Error( + "global.TextDecoder is not available, polyfill required" + ); + } + // End of polyfills for common API. + const encoder = new TextEncoder("utf-8"); + const decoder = new TextDecoder("utf-8"); + const Go = class { + constructor() { + this.argv = ["js"]; + this.env = {}; + this.exit = (code) => { + if (code !== 0) { + term.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 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 = { + go: { + // 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) + term.warn( + "scheduleTimeoutEvent: missed timeout event" + ); + this._resume(); + } + }, + getInt64(sp + 8) + 1 // setTimeout has been seen to fire up to 1 millisecond early + ) + ); + 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) => { + term.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, + 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], + [global, 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; + }); + this._inst.exports.run(argc, argv); + if (this.exited) { + this._resolveExitPromise(); + } + await this._exitPromise; + } + _resume() { + if (this.exited) { + (async () => { + await run(); + this._inst.exports.resume(); + })(); + return; + } + 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; + }; + } + }; + const go = new Go(); + const file = await fetch("bin/argon.wasm"); + const run = async () => { + const result = await WebAssembly.instantiateStreaming( + file.clone(), + go.importObject + ); + go.run(result.instance); + }; + await run(); +}; diff --git a/wasm/index.html b/wasm/index.html new file mode 100644 index 0000000..e77a0c9 --- /dev/null +++ b/wasm/index.html @@ -0,0 +1,106 @@ + + + + Argon WASM Runtime Example + + + + +

Argon WASM Runtime Example

+ + +
+
+        
+ + +