diff --git a/.gitignore b/.gitignore index 91dfed8..6bd8169 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .DS_Store -node_modules \ No newline at end of file +node_modules +deno.lock \ No newline at end of file diff --git a/Makefile b/Makefile index b036614..c826d76 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ dev: - GOOS=js GOARCH=wasm tinygo build -o stat/mod.wasm ./stat/main.go + GOOS=js GOARCH=wasm tinygo build -o stat/mod.wasm -no-debug ./stat/main.go + GOOS=js GOARCH=wasm go build -o plot/mod.wasm ./plot/main.go prod: - GOOS=js GOARCH=wasm tinygo build -o stat/mod.wasm -no-debug ./stat/main.go \ No newline at end of file + GOOS=js GOARCH=wasm tinygo build -o stat/mod.wasm -no-debug ./stat/main.go + GOOS=js GOARCH=wasm tinygo build -o plot/mod.wasm -no-debug ./plot/main.go \ No newline at end of file diff --git a/go.mod b/go.mod index 1c16f76..8c790e5 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,29 @@ module l12.xyz/x/shortcuts go 1.22.6 require ( - golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 + github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b gonum.org/v1/gonum v0.15.1 + gonum.org/v1/plot v0.14.0 +) + +require ( + git.sr.ht/~sbinet/gg v0.5.0 // indirect + github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect + github.com/campoy/embedmd v1.0.0 // indirect + github.com/go-fonts/liberation v0.3.2 // indirect + github.com/go-latex/latex v0.0.0-20231108140139-5c1ce85aa4ea // indirect + github.com/go-pdf/fpdf v0.9.0 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac // indirect + github.com/gonum/diff v0.0.0-20181124234638-500114f11e71 // indirect + github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82 // indirect + github.com/gonum/integrate v0.0.0-20181209220457-a422b5c0fdf2 // indirect + github.com/gonum/internal v0.0.0-20181124074243-f884aa714029 // indirect + github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9 // indirect + github.com/gonum/mathext v0.0.0-20181121095525-8a4bf007ea55 // indirect + github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect + golang.org/x/image v0.14.0 // indirect + golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index aa6c9d5..3d62b1d 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,79 @@ +git.sr.ht/~sbinet/cmpimg v0.1.0 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo= +git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dkp/pxE= +git.sr.ht/~sbinet/gg v0.5.0 h1:6V43j30HM623V329xA9Ntq+WJrMjDxRjuAB1LFWF5m8= +git.sr.ht/~sbinet/gg v0.5.0/go.mod h1:G2C0eRESqlKhS7ErsNey6HHrqU1PwsnCQlekFi9Q2Oo= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= +github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= +github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= +github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= +github.com/go-fonts/dejavu v0.3.2 h1:3XlHi0JBYX+Cp8n98c6qSoHrxPa4AUKDMKdrh/0sUdk= +github.com/go-fonts/dejavu v0.3.2/go.mod h1:m+TzKY7ZEl09/a17t1593E4VYW8L1VaBXHzFZOIjGEY= +github.com/go-fonts/latin-modern v0.3.2 h1:M+Sq24Dp0ZRPf3TctPnG1MZxRblqyWC/cRUL9WmdaFc= +github.com/go-fonts/latin-modern v0.3.2/go.mod h1:9odJt4NbRrbdj4UAMuLVd4zEukf6aAEKnDaQga0whqQ= +github.com/go-fonts/liberation v0.3.2 h1:XuwG0vGHFBPRRI8Qwbi5tIvR3cku9LUfZGq/Ar16wlQ= +github.com/go-fonts/liberation v0.3.2/go.mod h1:N0QsDLVUQPy3UYg9XAc3Uh3UDMp2Z7M1o4+X98dXkmI= +github.com/go-latex/latex v0.0.0-20231108140139-5c1ce85aa4ea h1:DfZQkvEbdmOe+JK2TMtBM+0I9GSdzE2y/L1/AmD8xKc= +github.com/go-latex/latex v0.0.0-20231108140139-5c1ce85aa4ea/go.mod h1:Y7Vld91/HRbTBm7JwoI7HejdDB0u+e9AUBO9MB7yuZk= +github.com/go-pdf/fpdf v0.9.0 h1:PPvSaUuo1iMi9KkaAn90NuKi+P4gwMedWPHhj8YlJQw= +github.com/go-pdf/fpdf v0.9.0/go.mod h1:oO8N111TkmKb9D7VvWGLvLJlaZUQVPM+6V42pp3iV4Y= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac h1:Q0Jsdxl5jbxouNs1TQYt0gxesYMU4VXRbsTlgDloZ50= +github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc= +github.com/gonum/diff v0.0.0-20181124234638-500114f11e71 h1:BE6g8oinc3Ek2elIHq+uDOiZgX3/ODi+EerJ48yrrKc= +github.com/gonum/diff v0.0.0-20181124234638-500114f11e71/go.mod h1:22dM4PLscQl+Nzf64qNBurVJvfyvZELT0iRW2l/NN70= +github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82 h1:EvokxLQsaaQjcWVWSV38221VAK7qc2zhaO17bKys/18= +github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg= +github.com/gonum/integrate v0.0.0-20181209220457-a422b5c0fdf2 h1:GUSkTcIe1SlregbHNUKbYDhBsS8lNgYfIp4S4cToUyU= +github.com/gonum/integrate v0.0.0-20181209220457-a422b5c0fdf2/go.mod h1:pDgmNM6seYpwvPos3q+zxlXMsbve6mOIPucUnUOrI7Y= +github.com/gonum/internal v0.0.0-20181124074243-f884aa714029 h1:8jtTdc+Nfj9AR+0soOeia9UZSvYBvETVHZrugUowJ7M= +github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhSyOzRwuXkOgAvijx4o+4YMUJJo9OvPYMkks= +github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9 h1:7qnwS9+oeSiOIsiUMajT+0R7HR6hw5NegnKPmn/94oI= +github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9/go.mod h1:XA3DeT6rxh2EAE789SSiSJNqxPaC0aE9J8NTOI0Jo/A= +github.com/gonum/mathext v0.0.0-20181121095525-8a4bf007ea55 h1:Ajwn2ENgC/pKtVat0LEHEWNa4a4VGyYJ1feGSccOzFU= +github.com/gonum/mathext v0.0.0-20181121095525-8a4bf007ea55/go.mod h1:fmo8aiSEWkJeiGXUJf+sPvuDgEFgqIoZSs843ePKrGg= +github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9 h1:V2IgdyerlBa/MxaEFRbV5juy/C3MGdj4ePi+g6ePIp4= +github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw= +github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b h1:fbskpz/cPqWH8VqkQ7LJghFkl2KPAiIFUHrTJ2O3RGk= +github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b/go.mod h1:Z4GIJBJO3Wa4gD4vbwQxXXZ+WHmW6E9ixmNrwvs0iZs= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= +golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= +gonum.org/v1/plot v0.14.0 h1:+LBDVFYwFe4LHhdP8coW6296MBEY4nQ+Y4vuUpJopcE= +gonum.org/v1/plot v0.14.0/go.mod h1:MLdR9424SJed+5VqC6MsouEpig9pZX2VZ57H9ko2bXU= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/lib/wasm_exec.js b/lib/wasm_exec.js new file mode 100644 index 0000000..3afccc1 --- /dev/null +++ b/lib/wasm_exec.js @@ -0,0 +1,679 @@ +// 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.path) { + globalThis.path = { + resolve(...pathSegments) { + return pathSegments.join("/"); + }, + }; + } + + 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 testCallExport = (a, b) => { + return this._inst.exports.testExport(a, b); + }; + + const timeOrigin = Date.now() - performance.now(); + this.importObject = { + _gotest: { + add: (a, b) => a + b, + callExport: testCallExport, + }, + 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/notebooks/deno_tinygo_wasm.ipynb b/notebooks/deno_tinygo_wasm.ipynb index 73fab97..6f584b5 100644 --- a/notebooks/deno_tinygo_wasm.ipynb +++ b/notebooks/deno_tinygo_wasm.ipynb @@ -2,18 +2,19 @@ "cells": [ { "cell_type": "code", - "execution_count": 4, + "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ + "Stats initialized\n", "\n", "Linear Regression Line:\n", - "\tEstimated offset is: 1.480586\n", - "\tEstimated slope is: 2.999774\n", - "\tR^2 is: 0.999991\n" + "\tEstimated offset is: 1.474039\n", + "\tEstimated slope is: 3.000136\n", + "\tR^2 is: 0.999989\n" ] } ], @@ -36,6 +37,28 @@ "console.log(\"\\tEstimated slope is: \", linreg.beta.toFixed(6));\n", "console.log(\"\\tR^2 is: \", r.toFixed(6));" ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "![name](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAkAAAAGACAIAAADK+EpIAAAmjUlEQVR4nOzde1xVZd738WtvNgKCnERAUINU8ITgoGJqNiZmHtIZ0UJLU7PsTnN00rLRGqe08nBnivcMjo4yORo6ZKaYR4ReKXkgxVTExAQBRUDOIIe9Wc9rWnPvmwexKWOzvODz/qP2ta7Fdf0We7O/rrXXXsugKIoAAEA2Bq0LAH4Gk8lkZWWldRWNJiYmZvfu3a6uruvWrbPoRElJSbt37759+3ZERITBwF89mgm91gUA/3Lo0KGJEyfa2dlNnDjx0KFDQojCwsLFixd7eXn5+fktXbrUZDJlZ2c7OTl9/vnnWhfbOBITE1etWrV582ZzJBcXFy9atKhdu3YODg6vv/56RkaGuvzIkSNPPfVUnz59IiMj72+uvn37Dh06NDIy0mQyNd4WAFpTgAdDfHy8ECI+Pr7uwsDAwHHjxqmPq6urly5dev369XuNUFNTs2/fPstX2jheffXV6dOn37187dq1QogNGzbUXRgaGnru3LlfMt2RI0eEEJWVlb9kEOCBwh4YpGFtbf3HP/6xY8eOdRfW/RB3x44dn332Wb2futenvLW1tUKI7OzsVatW/cQf+RH/8UfuXiErK8vGxubuNefMmRMSErJo0aLc3Fx1yY4dO3r27Nm7d++fW9V9u7taPizHA4gAgzT+/Oc/BwcH79q1S30/nTdvXmho6IwZM4KDg4uLi2NjY99+++2jR49OmjTp3Llz6idMoaGh06dPHz58+JtvvlldXa2OExkZ+fjjj7/yyiuDBw/u2rXroEGDamtr33333YCAgPj4+PDw8EGDBgkh3n///SeffPLZZ58NCAhQj1seO3Zs5MiRM2bMmDZtmpeXl6ura0RExNtvv92nTx/14KfRaLy77A0bNjzxxBPTpk0bOnToqlWr1OB89913T548efjw4UmTJm3btq3u+nq9fuPGjWVlZfPnzxdClJeXr169+p133qk37LJly3r16vXCCy98++23QojDhw+PHz9+wYIFRqPxL3/5y/Dhw6dMmRIQELB169Z6P1hcXLxgwYKAgIDS0lIhRERERP/+/Xfu3Gn+5QwbNmz69OmhoaFpaWnqsdyxY8eOHz9+8uTJ48aNa9SnFPhltN4FBP5NPYS4bt26r+ro0qWL+RCioiienp6bNm1SFOXQoUNeXl61tbWKokRFRRUWFiqK8pvf/GbmzJnqmt98802rVq0yMzMVRamsrOzVq9eyZcsURTl79qxOp7t27ZqiKDk5OUKIuLg4RVGysrKEEO+9996lS5fmz5+vKMq7775rMpkURXn77be7du2qDhseHj5u3LjS0tKKiopnn33Wzc0tLy9PUZQ9e/YIIU6ePFlvo/bs2ePm5lZUVKQoSlFRkaenZ1RUlNo1ZsyYl19++V6/jT/84Q/qR4Nvvvnm1q1b716htra2S5cuU6ZMMS8ZPHhwSUmJoigrV66sqKhQFGXdunUuLi5qb91DiMeOHRNCqFUpiuLj4xMZGakoSkJCQps2bQoKChRFee+99/r06aNu/oQJE9Q1P/roo/t6bgGLYA8MD5bz588fq6O8vLzB1aqrq3Nzc48ePSqEeP75552dneutsHnz5n79+nXo0EEIYWNjExYWtmXLFiHExYsXDQaDj4+PEMLDw8PBwUHdz1CNGjWqW7duH374oRBiyZIlev2//kC6du1qPponhGjTpo2Dg4Odnd1vf/vboqIiNzc3IcSYMWPUvKxXxt/+9rdhw4Y5OTkJIZycnEaPHq2W8R+99dZbXbt2ffHFF5OSkp577rm7V9DpdK+++urOnTvz8vLU7Ozdu3ebNm2EEAsXLrSzs1MrLywsbHC/sEH/+Mc/HnnkERcXFyHEgAEDkpOTy8vLq6urz5w5c+XKFSHE7373u584FNAEOKEWD5bJkyf/+te/Njejo6MbXO2JJ54ICwsbPnx4nz59fve7302dOrXeCunp6e7u7uZm+/bt1bM/AgICampqrl275uvre/PmzbKysoCAgAanOHz48I4dOxwcHNT37rvVPR9dp9OpaXd3GUOGDKlbhron9B/Z2tq+8847kyZN2rRp073WmT59+ltvvbVx48Y//OEPf/3rX1977TV1+fHjx6Oiolq3bp2dna3W9lNmFEJcv379ypUr06ZNE0JUVFT07t07Pz//1VdfPXjwYPfu3YcPH7548eLBgwf/xNEASyPAICVra+vo6Og//elP69evnz59usFgmDx5ct0V7O3ty8rKzM3KykpXV1edTte7d+8lS5aMGTPmscceu3Tp0sqVKx955JG7xz916tRvf/vblJSUTp06/eMf/zh+/Pj91Wlvb19VVWVuVlVVqXtsP4W6J6TuVDWoTZs2M2bMiIyMnDVrVm5ubo8ePYQQV65cefzxx8+ePdujR4+DBw9++umnP71aOzs7f3//qKioesvPnDlz7Nix9957b9iwYRcvXuzSpctPHxOwHA4hQkrnzp27c+eOv79/RETEiBEjTp06pS5XT5EQQgQGBtY9NpiamqruaVVUVBw/fjwhIWHt2rXx8fELFy5scPzTp0+7urp26tTpF9ZZr4xLly7da4fv/sydOzc7Ozs8PHz69OnqkjNnzlhbW3fv3t186uDdJxBaW1sLIUpKSuot9/f3T0lJqXfI8euvvxZCDB48eO/evfb29snJyY1YP/BLEGB4UFRUVJj/q6qpqTEajdXV1eav36qf3Aohzp49u3LlyqqqqvT09JSUlMcee0wI4efnl5iY+M0336Snp7/yyit37tzZvHmz0Wg8duzYnj173n//ffWzooSEhEmTJk2ePHnixIkzZ848cODA3e/13bp1y8zM3LRpU1xcnPrF6noF1Ht8d1O1cOHC8+fP79u3z2g07t279+zZs0uWLDFvXd2ds7upZwmq/70XX1/fp556KjU11Xx+oL+/f3l5+X//938nJCSop5aYyzP/18/Pz8bGZsmSJQcOHNi4caN66ocQYvbs2YWFhc8///xXX3117ty5uLg4IcSWLVv2799vMpkSExOrq6v79ev3n55JoIlYLV26VOsaAHHo0KHjx48PGTIkKyvLaDR27ty5sLBw9erV/v7+Xl5eiYmJgYGB69ev79mzZ1lZWW1tbVBQ0DfffLNt27YTJ07MnTv3N7/5jfrenZ6efvHixR49evj4+Dz//PMJCQlbt24tLy9/5513goKC1Hf8S5cudezY0dnZ2dHRMS0tbdmyZRMnTty1a1dISMjVq1ddXV09PDwefvhhb2/vxMREk8n04osv2tjYZGRklJWVFRUVubi4FBcX63S6o0ePDhw48MyZMwEBAREREcHBwYWFhV26dHF0dDRvl4uLy6RJk2JjY7dv324wGD744IPOnTsLIbZt22ZjY+Po6JiUlNSzZ09bW9t6v5DVq1dnZGQMGTIkIyMjJSWlf//+9/rV+fr69u7du0+fPmrT09PT39//5MmT5eXls2bNcnBwSE1N1ev1R48eHTx48JkzZ3x9fd3d3fv375+amlpYWBgaGvrwww/n5+d36tTJx8dn8uTJ6enpR48evX79emBgoLe3d9u2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)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import plot from \"../plot/mod.ts?5\";\n", + "\n", + "plot.DrawHist(ys, 16, { title : \"Histogram of Y values\" });\n" + ] } ], "metadata": { diff --git a/plot/main.go b/plot/main.go new file mode 100644 index 0000000..2276bb2 --- /dev/null +++ b/plot/main.go @@ -0,0 +1,22 @@ +//go:build js && wasm +// +build js,wasm + +package main + +import ( + "l12.xyz/x/shortcuts/plot/src" + + "syscall/js" +) + +func InitPlotExports(this js.Value, args []js.Value) interface{} { + exports := args[0] + exports.Set("Hist", js.FuncOf(src.HistPlot)) + return nil +} + +func main() { + c := make(chan struct{}, 0) + js.Global().Set("__InitPlotExports", js.FuncOf(InitPlotExports)) + <-c +} diff --git a/plot/mod.ts b/plot/mod.ts new file mode 100644 index 0000000..8f002e4 --- /dev/null +++ b/plot/mod.ts @@ -0,0 +1,33 @@ +import "../lib/wasm_exec.js"; + +// @ts-expect-error: no types +const go = new Go(); + +const code = + await (await fetch(import.meta.url.replace("/mod.ts", "/mod.wasm"))) + .arrayBuffer(); + +const wasmMmodule = await WebAssembly.instantiate(code, go.importObject); +const wasm = wasmMmodule.instance; + +go.run(wasm); + +const _exports = {} as Record unknown>; + +// @ts-ignore: no types +__InitPlotExports(_exports); + +for (const key in _exports) { + const draw = _exports[key]; + const drawKey = "Draw" + key; + _exports[drawKey] = (...args: unknown[]) => { + const data = "data:image/png;base64," + draw(...args); + return { + [Symbol.for("Jupyter.display")]: () => ({ + "text/markdown": `![name](${data})`, + }), + }; + }; +} + +export default _exports; diff --git a/plot/mod.wasm b/plot/mod.wasm new file mode 100755 index 0000000..2db6f8c Binary files /dev/null and b/plot/mod.wasm differ diff --git a/plot/src/Hist.go b/plot/src/Hist.go new file mode 100644 index 0000000..dad660a --- /dev/null +++ b/plot/src/Hist.go @@ -0,0 +1,54 @@ +//go:build js && wasm +// +build js,wasm + +package src + +import ( + "image/color" + + "syscall/js" + + "github.com/gonum/stat/distuv" + "gonum.org/v1/plot" + "gonum.org/v1/plot/plotter" + "gonum.org/v1/plot/vg" +) + +func HistPlot(this js.Value, args []js.Value) interface{} { + var ( + values = args[0] + bins = args[1] + opts js.Value + text = "Histogram" + ) + + if len(args) == 3 { + opts = args[2] + if !opts.Get("title").IsUndefined() { + text = opts.Get("title").String() + } + } + + v := make(plotter.Values, values.Length()) + for i := range v { + v[i] = values.Index(i).Float() + } + p := plot.New() + p.Title.Text = text + h, err := plotter.NewHist(v, bins.Int()) + if err != nil { + panic(err) + } + h.Normalize(1) + p.Add(h) + norm := plotter.NewFunction(distuv.UnitNormal.Prob) + norm.Color = color.RGBA{R: 255, A: 255} + norm.Width = vg.Points(2) + p.Add(norm) + writer, err := p.WriterTo(6*vg.Inch, 4*vg.Inch, "png") + if err != nil { + panic(err) + } + b64string := WriterToBase64String(writer) + return b64string +} diff --git a/plot/src/utils.go b/plot/src/utils.go new file mode 100644 index 0000000..570a9d8 --- /dev/null +++ b/plot/src/utils.go @@ -0,0 +1,16 @@ +//go:build js && wasm +// +build js,wasm + +package src + +import ( + "bytes" + "encoding/base64" + "io" +) + +func WriterToBase64String(writer io.WriterTo) string { + var buf bytes.Buffer + writer.WriteTo(&buf) + return base64.StdEncoding.EncodeToString(buf.Bytes()) +}