diff --git a/.circleci/config.yml b/.circleci/config.yml index 67ae73422d..4988610277 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -232,8 +232,8 @@ commands: - run: name: "Install dependencies" command: | - curl https://dl.google.com/go/go1.13.darwin-amd64.tar.gz -o go1.13.darwin-amd64.tar.gz - sudo tar -C /usr/local -xzf go1.13.darwin-amd64.tar.gz + curl https://dl.google.com/go/go1.14.darwin-amd64.tar.gz -o go1.14.darwin-amd64.tar.gz + sudo tar -C /usr/local -xzf go1.14.darwin-amd64.tar.gz ln -s /usr/local/go/bin/go /usr/local/bin/go HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu - restore_cache: @@ -327,14 +327,20 @@ jobs: steps: - test-linux: llvm: "10" + test-llvm10-go114: + docker: + - image: circleci/golang:1.14-buster + steps: + - test-linux: + llvm: "10" assert-test-linux: docker: - - image: circleci/golang:1.13-stretch + - image: circleci/golang:1.14-stretch steps: - assert-test-linux build-linux: docker: - - image: circleci/golang:1.13-stretch + - image: circleci/golang:1.14-stretch steps: - build-linux build-macos: @@ -352,6 +358,7 @@ workflows: - test-llvm9-go111 - test-llvm10-go112 - test-llvm10-go113 + - test-llvm10-go114 - build-linux - build-macos - assert-test-linux diff --git a/Dockerfile b/Dockerfile index 4632c4fb2a..6d958125eb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ -# TinyGo base stage installs Go 1.13, LLVM 10 and the TinyGo compiler itself. -FROM golang:1.13 AS tinygo-base +# TinyGo base stage installs Go 1.14, LLVM 10 and the TinyGo compiler itself. +FROM golang:1.14 AS tinygo-base RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \ echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-10 main" >> /etc/apt/sources.list && \ diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 135dcf4142..40a222a16c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -12,7 +12,7 @@ jobs: steps: - task: GoTool@0 inputs: - version: '1.13.8' + version: '1.14.1' - checkout: self - task: CacheBeta@0 displayName: Cache LLVM source diff --git a/builder/config.go b/builder/config.go index 8bb3a1162f..aabf82ac5a 100644 --- a/builder/config.go +++ b/builder/config.go @@ -25,8 +25,8 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) { if err != nil { return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err) } - if major != 1 || (minor != 11 && minor != 12 && minor != 13) { - return nil, fmt.Errorf("requires go version 1.11, 1.12, or 1.13, got go%d.%d", major, minor) + if major != 1 || minor < 11 || minor > 14 { + return nil, fmt.Errorf("requires go version 1.11, 1.12, 1.13, or 1.14, got go%d.%d", major, minor) } clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT")) return &compileopts.Config{ diff --git a/cgo/cgo_test.go b/cgo/cgo_test.go index e9ae2f43e7..5673ebad8d 100644 --- a/cgo/cgo_test.go +++ b/cgo/cgo_test.go @@ -11,6 +11,7 @@ import ( "go/types" "io/ioutil" "path/filepath" + "regexp" "runtime" "strings" "testing" @@ -19,6 +20,21 @@ import ( // Pass -update to go test to update the output of the test files. var flagUpdate = flag.Bool("update", false, "Update images based on test output.") +// normalizeResult normalizes Go source code that comes out of tests across +// platforms and Go versions. +func normalizeResult(result string) string { + actual := strings.Replace(result, "\r\n", "\n", -1) + + // Make sure all functions are wrapped, even those that would otherwise be + // single-line functions. This is necessary because Go 1.14 changed the way + // such functions are wrapped and it's important to have consistent test + // results. + re := regexp.MustCompile(`func \((.+)\)( .*?) +{ (.+) }`) + actual = re.ReplaceAllString(actual, "func ($1)$2 {\n\t$3\n}") + + return actual +} + func TestCGo(t *testing.T) { var cflags = []string{"--target=armv6m-none-eabi"} @@ -74,7 +90,7 @@ func TestCGo(t *testing.T) { if err != nil { t.Errorf("could not write out CGo AST: %v", err) } - actual := strings.Replace(string(buf.Bytes()), "\r\n", "\n", -1) + actual := normalizeResult(string(buf.Bytes())) // Read the file with the expected output, to compare against. outfile := filepath.Join("testdata", name+".out.go") diff --git a/cgo/testdata/types.out.go b/cgo/testdata/types.out.go index 8a750080bb..af638b9e74 100644 --- a/cgo/testdata/types.out.go +++ b/cgo/testdata/types.out.go @@ -62,12 +62,18 @@ type C.union3_t = C.union_1 type C.union_nested_t = C.union_3 type C.unionarray_t = struct{ arr [10]C.uchar } -func (s *C.struct_4) bitfield_a() C.uchar { return s.__bitfield_1 & 0x1f } -func (s *C.struct_4) set_bitfield_a(value C.uchar) { s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0 } +func (s *C.struct_4) bitfield_a() C.uchar { + return s.__bitfield_1 & 0x1f +} +func (s *C.struct_4) set_bitfield_a(value C.uchar) { + s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0 +} func (s *C.struct_4) bitfield_b() C.uchar { return s.__bitfield_1 >> 5 & 0x1 } -func (s *C.struct_4) set_bitfield_b(value C.uchar) { s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5 } +func (s *C.struct_4) set_bitfield_b(value C.uchar) { + s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5 +} func (s *C.struct_4) bitfield_c() C.uchar { return s.__bitfield_1 >> 6 } @@ -94,25 +100,45 @@ type C.struct_type1 struct { } type C.struct_type2 struct{ _type C.int } -func (union *C.union_1) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) } -func (union *C.union_1) unionfield_d() *float64 { return (*float64)(unsafe.Pointer(&union.$union)) } -func (union *C.union_1) unionfield_s() *C.short { return (*C.short)(unsafe.Pointer(&union.$union)) } +func (union *C.union_1) unionfield_i() *C.int { + return (*C.int)(unsafe.Pointer(&union.$union)) +} +func (union *C.union_1) unionfield_d() *float64 { + return (*float64)(unsafe.Pointer(&union.$union)) +} +func (union *C.union_1) unionfield_s() *C.short { + return (*C.short)(unsafe.Pointer(&union.$union)) +} type C.union_1 struct{ $union uint64 } -func (union *C.union_2) unionfield_area() *C.point2d_t { return (*C.point2d_t)(unsafe.Pointer(&union.$union)) } -func (union *C.union_2) unionfield_solid() *C.point3d_t { return (*C.point3d_t)(unsafe.Pointer(&union.$union)) } +func (union *C.union_2) unionfield_area() *C.point2d_t { + return (*C.point2d_t)(unsafe.Pointer(&union.$union)) +} +func (union *C.union_2) unionfield_solid() *C.point3d_t { + return (*C.point3d_t)(unsafe.Pointer(&union.$union)) +} type C.union_2 struct{ $union [3]uint32 } -func (union *C.union_3) unionfield_point() *C.point3d_t { return (*C.point3d_t)(unsafe.Pointer(&union.$union)) } -func (union *C.union_3) unionfield_array() *C.unionarray_t { return (*C.unionarray_t)(unsafe.Pointer(&union.$union)) } -func (union *C.union_3) unionfield_thing() *C.union3_t { return (*C.union3_t)(unsafe.Pointer(&union.$union)) } +func (union *C.union_3) unionfield_point() *C.point3d_t { + return (*C.point3d_t)(unsafe.Pointer(&union.$union)) +} +func (union *C.union_3) unionfield_array() *C.unionarray_t { + return (*C.unionarray_t)(unsafe.Pointer(&union.$union)) +} +func (union *C.union_3) unionfield_thing() *C.union3_t { + return (*C.union3_t)(unsafe.Pointer(&union.$union)) +} type C.union_3 struct{ $union [2]uint64 } -func (union *C.union_union2d) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) } -func (union *C.union_union2d) unionfield_d() *[2]float64 { return (*[2]float64)(unsafe.Pointer(&union.$union)) } +func (union *C.union_union2d) unionfield_i() *C.int { + return (*C.int)(unsafe.Pointer(&union.$union)) +} +func (union *C.union_union2d) unionfield_d() *[2]float64 { + return (*[2]float64)(unsafe.Pointer(&union.$union)) +} type C.union_union2d struct{ $union [2]uint64 } type C.enum_option C.int diff --git a/main_test.go b/main_test.go index c8f46915e4..ec7f708542 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 b0bee21591..4c87906677 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; + typeFlag = 2; break; case "symbol": - typeFlag = 2; + typeFlag = 3; break; case "function": - typeFlag = 3; + 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,9 +426,10 @@ global, this, ]; - this._refs = new Map(); - this._callbackShutdown = false; - 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) @@ -472,12 +485,6 @@ const go = new Go(); WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { - process.on("exit", (code) => { // Node.js exits if no callback is pending - if (code === 0 && !go.exited) { - // deadlock, make Go print error and stack traces - go._callbackShutdown = true; - } - }); return go.run(result.instance); }).catch((err) => { throw err;