From 5674c35e1477e21eaafab3a51241d84e47a679dd Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 6 Apr 2020 23:12:41 +0200 Subject: [PATCH] wasm: backport "garbage collect references to JavaScript values" See commit: https://github.com/golang/go/commit/54e6ba6724dfde355070238f9abc16362cac2e3d Warning: this will drop support for Go 1.13 for WebAssembly targets! I have modified the integration tests to specifically blacklist Go 1.13 instead of whitelisting any other version, to avoid accidentally not testing WebAssembly. --- main_test.go | 19 ++++++++++++++++--- targets/wasm_exec.js | 42 ++++++++++++++++++++++++++++-------------- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/main_test.go b/main_test.go index c8f46915..ec7f7085 100644 --- a/main_test.go +++ b/main_test.go @@ -13,12 +13,14 @@ import ( "path/filepath" "runtime" "sort" + "strings" "sync" "testing" "time" "github.com/tinygo-org/tinygo/builder" "github.com/tinygo-org/tinygo/compileopts" + "github.com/tinygo-org/tinygo/goenv" ) const TESTDATA = "testdata" @@ -71,9 +73,20 @@ func TestCompiler(t *testing.T) { t.Run("ARM64Linux", func(t *testing.T) { runPlatTests("aarch64--linux-gnu", matches, t) }) - t.Run("WebAssembly", func(t *testing.T) { - runPlatTests("wasm", matches, t) - }) + goVersion, err := builder.GorootVersionString(goenv.Get("GOROOT")) + if err != nil { + t.Error("could not get Go version:", err) + return + } + minorVersion := strings.Split(goVersion, ".")[1] + if minorVersion != "13" { + // WebAssembly tests fail on Go 1.13, so skip them there. Versions + // below that are also not supported but still seem to pass, so + // include them in the tests for now. + t.Run("WebAssembly", func(t *testing.T) { + runPlatTests("wasm", matches, t) + }) + } } } diff --git a/targets/wasm_exec.js b/targets/wasm_exec.js index 85f147cd..4c879066 100644 --- a/targets/wasm_exec.js +++ b/targets/wasm_exec.js @@ -202,26 +202,31 @@ return; } - let ref = this._refs.get(v); - if (ref === undefined) { - ref = this._values.length; - this._values.push(v); - this._refs.set(v, ref); + 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); } - let typeFlag = 0; + this._goRefCounts[id]++; + let typeFlag = 1; switch (typeof v) { case "string": - typeFlag = 1; - break; - case "symbol": typeFlag = 2; break; - case "function": + case "symbol": typeFlag = 3; break; + case "function": + typeFlag = 4; + break; } mem().setUint32(addr + 4, nanHead | typeFlag, true); - mem().setUint32(addr, ref, true); + mem().setUint32(addr, id, true); } const loadSlice = (array, len, cap) => { @@ -284,6 +289,13 @@ setTimeout(this._inst.exports.go_scheduler, timeout); }, + // func finalizeRef(v ref) + "syscall/js.finalizeRef": (sp) => { + // Note: TinyGo does not support finalizers so this should never be + // called. + console.error('syscall/js.finalizeRef not implemented'); + }, + // func stringVal(value string) ref "syscall/js.stringVal": (ret_ptr, value_ptr, value_len) => { const s = loadString(value_ptr, value_len); @@ -405,7 +417,7 @@ async run(instance) { this._inst = instance; - this._values = [ // TODO: garbage collection + this._values = [ // JS values that Go currently has references to, indexed by reference id NaN, 0, null, @@ -414,8 +426,10 @@ global, this, ]; - this._refs = new Map(); - this.exited = false; + 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 const mem = new DataView(this._inst.exports.memory.buffer)