From ab47cea055d24e06135ba4d7d896dc6e8837f8d7 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 31 Jul 2021 19:17:07 +0200 Subject: [PATCH 01/47] transform: improve GC stack slot pass to work around a bug Bug 1790 ("musttail call must precede a ret with an optional bitcast") is caused by the GC stack slot pass inserting a store instruction between a musttail call and a return instruction. This is not allowed in LLVM IR. One solution would be to remove the musttail. That would probably work, but 1) the go-llvm API doesn't support this and 2) this might have unforeseen consequences. What I've done in this commit is to move the store instruction to a position earlier in the basic block, just after the last access to the GC stack slot alloca. Thanks to @fgsch for a very small repro, which I've used as a regression test. --- testdata/goroutines.go | 12 +++++++++ transform/gc.go | 36 +++++++++++++++++++++++-- transform/testdata/gc-stackslots.ll | 8 ++++++ transform/testdata/gc-stackslots.out.ll | 8 +++++- 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/testdata/goroutines.go b/testdata/goroutines.go index c23dbdb9d..e3cc62fa7 100644 --- a/testdata/goroutines.go +++ b/testdata/goroutines.go @@ -76,6 +76,8 @@ func main() { testGoOnBuiltins() testCond() + + testIssue1790() } func acquire(m *sync.Mutex) { @@ -198,3 +200,13 @@ func testCond() { panic("missing queued notification") } } + +var once sync.Once + +// This tests a fix for issue 1790: +// https://github.com/tinygo-org/tinygo/issues/1790 +func testIssue1790() *int { + once.Do(func() {}) + i := 0 + return &i +} diff --git a/transform/gc.go b/transform/gc.go index 068860498..190760ace 100644 --- a/transform/gc.go +++ b/transform/gc.go @@ -246,6 +246,7 @@ func MakeGCStackSlots(mod llvm.Module) bool { } // Do a store to the stack object after each new pointer that is created. + pointerStores := make(map[llvm.Value]struct{}) for i, ptr := range pointers { // Insert the store after the pointer value is created. insertionPoint := llvm.NextInstruction(ptr) @@ -263,13 +264,44 @@ func MakeGCStackSlots(mod llvm.Module) bool { }, "") // Store the pointer into the stack slot. - builder.CreateStore(ptr, gep) + store := builder.CreateStore(ptr, gep) + pointerStores[store] = struct{}{} } // Make sure this stack object is popped from the linked list of stack // objects at return. for _, ret := range returns { - builder.SetInsertPointBefore(ret) + inst := ret + // Try to do the popping of the stack object earlier, by inserting + // it not right before the return instruction but moving the insert + // position up. + // This is necessary so that the GC stack slot pass doesn't + // interfere with tail calls (in particular, musttail calls). + for { + prevInst := llvm.PrevInstruction(inst) + if prevInst == parent { + break + } + if _, ok := pointerStores[prevInst]; ok { + // Pop the stack object after the last store instruction. + // This can probably be made more efficient: storing to the + // stack chain object and then immediately popping isn't + // useful. + break + } + if prevInst.IsNil() { + // Start of basic block. Pop the stack object here. + break + } + if !prevInst.IsAPHINode().IsNil() { + // Do not insert before a PHI node. PHI nodes must be + // grouped at the beginning of a basic block before any + // other instruction. + break + } + inst = prevInst + } + builder.SetInsertPointBefore(inst) builder.CreateStore(parent, stackChainStart) } } diff --git a/transform/testdata/gc-stackslots.ll b/transform/testdata/gc-stackslots.ll index deba7b128..8b43a83e8 100644 --- a/transform/testdata/gc-stackslots.ll +++ b/transform/testdata/gc-stackslots.ll @@ -20,6 +20,10 @@ define i8* @needsStackSlots() { ; so tracking it is not really necessary. %ptr = call i8* @runtime.alloc(i32 4) call void @runtime.trackPointer(i8* %ptr) + ; Restoring the stack pointer can happen at this position, before the return. + ; This avoids issues with tail calls. + call void @someArbitraryFunction() + %val = load i8, i8* @someGlobal ret i8* %ptr } @@ -95,3 +99,7 @@ define void @testGEPBitcast() { call void @runtime.trackPointer(i8* %other) ret void } + +define void @someArbitraryFunction() { + ret void +} diff --git a/transform/testdata/gc-stackslots.out.ll b/transform/testdata/gc-stackslots.out.ll index 9acb0abe7..0edb91686 100644 --- a/transform/testdata/gc-stackslots.out.ll +++ b/transform/testdata/gc-stackslots.out.ll @@ -26,6 +26,8 @@ define i8* @needsStackSlots() { %4 = getelementptr { %runtime.stackChainObject*, i32, i8* }, { %runtime.stackChainObject*, i32, i8* }* %gc.stackobject, i32 0, i32 2 store i8* %ptr, i8** %4 store %runtime.stackChainObject* %1, %runtime.stackChainObject** @runtime.stackChainStart + call void @someArbitraryFunction() + %val = load i8, i8* @someGlobal ret i8* %ptr } @@ -73,8 +75,8 @@ define i8* @fibNext(i8* %x, i8* %y) { %out.alloc = call i8* @runtime.alloc(i32 1) %4 = getelementptr { %runtime.stackChainObject*, i32, i8* }, { %runtime.stackChainObject*, i32, i8* }* %gc.stackobject, i32 0, i32 2 store i8* %out.alloc, i8** %4 - store i8 %out.val, i8* %out.alloc store %runtime.stackChainObject* %1, %runtime.stackChainObject** @runtime.stackChainStart + store i8 %out.val, i8* %out.alloc ret i8* %out.alloc } @@ -135,3 +137,7 @@ define void @testGEPBitcast() { store %runtime.stackChainObject* %1, %runtime.stackChainObject** @runtime.stackChainStart ret void } + +define void @someArbitraryFunction() { + ret void +} From 478c592b131b21506e50ed1793b09689f0da456f Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 31 Jul 2021 15:10:18 +0200 Subject: [PATCH 02/47] wasm: add support for the crypto/rand package This is done via wasi-libc and the WASI interface, for ease of maintenance (only one implementation for both WASI and JS/browsers). --- main_test.go | 8 +++++++- src/crypto/rand/rand_getentropy.go | 2 +- targets/wasm_exec.js | 4 ++++ testdata/env.go | 23 ----------------------- testdata/env.txt | 1 - testdata/rand.go | 24 ++++++++++++++++++++++++ testdata/rand.txt | 1 + 7 files changed, 37 insertions(+), 26 deletions(-) create mode 100644 testdata/rand.go create mode 100644 testdata/rand.txt diff --git a/main_test.go b/main_test.go index 9ed0c6c54..2c1b2f864 100644 --- a/main_test.go +++ b/main_test.go @@ -162,7 +162,7 @@ func runPlatTests(target string, tests []string, t *testing.T) { runTest(name, target, t, nil, nil) }) } - if target == "wasi" || target == "" { + if target == "" || target == "wasi" { t.Run("filesystem.go", func(t *testing.T) { t.Parallel() runTest("filesystem.go", target, t, nil, nil) @@ -172,6 +172,12 @@ func runPlatTests(target string, tests []string, t *testing.T) { runTest("env.go", target, t, []string{"first", "second"}, []string{"ENV1=VALUE1", "ENV2=VALUE2"}) }) } + if target == "" || target == "wasi" || target == "wasm" { + t.Run("rand.go", func(t *testing.T) { + t.Parallel() + runTest("rand.go", target, t, nil, nil) + }) + } } // Due to some problems with LLD, we cannot run links in parallel, or in parallel with compiles. diff --git a/src/crypto/rand/rand_getentropy.go b/src/crypto/rand/rand_getentropy.go index 661132fb7..4cd037956 100644 --- a/src/crypto/rand/rand_getentropy.go +++ b/src/crypto/rand/rand_getentropy.go @@ -1,4 +1,4 @@ -// +build darwin freebsd wasi +// +build darwin freebsd tinygo.wasm // This implementation of crypto/rand uses the getentropy system call (available // on both MacOS and WASI) to generate random numbers. diff --git a/targets/wasm_exec.js b/targets/wasm_exec.js index 21bfcda18..b0545abdb 100644 --- a/targets/wasm_exec.js +++ b/targets/wasm_exec.js @@ -285,6 +285,10 @@ throw 'trying to exit with code ' + code; } }, + random_get: (bufPtr, bufLen) => { + crypto.getRandomValues(loadSlice(bufPtr, bufLen)); + return 0; + }, }, env: { // func ticks() float64 diff --git a/testdata/env.go b/testdata/env.go index 9c5bb5a89..115da6d3d 100644 --- a/testdata/env.go +++ b/testdata/env.go @@ -1,7 +1,6 @@ package main import ( - "crypto/rand" "os" ) @@ -21,26 +20,4 @@ func main() { for _, arg := range os.Args[1:] { println("arg:", arg) } - - // Check for crypto/rand support. - checkRand() -} - -func checkRand() { - buf := make([]byte, 500) - n, err := rand.Read(buf) - if n != len(buf) || err != nil { - println("could not read random numbers:", err) - } - - // Very simple test that random numbers are at least somewhat random. - sum := 0 - for _, b := range buf { - sum += int(b) - } - if sum < 95*len(buf) || sum > 159*len(buf) { - println("random numbers don't seem that random, the average byte is", sum/len(buf)) - } else { - println("random number check was successful") - } } diff --git a/testdata/env.txt b/testdata/env.txt index e392cd5e9..8ba50a7fd 100644 --- a/testdata/env.txt +++ b/testdata/env.txt @@ -3,4 +3,3 @@ ENV2: VALUE2 arg: first arg: second -random number check was successful diff --git a/testdata/rand.go b/testdata/rand.go new file mode 100644 index 000000000..958b95c22 --- /dev/null +++ b/testdata/rand.go @@ -0,0 +1,24 @@ +package main + +import "crypto/rand" + +// TODO: make this a test in the crypto/rand package. + +func main() { + buf := make([]byte, 500) + n, err := rand.Read(buf) + if n != len(buf) || err != nil { + println("could not read random numbers:", err) + } + + // Very simple test that random numbers are at least somewhat random. + sum := 0 + for _, b := range buf { + sum += int(b) + } + if sum < 95*len(buf) || sum > 159*len(buf) { + println("random numbers don't seem that random, the average byte is", sum/len(buf)) + } else { + println("random number check was successful") + } +} diff --git a/testdata/rand.txt b/testdata/rand.txt new file mode 100644 index 000000000..d6b8162f7 --- /dev/null +++ b/testdata/rand.txt @@ -0,0 +1 @@ +random number check was successful From 55789fd2c2e02b58c40f714347a1c93d61a02b5c Mon Sep 17 00:00:00 2001 From: Dan Kegel Date: Fri, 16 Apr 2021 11:04:22 -0700 Subject: [PATCH 03/47] src/testing/benchmark.go: add subset implementation of Benchmark Partially fixes #1808 Allows the following to succeed: curl "https://golang.org/test/fibo.go?m=text" > fibo.go tinygo build -o fibo fibo.go ./fibo -bench --- Makefile | 2 + src/testing/benchmark.go | 204 +++++++++++++++++++++++++++-- tests/tinygotest/benchmark_test.go | 50 +++++++ 3 files changed, 244 insertions(+), 12 deletions(-) create mode 100644 tests/tinygotest/benchmark_test.go diff --git a/Makefile b/Makefile index b62956a56..e184dcf8a 100644 --- a/Makefile +++ b/Makefile @@ -207,6 +207,8 @@ TEST_PACKAGES = \ .PHONY: tinygo-test tinygo-test: $(TINYGO) test $(TEST_PACKAGES) + # until "test testing" passes + cd tests/tinygotest && $(TINYGO) test benchmark_test.go .PHONY: smoketest smoketest: diff --git a/src/testing/benchmark.go b/src/testing/benchmark.go index 56f9f6ab4..d17b1c06a 100644 --- a/src/testing/benchmark.go +++ b/src/testing/benchmark.go @@ -6,29 +6,209 @@ package testing -// B is a type passed to Benchmark functions to manage benchmark timing and to -// specify the number of iterations to run. -// -// TODO: Implement benchmarks. This struct allows test files containing -// benchmarks to compile and run, but will not run the benchmarks themselves. -type B struct { - common - N int +import ( + "time" +) + +var ( + benchTime = benchTimeFlag{d: 1 * time.Second} // changed during test of testing package +) + +type benchTimeFlag struct { + d time.Duration } +// B is a type passed to Benchmark functions to manage benchmark timing and to +// specify the number of iterations to run. +type B struct { + common + hasSub bool // TODO: should be in common, and atomic + start time.Time // TODO: should be in common + duration time.Duration // TODO: should be in common + N int + benchFunc func(b *B) + benchTime benchTimeFlag + timerOn bool + result BenchmarkResult +} + +// InternalBenchmark is an internal type but exported because it is cross-package; +// it is part of the implementation of the "go test" command. type InternalBenchmark struct { Name string F func(b *B) } +// BenchmarkResult contains the results of a benchmark run. +type BenchmarkResult struct { + N int // The number of iterations. + T time.Duration // The total time taken. +} + +// NsPerOp returns the "ns/op" metric. +func (r BenchmarkResult) NsPerOp() int64 { + if r.N <= 0 { + return 0 + } + return r.T.Nanoseconds() / int64(r.N) +} + +// AllocsPerOp returns the "allocs/op" metric, +// which is calculated as r.MemAllocs / r.N. +func (r BenchmarkResult) AllocsPerOp() int64 { + return 0 // Dummy version to allow running e.g. golang.org/test/fibo.go +} + +// AllocedBytesPerOp returns the "B/op" metric, +// which is calculated as r.MemBytes / r.N. +func (r BenchmarkResult) AllocedBytesPerOp() int64 { + return 0 // Dummy version to allow running e.g. golang.org/test/fibo.go +} + func (b *B) SetBytes(n int64) { panic("testing: unimplemented: B.SetBytes") } -func (b *B) ResetTimer() { - panic("testing: unimplemented: B.ResetTimer") +// StartTimer starts timing a test. This function is called automatically +// before a benchmark starts, but it can also be used to resume timing after +// a call to StopTimer. +func (b *B) StartTimer() { + if !b.timerOn { + b.start = time.Now() + b.timerOn = true + } } -func (b *B) Run(name string, f func(b *B)) bool { - panic("testing: unimplemented: B.Run") +// StopTimer stops timing a test. This can be used to pause the timer +// while performing complex initialization that you don't +// want to measure. +func (b *B) StopTimer() { + if b.timerOn { + b.duration += time.Since(b.start) + b.timerOn = false + } +} + +// ResetTimer zeroes the elapsed benchmark time. +// It does not affect whether the timer is running. +func (b *B) ResetTimer() { + if b.timerOn { + b.start = time.Now() + } + b.duration = 0 +} + +// runN runs a single benchmark for the specified number of iterations. +func (b *B) runN(n int) { + b.N = n + b.ResetTimer() + b.StartTimer() + b.benchFunc(b) + b.StopTimer() +} + +func min(x, y int64) int64 { + if x > y { + return y + } + return x +} + +func max(x, y int64) int64 { + if x < y { + return y + } + return x +} + +// run1 runs the first iteration of benchFunc. It reports whether more +// iterations of this benchmarks should be run. +func (b *B) run1() bool { + b.runN(1) + return !b.hasSub +} + +// run executes the benchmark. +func (b *B) run() { + b.launch() +} + +// launch launches the benchmark function. It gradually increases the number +// of benchmark iterations until the benchmark runs for the requested benchtime. +// run1 must have been called on b. +func (b *B) launch() { + d := b.benchTime.d + for n := int64(1); !b.failed && b.duration < d && n < 1e9; { + last := n + // Predict required iterations. + goalns := d.Nanoseconds() + prevIters := int64(b.N) + prevns := b.duration.Nanoseconds() + if prevns <= 0 { + // Round up, to avoid div by zero. + prevns = 1 + } + // Order of operations matters. + // For very fast benchmarks, prevIters ~= prevns. + // If you divide first, you get 0 or 1, + // which can hide an order of magnitude in execution time. + // So multiply first, then divide. + n = goalns * prevIters / prevns + // Run more iterations than we think we'll need (1.2x). + n += n / 5 + // Don't grow too fast in case we had timing errors previously. + n = min(n, 100*last) + // Be sure to run at least one more than last time. + n = max(n, last+1) + // Don't run more than 1e9 times. (This also keeps n in int range on 32 bit platforms.) + n = min(n, 1e9) + b.runN(int(n)) + } + b.result = BenchmarkResult{b.N, b.duration} +} + +// Run benchmarks f as a subbenchmark with the given name. It reports +// true if the subbenchmark succeeded. +// +// A subbenchmark is like any other benchmark. A benchmark that calls Run at +// least once will not be measured itself and will be called once with N=1. +func (b *B) Run(name string, f func(b *B)) bool { + b.hasSub = true + sub := &B{ + common: common{name: name}, + benchFunc: f, + benchTime: b.benchTime, + } + if sub.run1() { + sub.run() + } + b.add(sub.result) + return !sub.failed +} + +// Benchmark benchmarks a single function. It is useful for creating +// custom benchmarks that do not use the "go test" command. +// +// If f calls Run, the result will be an estimate of running all its +// subbenchmarks that don't call Run in sequence in a single benchmark. +func Benchmark(f func(b *B)) BenchmarkResult { + b := &B{ + benchFunc: f, + benchTime: benchTime, + } + if b.run1() { + b.run() + } + return b.result +} + +// add simulates running benchmarks in sequence in a single iteration. It is +// used to give some meaningful results in case func Benchmark is used in +// combination with Run. +func (b *B) add(other BenchmarkResult) { + r := &b.result + // The aggregated BenchmarkResults resemble running all subbenchmarks as + // in sequence in a single benchmark. + r.N = 1 + r.T += time.Duration(other.NsPerOp()) } diff --git a/tests/tinygotest/benchmark_test.go b/tests/tinygotest/benchmark_test.go new file mode 100644 index 000000000..c045c975b --- /dev/null +++ b/tests/tinygotest/benchmark_test.go @@ -0,0 +1,50 @@ +// Copyright 2011 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. + +package testbench + +import ( + "testing" +) + +var buf = make([]byte, 13579) + +func NonASCII(b []byte, i int, offset int) int { + for i = offset; i < len(b) + offset; i++ { + if b[i % len(b)] >= 0x80 { + break + } + } + return i +} + +func BenchmarkFastNonASCII(b *testing.B) { + var val int + for i := 0; i < b.N; i++ { + val += NonASCII(buf, 0, 0) + } +} + +func BenchmarkSlowNonASCII(b *testing.B) { + var val int + for i := 0; i < b.N; i++ { + val += NonASCII(buf, 0, 0) + val += NonASCII(buf, 0, 1) + } +} + +// TestBenchmark simply uses Benchmark twice and makes sure it does not crash. +func TestBenchmark(t *testing.T) { + // FIXME: reduce runtime from the current 3 seconds. + rslow := testing.Benchmark(BenchmarkSlowNonASCII) + rfast := testing.Benchmark(BenchmarkFastNonASCII) + tslow := rslow.NsPerOp() + tfast := rfast.NsPerOp() + + // Be exceedingly forgiving; do not fail even if system gets busy. + speedup := float64(tslow) / float64(tfast) + if speedup < 0.3 { + t.Errorf("Expected speedup >= 0.3, got %f", speedup) + } +} From cfae2d4f9a8e4eb8ea1d9a7717d55d52492bdecf Mon Sep 17 00:00:00 2001 From: Dan Kegel Date: Thu, 5 Aug 2021 12:01:33 -0700 Subject: [PATCH 04/47] Makefile: add src/testing to FMT_PATHS --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e184dcf8a..21ee69d94 100644 --- a/Makefile +++ b/Makefile @@ -100,7 +100,7 @@ endif clean: @rm -rf build -FMT_PATHS = ./*.go builder cgo compiler interp loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform +FMT_PATHS = ./*.go builder cgo compiler interp loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/testing src/internal/reflectlite transform fmt: @gofmt -l -w $(FMT_PATHS) fmt-check: From 4f7b23c2b7eb1ecd33f8f478de64b491f91b39bb Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Fri, 6 Aug 2021 12:22:50 -0300 Subject: [PATCH 05/47] machine/rp2040: add I2C support (#2013) machine/rp2040: add i2c support --- src/machine/board_feather_rp2040.go | 9 + src/machine/board_nano-rp2040.go | 7 +- src/machine/board_pico.go | 9 + src/machine/i2c.go | 2 +- src/machine/machine_rp2040_gpio.go | 43 ++- src/machine/machine_rp2040_i2c.go | 461 ++++++++++++++++++++++++++++ 6 files changed, 522 insertions(+), 9 deletions(-) create mode 100644 src/machine/machine_rp2040_i2c.go diff --git a/src/machine/board_feather_rp2040.go b/src/machine/board_feather_rp2040.go index 4be1b4938..58816f05c 100644 --- a/src/machine/board_feather_rp2040.go +++ b/src/machine/board_feather_rp2040.go @@ -9,6 +9,15 @@ const ( xoscFreq = 12 // MHz ) +// I2C Pins. +const ( + I2C0_SDA_PIN = GPIO24 + I2C0_SCL_PIN = GPIO25 + + I2C1_SDA_PIN = GPIO2 + I2C1_SCL_PIN = GPIO3 +) + // SPI default pins const ( // Default Serial Clock Bus 0 for SPI communications diff --git a/src/machine/board_nano-rp2040.go b/src/machine/board_nano-rp2040.go index 3db965a79..1b799d70b 100644 --- a/src/machine/board_nano-rp2040.go +++ b/src/machine/board_nano-rp2040.go @@ -49,8 +49,11 @@ const ( // I2C pins const ( - SDA_PIN Pin = GPIO12 - SCL_PIN Pin = GPIO13 + I2C0_SDA_PIN Pin = GPIO12 + I2C0_SCL_PIN Pin = GPIO13 + + I2C1_SDA_PIN Pin = GPIO18 + I2C1_SCL_PIN Pin = GPIO19 ) // SPI pins. SPI1 not available on Nano RP2040 Connect. diff --git a/src/machine/board_pico.go b/src/machine/board_pico.go index 961c923cb..2cbba390d 100644 --- a/src/machine/board_pico.go +++ b/src/machine/board_pico.go @@ -38,6 +38,15 @@ const ( xoscFreq = 12 // MHz ) +// I2C Default pins on Raspberry Pico. +const ( + I2C0_SDA_PIN = GP4 + I2C0_SCL_PIN = GP5 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 +) + // SPI default pins const ( // Default Serial Clock Bus 0 for SPI communications diff --git a/src/machine/i2c.go b/src/machine/i2c.go index 60204f48d..5e66c36ea 100644 --- a/src/machine/i2c.go +++ b/src/machine/i2c.go @@ -1,4 +1,4 @@ -// +build atmega nrf sam stm32 fe310 k210 +// +build atmega nrf sam stm32 fe310 k210 rp2040 package machine diff --git a/src/machine/machine_rp2040_gpio.go b/src/machine/machine_rp2040_gpio.go index e5394685d..5e3d06cc0 100644 --- a/src/machine/machine_rp2040_gpio.go +++ b/src/machine/machine_rp2040_gpio.go @@ -47,14 +47,27 @@ type pinFunc uint8 // GPIO function selectors const ( fnJTAG pinFunc = 0 - fnSPI pinFunc = 1 + fnSPI pinFunc = 1 // Connect one of the internal PL022 SPI peripherals to GPIO fnUART pinFunc = 2 fnI2C pinFunc = 3 - fnPWM pinFunc = 4 - fnSIO pinFunc = 5 - fnPIO0 pinFunc = 6 - fnPIO1 pinFunc = 7 + // Connect a PWM slice to GPIO. There are eight PWM slices, + // each with two outputchannels (A/B). The B pin can also be used as an input, + // for frequency and duty cyclemeasurement + fnPWM pinFunc = 4 + // Software control of GPIO, from the single-cycle IO (SIO) block. + // The SIO function (F5)must be selected for the processors to drive a GPIO, + // but the input is always connected,so software can check the state of GPIOs at any time. + fnSIO pinFunc = 5 + // Connect one of the programmable IO blocks (PIO) to GPIO. PIO can implement a widevariety of interfaces, + // and has its own internal pin mapping hardware, allowing flexibleplacement of digital interfaces on bank 0 GPIOs. + // The PIO function (F6, F7) must beselected for PIO to drive a GPIO, but the input is always connected, + // so the PIOs canalways see the state of all pins. + fnPIO0, fnPIO1 pinFunc = 6, 7 + // General purpose clock inputs/outputs. Can be routed to a number of internal clock domains onRP2040, + // e.g. Input: to provide a 1 Hz clock for the RTC, or can be connected to an internalfrequency counter. + // e.g. Output: optional integer divide fnGPCK pinFunc = 8 + // USB power control signals to/from the internal USB controller fnUSB pinFunc = 9 fnNULL pinFunc = 0x1f @@ -68,6 +81,7 @@ const ( PinInputPullup PinAnalog PinUART + PinI2C PinSPI ) @@ -91,7 +105,7 @@ func (p Pin) xor() { // get returns the pin value func (p Pin) get() bool { - return rp.SIO.GPIO_IN.HasBits(uint32(1) << p) + return rp.SIO.GPIO_IN.HasBits(1 << p) } func (p Pin) ioCtrl() *volatile.Register32 { @@ -117,6 +131,17 @@ func (p Pin) pulloff() { p.padCtrl().ClearBits(rp.PADS_BANK0_GPIO0_PUE) } +// setSlew sets pad slew rate control. +// true sets to fast. false sets to slow. +func (p Pin) setSlew(sr bool) { + p.padCtrl().ReplaceBits(boolToBit(sr)< 0 { + if err := i2c.tx(uint8(addr), w, false, timeout); nil != err { + return err + } + } + + if len(r) > 0 { + if err := i2c.rx(uint8(addr), r, false, timeout); nil != err { + return err + } + } + + return nil +} + +// Configure initializes i2c peripheral and configures I2C config's pins passed. +// Here's a list of valid SDA and SCL GPIO pins on bus I2C0 of the rp2040: +// SDA: 0, 4, 8, 12, 16, 20 +// SCL: 1, 5, 9, 13, 17, 21 +// Same as above for I2C1 bus: +// SDA: 2, 6, 10, 14, 18, 26 +// SCL: 3, 7, 11, 15, 19, 27 +func (i2c *I2C) Configure(config I2CConfig) error { + if config.SCL == 0 { + // If config pins are zero valued or clock pin is invalid then we set default values. + switch i2c.Bus { + case rp.I2C0: + config.SCL = I2C0_SCL_PIN + config.SDA = I2C0_SDA_PIN + case rp.I2C1: + config.SCL = I2C1_SCL_PIN + config.SDA = I2C1_SDA_PIN + } + } + config.SDA.Configure(PinConfig{PinI2C}) + config.SCL.Configure(PinConfig{PinI2C}) + return i2c.init(config) +} + +// SetBaudrate sets the I2C frequency. It has the side effect of also +// enabling the I2C hardware if disabled beforehand. +//go:inline +func (i2c *I2C) SetBaudrate(br uint32) error { + const freqin uint32 = 125 * MHz + // Find smallest prescale value which puts o + + // TODO there are some subtleties to I2C timing which we are completely ignoring here + period := (freqin + br/2) / br + lcnt := period * 3 / 5 // oof this one hurts + hcnt := period - lcnt + // Check for out-of-range divisors: + if hcnt > rp.I2C0_IC_FS_SCL_HCNT_IC_FS_SCL_HCNT_Msk || hcnt < 8 || lcnt > rp.I2C0_IC_FS_SCL_LCNT_IC_FS_SCL_LCNT_Msk || lcnt < 8 { + return ErrInvalidI2CBaudrate + } + + // Per I2C-bus specification a device in standard or fast mode must + // internally provide a hold time of at least 300ns for the SDA signal to + // bridge the undefined region of the falling edge of SCL. A smaller hold + // time of 120ns is used for fast mode plus. + + // sda_tx_hold_count = freq_in [cycles/s] * 300ns * (1s / 1e9ns) + // Reduce 300/1e9 to 3/1e7 to avoid numbers that don't fit in uint. + // Add 1 to avoid division truncation. + sdaTxHoldCnt := ((freqin * 3) / 10000000) + 1 + if br >= 1_000_000 { + // sda_tx_hold_count = freq_in [cycles/s] * 120ns * (1s / 1e9ns) + // Reduce 120/1e9 to 3/25e6 to avoid numbers that don't fit in uint. + // Add 1 to avoid division truncation. + sdaTxHoldCnt = ((freqin * 3) / 25000000) + 1 + } + + if sdaTxHoldCnt > lcnt-2 { + return ErrInvalidI2CBaudrate + } + err := i2c.disable() + if err != nil { + return err + } + // Always use "fast" mode (<= 400 kHz, works fine for standard mode too) + + i2c.Bus.IC_CON.ReplaceBits(rp.I2C0_IC_CON_SPEED_FAST< deadline { + return ErrRP2040I2CDisable + } + } + return nil +} + +//go:inline +func (i2c *I2C) init(config I2CConfig) error { + i2c.reset() + if err := i2c.disable(); err != nil { + return err + } + i2c.restartOnNext = false + // Configure as a fast-mode master with RepStart support, 7-bit addresses + i2c.Bus.IC_CON.Set((rp.I2C0_IC_CON_SPEED_FAST << rp.I2C0_IC_CON_SPEED_Pos) | + rp.I2C0_IC_CON_MASTER_MODE | rp.I2C0_IC_CON_IC_SLAVE_DISABLE | + rp.I2C0_IC_CON_IC_RESTART_EN | rp.I2C0_IC_CON_TX_EMPTY_CTRL) // sets TX_EMPTY_CTRL to enable TX_EMPTY interrupt status + + // Set FIFO watermarks to 1 to make things simpler. This is encoded by a register value of 0. + i2c.Bus.IC_TX_TL.Set(0) + i2c.Bus.IC_RX_TL.Set(0) + + // Always enable the DREQ signalling -- harmless if DMA isn't listening + i2c.Bus.IC_DMA_CR.Set(rp.I2C0_IC_DMA_CR_TDMAE | rp.I2C0_IC_DMA_CR_RDMAE) + return i2c.SetBaudrate(config.Frequency) +} + +// reset sets I2C register RESET bits in the reset peripheral and then clears them. +//go:inline +func (i2c *I2C) reset() { + resetVal := i2c.deinit() + rp.RESETS.RESET.ClearBits(resetVal) + // Wait until reset is done. + for !rp.RESETS.RESET_DONE.HasBits(resetVal) { + } +} + +// deinit sets reset bit for I2C. Must call reset to reenable I2C after deinit. +//go:inline +func (i2c *I2C) deinit() (resetVal uint32) { + switch { + case i2c.Bus == rp.I2C0: + resetVal = rp.RESETS_RESET_I2C0 + case i2c.Bus == rp.I2C1: + resetVal = rp.RESETS_RESET_I2C1 + } + // Perform I2C reset. + rp.RESETS.RESET.SetBits(resetVal) + + return resetVal +} + +// tx is a primitive i2c blocking write to bus routine. timeout is time to wait +// in microseconds since calling this function for write to finish. +func (i2c *I2C) tx(addr uint8, tx []byte, nostop bool, timeout uint64) (err error) { + deadline := ticks() + timeout + if addr >= 0x80 || isReservedI2CAddr(addr) { + return ErrInvalidTgtAddr + } + tlen := len(tx) + // Quick return if possible. + if tlen == 0 { + return nil + } + + err = i2c.disable() + if err != nil { + return err + } + i2c.Bus.IC_TAR.Set(uint32(addr)) + i2c.enable() + // If no timeout was passed timeoutCheck is false. + abort := false + var abortReason uint32 + byteCtr := 0 + for ; byteCtr < tlen; byteCtr++ { + first := byteCtr == 0 + last := byteCtr == tlen-1 + i2c.Bus.IC_DATA_CMD.Set( + (boolToBit(first && i2c.restartOnNext) << rp.I2C0_IC_DATA_CMD_RESTART_Pos) | + (boolToBit(last && !nostop) << rp.I2C0_IC_DATA_CMD_STOP_Pos) | + uint32(tx[byteCtr])) + + // Wait until the transmission of the address/data from the internal + // shift register has completed. For this to function correctly, the + // TX_EMPTY_CTRL flag in IC_CON must be set. The TX_EMPTY_CTRL flag + // was set in i2c_init. + + // IC_RAW_INTR_STAT_TX_EMPTY: This bit is set to 1 when the transmit buffer is at or below + // the threshold value set in the IC_TX_TL register and the + // transmission of the address/data from the internal shift + // register for the most recently popped command is + // completed. It is automatically cleared by hardware when + // the buffer level goes above the threshold. When + // IC_ENABLE[0] is set to 0, the TX FIFO is flushed and held + // in reset. There the TX FIFO looks like it has no data within + // it, so this bit is set to 1, provided there is activity in the + // master or slave state machines. When there is no longer + // any activity, then with ic_en=0, this bit is set to 0. + for !i2c.interrupted(rp.I2C0_IC_RAW_INTR_STAT_TX_EMPTY) { + if ticks() > deadline { + i2c.restartOnNext = nostop + println(1) + return errI2CWriteTimeout // If there was a timeout, don't attempt to do anything else. + } + } + + abortReason = i2c.getAbortReason() + if abortReason != 0 { + i2c.clearAbortReason() + abort = true + } + if abort || (last && !nostop) { + // If the transaction was aborted or if it completed + // successfully wait until the STOP condition has occured. + + // TODO Could there be an abort while waiting for the STOP + // condition here? If so, additional code would be needed here + // to take care of the abort. + for !i2c.interrupted(rp.I2C0_IC_RAW_INTR_STAT_STOP_DET) { + if ticks() > deadline { + println(2) + i2c.restartOnNext = nostop + return errI2CWriteTimeout + } + } + i2c.Bus.IC_CLR_STOP_DET.Get() + } + } + + // From Pico SDK: A lot of things could have just happened due to the ingenious and + // creative design of I2C. Try to figure things out. + if abort { + switch { + case abortReason == 0 || abortReason&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_7B_ADDR_NOACK != 0: + // No reported errors - seems to happen if there is nothing connected to the bus. + // Address byte not acknowledged + err = ErrI2CGeneric + case abortReason&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_TXDATA_NOACK != 0: + // Address acknowledged, some data not acknowledged + fallthrough + default: + // panic("unknown i2c abortReason:" + strconv.Itoa(abortReason) + err = makeI2CBuffError(byteCtr) + } + } + + // nostop means we are now at the end of a *message* but not the end of a *transfer* + i2c.restartOnNext = nostop + return err +} + +// rx is a primitive i2c blocking read routine. timeout is time to wait +// in microseconds since calling this function for read to finish. +func (i2c *I2C) rx(addr uint8, rx []byte, nostop bool, timeout uint64) (err error) { + deadline := ticks() + timeout + if addr >= 0x80 || isReservedI2CAddr(addr) { + return ErrInvalidTgtAddr + } + rlen := len(rx) + // Quick return if possible. + if rlen == 0 { + return nil + } + err = i2c.disable() + if err != nil { + return err + } + i2c.Bus.IC_TAR.Set(uint32(addr)) + i2c.enable() + // If no timeout was passed timeoutCheck is false. + abort := false + var abortReason uint32 + byteCtr := 0 + for ; byteCtr < rlen; byteCtr++ { + first := byteCtr == 0 + last := byteCtr == rlen-1 + for i2c.writeAvailable() == 0 { + } + i2c.Bus.IC_DATA_CMD.Set( + boolToBit(first && i2c.restartOnNext)< 1 for read + + for !abort && i2c.readAvailable() == 0 { + abortReason = i2c.getAbortReason() + i2c.clearAbortReason() + if abortReason != 0 { + abort = true + } + if ticks() > deadline { + i2c.restartOnNext = nostop + return errI2CReadTimeout // If there was a timeout, don't attempt to do anything else. + } + } + if abort { + break + } + rx[byteCtr] = uint8(i2c.Bus.IC_DATA_CMD.Get()) + } + + if abort { + switch { + case abortReason == 0 || abortReason&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_7B_ADDR_NOACK != 0: + // No reported errors - seems to happen if there is nothing connected to the bus. + // Address byte not acknowledged + err = ErrI2CGeneric + default: + // undefined abort sequence + err = makeI2CBuffError(byteCtr) + } + } + + i2c.restartOnNext = nostop + return err +} + +// writeAvailable determines non-blocking write space available +//go:inline +func (i2c *I2C) writeAvailable() uint32 { + return rp.I2C0_IC_COMP_PARAM_1_TX_BUFFER_DEPTH_Pos - i2c.Bus.IC_TXFLR.Get() +} + +// readAvailable determines number of bytes received +//go:inline +func (i2c *I2C) readAvailable() uint32 { + return i2c.Bus.IC_RXFLR.Get() +} + +// Equivalent to IC_CLR_TX_ABRT.Get() (side effect clears ABORT_REASON) +//go:inline +func (i2c *I2C) clearAbortReason() { + // Note clearing the abort flag also clears the reason, and + // this instance of flag is clear-on-read! Note also the + // IC_CLR_TX_ABRT register always reads as 0. + i2c.Bus.IC_CLR_TX_ABRT.Get() +} + +//go:inline +func (i2c *I2C) getAbortReason() uint32 { + return i2c.Bus.IC_TX_ABRT_SOURCE.Get() +} + +// returns true if RAW_INTR_STAT bits in mask are all set. performs: +// RAW_INTR_STAT & mask == mask +//go:inline +func (i2c *I2C) interrupted(mask uint32) bool { + reg := i2c.Bus.IC_RAW_INTR_STAT.Get() + return reg&mask == mask +} + +type i2cBuffError int + +func (b i2cBuffError) Error() string { + return "i2c err after addr ack at data " + strconv.Itoa(int(b)) +} + +//go:inline +func makeI2CBuffError(idx int) error { + return i2cBuffError(idx) +} + +//go:inline +func boolToBit(a bool) uint32 { + if a { + return 1 + } + return 0 +} + +//go:inline +func u32max(a, b uint32) uint32 { + if a > b { + return a + } + return b +} + +//go:inline +func isReservedI2CAddr(addr uint8) bool { + return (addr&0x78) == 0 || (addr&0x78) == 0x78 +} From d05103668f207f8d33d82d1c306692557cc81025 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 6 Aug 2021 21:00:51 +0200 Subject: [PATCH 06/47] crypto/rand: switch to arc4random_buf This doesn't have the potential blocking issue of the getentropy call (which calls WASI random_get when using wasi-libc) and should therefore be a lot faster. For context, this is what the random_get documentation says: > Write high-quality random data into a buffer. This function blocks > when the implementation is unable to immediately provide sufficient > high-quality random data. This function may execute slowly, so when > large mounts of random data are required, it's advisable to use this > function to seed a pseudo-random number generator, rather than to > provide the random data directly. --- src/crypto/rand/rand_arc4random.go | 30 +++++++++++++++++++++++ src/crypto/rand/rand_getentropy.go | 38 ------------------------------ 2 files changed, 30 insertions(+), 38 deletions(-) create mode 100644 src/crypto/rand/rand_arc4random.go delete mode 100644 src/crypto/rand/rand_getentropy.go diff --git a/src/crypto/rand/rand_arc4random.go b/src/crypto/rand/rand_arc4random.go new file mode 100644 index 000000000..cbc76af94 --- /dev/null +++ b/src/crypto/rand/rand_arc4random.go @@ -0,0 +1,30 @@ +// +build darwin freebsd tinygo.wasm + +// This implementation of crypto/rand uses the arc4random_buf function +// (available on both MacOS and WASI) to generate random numbers. +// +// Note: arc4random_buf (unlike what the name suggets) does not use the insecure +// RC4 cipher. Instead, it uses a high-quality cipher, varying by the libc +// implementation. + +package rand + +import "unsafe" + +func init() { + Reader = &reader{} +} + +type reader struct { +} + +func (r *reader) Read(b []byte) (n int, err error) { + if len(b) != 0 { + libc_arc4random_buf(unsafe.Pointer(&b[0]), uint(len(b))) + } + return len(b), nil +} + +// void arc4random_buf(void *buf, size_t buflen); +//export arc4random_buf +func libc_arc4random_buf(buf unsafe.Pointer, buflen uint) diff --git a/src/crypto/rand/rand_getentropy.go b/src/crypto/rand/rand_getentropy.go deleted file mode 100644 index 4cd037956..000000000 --- a/src/crypto/rand/rand_getentropy.go +++ /dev/null @@ -1,38 +0,0 @@ -// +build darwin freebsd tinygo.wasm - -// This implementation of crypto/rand uses the getentropy system call (available -// on both MacOS and WASI) to generate random numbers. - -package rand - -import ( - "errors" - "unsafe" -) - -var errReadFailed = errors.New("rand: could not read random bytes") - -func init() { - Reader = &reader{} -} - -type reader struct { -} - -func (r *reader) Read(b []byte) (n int, err error) { - if len(b) != 0 { - if len(b) > 256 { - b = b[:256] - } - result := libc_getentropy(unsafe.Pointer(&b[0]), len(b)) - if result < 0 { - // Maybe we should return a syscall.Errno here? - return 0, errReadFailed - } - } - return len(b), nil -} - -// int getentropy(void *buf, size_t buflen); -//export getentropy -func libc_getentropy(buf unsafe.Pointer, buflen int) int From 6c1301688b9b16d2b9d3aa23534592ed87640b01 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 5 Aug 2021 15:53:29 +0200 Subject: [PATCH 07/47] math: fix math.Max and math.Min The math package failed the package tests on arm64 and wasm: GOARCH=arm64 tinygo test math Apparently the builtins llvm.maximum.f64 and llvm.minimum.f64 have slightly different behavior on arm64 and wasm compared to what Go expects. --- src/runtime/math.go | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/runtime/math.go b/src/runtime/math.go index 25a48d4f2..5498da6f4 100644 --- a/src/runtime/math.go +++ b/src/runtime/math.go @@ -168,29 +168,13 @@ func math_Log2(x float64) float64 { return math_log2(x) } func math_log2(x float64) float64 //go:linkname math_Max math.Max -func math_Max(x, y float64) float64 { - if GOARCH == "arm64" || GOARCH == "wasm" { - return llvm_maximum(x, y) - } - return math_max(x, y) -} - -//export llvm.maximum.f64 -func llvm_maximum(x, y float64) float64 +func math_Max(x, y float64) float64 { return math_max(x, y) } //go:linkname math_max math.max func math_max(x, y float64) float64 //go:linkname math_Min math.Min -func math_Min(x, y float64) float64 { - if GOARCH == "arm64" || GOARCH == "wasm" { - return llvm_minimum(x, y) - } - return math_min(x, y) -} - -//export llvm.minimum.f64 -func llvm_minimum(x, y float64) float64 +func math_Min(x, y float64) float64 { return math_min(x, y) } //go:linkname math_min math.min func math_min(x, y float64) float64 From ca7c849da36ff6bc614a1d3a4520b74fcbc2b5cf Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 6 Aug 2021 01:37:41 +0200 Subject: [PATCH 08/47] 386: bump minimum requirement to the Pentium 4 Previously we used the i386 target, probably with all optional features disabled. However, the Pentium 4 has been released a _long_ time ago and it seems reasonable to me to take that as a minimum requirement. Upstream Go now also seems to move in this direction: https://github.com/golang/go/issues/40255 The main motivation for this is that there were floating point issues when running the tests for the math package: GOARCH=386 tinygo test math I haven't investigated what's the issue, but I strongly suspect it's caused by the weird x87 80-bit floating point format. This could perhaps be fixed in a different way (by setting the FPU precision to 64 bits) but I figured that just setting the minimum requirement to the Pentium 4 would probably be fine. If needed, we can respect the GO386 environment variable to support these very old CPUs. To support this newer CPU, I had to make sure that the stack is aligned to 16 bytes everywhere. This was not yet always the case. --- compileopts/target.go | 3 +++ src/internal/task/task_stack_386.go | 3 +++ src/runtime/arch_386.go | 2 +- src/runtime/gc_386.S | 3 ++- src/runtime/math.go | 2 +- 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/compileopts/target.go b/compileopts/target.go index ba5f73826..19951426b 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -253,6 +253,9 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { GDB: []string{"gdb"}, PortReset: "false", } + if goarch == "386" { + spec.CPU = "pentium4" + } if goos == "darwin" { spec.CFlags = append(spec.CFlags, "-isysroot", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk") spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip") diff --git a/src/internal/task/task_stack_386.go b/src/internal/task/task_stack_386.go index 3f1cd3ee0..c0f066594 100644 --- a/src/internal/task/task_stack_386.go +++ b/src/internal/task/task_stack_386.go @@ -16,6 +16,9 @@ type calleeSavedRegs struct { ebp uintptr pc uintptr + + // Pad this struct so that tasks start on a 16-byte aligned stack. + _ [3]uintptr } // archInit runs architecture-specific setup for the goroutine startup. diff --git a/src/runtime/arch_386.go b/src/runtime/arch_386.go index 2dd7c9fff..9f1a99c05 100644 --- a/src/runtime/arch_386.go +++ b/src/runtime/arch_386.go @@ -9,7 +9,7 @@ const TargetBits = 32 // Align on word boundary. func align(ptr uintptr) uintptr { - return (ptr + 3) &^ 3 + return (ptr + 15) &^ 15 } func getCurrentStackPointer() uintptr { diff --git a/src/runtime/gc_386.S b/src/runtime/gc_386.S index 3ca801510..9604ddbd0 100644 --- a/src/runtime/gc_386.S +++ b/src/runtime/gc_386.S @@ -13,10 +13,11 @@ tinygo_scanCurrentStack: pushl %ebp // Scan the stack. + subl $8, %esp // adjust the stack before the call to maintain 16-byte alignment pushl %esp calll tinygo_scanstack // Restore the stack pointer. Registers do not need to be restored as they // were only pushed to be discoverable by the GC. - addl $20, %esp + addl $28, %esp retl diff --git a/src/runtime/math.go b/src/runtime/math.go index 5498da6f4..f16173858 100644 --- a/src/runtime/math.go +++ b/src/runtime/math.go @@ -217,7 +217,7 @@ func math_sinh(x float64) float64 //go:linkname math_Sqrt math.Sqrt func math_Sqrt(x float64) float64 { - if GOARCH == "x86" || GOARCH == "amd64" || GOARCH == "wasm" { + if GOARCH == "386" || GOARCH == "amd64" || GOARCH == "wasm" { return llvm_sqrt(x) } return math_sqrt(x) From 58565b42cc9bae78c0f79fe989ecb85e95a4e0d1 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 6 Aug 2021 15:49:02 +0200 Subject: [PATCH 09/47] compiler: move LLVM math builtin support into the compiler This simplifies src/runtime/math.go, which I eventually want to remove entirely by moving the given functionality into the compiler. --- compiler/compiler.go | 7 ++- compiler/compiler_test.go | 2 + compiler/intrinsics.go | 54 +++++++++++++++++++ compiler/testdata/intrinsics-cortex-m-qemu.ll | 27 ++++++++++ compiler/testdata/intrinsics-wasm.ll | 31 +++++++++++ compiler/testdata/intrinsics.go | 14 +++++ src/runtime/math.go | 40 ++------------ 7 files changed, 138 insertions(+), 37 deletions(-) create mode 100644 compiler/testdata/intrinsics-cortex-m-qemu.ll create mode 100644 compiler/testdata/intrinsics-wasm.ll create mode 100644 compiler/testdata/intrinsics.go diff --git a/compiler/compiler.go b/compiler/compiler.go index 7841cd656..94bbc1afd 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -23,7 +23,7 @@ import ( // Version of the compiler pacakge. Must be incremented each time the compiler // package changes in a way that affects the generated LLVM module. // This version is independent of the TinyGo version number. -const Version = 12 // last change: implement syscall.rawSyscallNoError +const Version = 13 // last change: implement LLVM math builtins in the compiler func init() { llvm.InitializeAllTargets() @@ -1298,6 +1298,11 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) return b.createMemoryCopyCall(fn, instr.Args) case name == "runtime.memzero": return b.createMemoryZeroCall(instr.Args) + case name == "math.Ceil" || name == "math.Floor" || name == "math.Sqrt" || name == "math.Trunc": + result, ok := b.createMathOp(instr) + if ok { + return result, nil + } case name == "device.Asm" || name == "device/arm.Asm" || name == "device/arm64.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm": return b.createInlineAsm(instr.Args) case name == "device.AsmFull" || name == "device/arm.AsmFull" || name == "device/arm64.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull": diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 45d5b8c3a..6db5ebb38 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -48,6 +48,8 @@ func TestCompiler(t *testing.T) { {"pragma.go", ""}, {"goroutine.go", "wasm"}, {"goroutine.go", "cortex-m-qemu"}, + {"intrinsics.go", "cortex-m-qemu"}, + {"intrinsics.go", "wasm"}, } for _, tc := range tests { diff --git a/compiler/intrinsics.go b/compiler/intrinsics.go index 634e8362e..aeab5b11f 100644 --- a/compiler/intrinsics.go +++ b/compiler/intrinsics.go @@ -48,3 +48,57 @@ func (b *builder) createMemoryZeroCall(args []ssa.Value) (llvm.Value, error) { b.CreateCall(llvmFn, params, "") return llvm.Value{}, nil } + +var mathToLLVMMapping = map[string]string{ + "math.Sqrt": "llvm.sqrt.f64", + "math.Floor": "llvm.floor.f64", + "math.Ceil": "llvm.ceil.f64", + "math.Trunc": "llvm.trunc.f64", +} + +// createMathOp tries to lower the given call as a LLVM math intrinsic, if +// possible. It returns the call result if possible, and a boolean whether it +// succeeded. If it doesn't succeed, the architecture doesn't support the given +// intrinsic. +func (b *builder) createMathOp(call *ssa.CallCommon) (llvm.Value, bool) { + // Check whether this intrinsic is supported on the given GOARCH. + // If it is unsupported, this can have two reasons: + // + // 1. LLVM can expand the intrinsic inline (using float instructions), but + // the result doesn't pass the tests of the math package. + // 2. LLVM cannot expand the intrinsic inline, will therefore lower it as a + // libm function call, but the libm function call also fails the math + // package tests. + // + // Whatever the implementation, it must pass the tests in the math package + // so unfortunately only the below intrinsic+architecture combinations are + // supported. + name := call.StaticCallee().RelString(nil) + switch name { + case "math.Ceil", "math.Floor", "math.Trunc": + if b.GOARCH != "wasm" && b.GOARCH != "arm64" { + return llvm.Value{}, false + } + case "math.Sqrt": + if b.GOARCH != "wasm" && b.GOARCH != "amd64" && b.GOARCH != "386" { + return llvm.Value{}, false + } + default: + return llvm.Value{}, false // only the above functions are supported. + } + + llvmFn := b.mod.NamedFunction(mathToLLVMMapping[name]) + if llvmFn.IsNil() { + // The intrinsic doesn't exist yet, so declare it. + // At the moment, all supported intrinsics have the form "double + // foo(double %x)" so we can hardcode the signature here. + llvmType := llvm.FunctionType(b.ctx.DoubleType(), []llvm.Type{b.ctx.DoubleType()}, false) + llvmFn = llvm.AddFunction(b.mod, mathToLLVMMapping[name], llvmType) + } + // Create a call to the intrinsic. + args := make([]llvm.Value, len(call.Args)) + for i, arg := range call.Args { + args[i] = b.getValue(arg) + } + return b.CreateCall(llvmFn, args, ""), true +} diff --git a/compiler/testdata/intrinsics-cortex-m-qemu.ll b/compiler/testdata/intrinsics-cortex-m-qemu.ll new file mode 100644 index 000000000..e3e7580c7 --- /dev/null +++ b/compiler/testdata/intrinsics-cortex-m-qemu.ll @@ -0,0 +1,27 @@ +; ModuleID = 'intrinsics.go' +source_filename = "intrinsics.go" +target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64" +target triple = "armv7m-none-eabi" + +declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) + +define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr { +entry: + ret void +} + +define hidden double @main.mySqrt(double %x, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = call double @math.Sqrt(double %x, i8* undef, i8* undef) + ret double %0 +} + +declare double @math.Sqrt(double, i8*, i8*) + +define hidden double @main.myTrunc(double %x, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = call double @math.Trunc(double %x, i8* undef, i8* undef) + ret double %0 +} + +declare double @math.Trunc(double, i8*, i8*) diff --git a/compiler/testdata/intrinsics-wasm.ll b/compiler/testdata/intrinsics-wasm.ll new file mode 100644 index 000000000..433b0a7e9 --- /dev/null +++ b/compiler/testdata/intrinsics-wasm.ll @@ -0,0 +1,31 @@ +; ModuleID = 'intrinsics.go' +source_filename = "intrinsics.go" +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32--wasi" + +declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) + +define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr { +entry: + ret void +} + +define hidden double @main.mySqrt(double %x, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = call double @llvm.sqrt.f64(double %x) + ret double %0 +} + +; Function Attrs: nounwind readnone speculatable willreturn +declare double @llvm.sqrt.f64(double) #0 + +define hidden double @main.myTrunc(double %x, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = call double @llvm.trunc.f64(double %x) + ret double %0 +} + +; Function Attrs: nounwind readnone speculatable willreturn +declare double @llvm.trunc.f64(double) #0 + +attributes #0 = { nounwind readnone speculatable willreturn } diff --git a/compiler/testdata/intrinsics.go b/compiler/testdata/intrinsics.go new file mode 100644 index 000000000..fdca446f0 --- /dev/null +++ b/compiler/testdata/intrinsics.go @@ -0,0 +1,14 @@ +package main + +// Test how intrinsics are lowered: either as regular calls to the math +// functions or as LLVM builtins (such as llvm.sqrt.f64). + +import "math" + +func mySqrt(x float64) float64 { + return math.Sqrt(x) +} + +func myTrunc(x float64) float64 { + return math.Trunc(x) +} diff --git a/src/runtime/math.go b/src/runtime/math.go index f16173858..73af15915 100644 --- a/src/runtime/math.go +++ b/src/runtime/math.go @@ -56,15 +56,7 @@ func math_Cbrt(x float64) float64 { return math_cbrt(x) } func math_cbrt(x float64) float64 //go:linkname math_Ceil math.Ceil -func math_Ceil(x float64) float64 { - if GOARCH == "arm64" || GOARCH == "wasm" { - return llvm_ceil(x) - } - return math_ceil(x) -} - -//export llvm.ceil.f64 -func llvm_ceil(x float64) float64 +func math_Ceil(x float64) float64 { return math_ceil(x) } //go:linkname math_ceil math.ceil func math_ceil(x float64) float64 @@ -112,15 +104,7 @@ func math_Exp2(x float64) float64 { return math_exp2(x) } func math_exp2(x float64) float64 //go:linkname math_Floor math.Floor -func math_Floor(x float64) float64 { - if GOARCH == "arm64" || GOARCH == "wasm" { - return llvm_floor(x) - } - return math_floor(x) -} - -//export llvm.floor.f64 -func llvm_floor(x float64) float64 +func math_Floor(x float64) float64 { return math_floor(x) } //go:linkname math_floor math.floor func math_floor(x float64) float64 @@ -216,15 +200,7 @@ func math_Sinh(x float64) float64 { return math_sinh(x) } func math_sinh(x float64) float64 //go:linkname math_Sqrt math.Sqrt -func math_Sqrt(x float64) float64 { - if GOARCH == "386" || GOARCH == "amd64" || GOARCH == "wasm" { - return llvm_sqrt(x) - } - return math_sqrt(x) -} - -//export llvm.sqrt.f64 -func llvm_sqrt(x float64) float64 +func math_Sqrt(x float64) float64 { return math_sqrt(x) } //go:linkname math_sqrt math.sqrt func math_sqrt(x float64) float64 @@ -242,15 +218,7 @@ func math_Tanh(x float64) float64 { return math_tanh(x) } func math_tanh(x float64) float64 //go:linkname math_Trunc math.Trunc -func math_Trunc(x float64) float64 { - if GOARCH == "arm64" || GOARCH == "wasm" { - return llvm_trunc(x) - } - return math_trunc(x) -} - -//export llvm.trunc.f64 -func llvm_trunc(x float64) float64 +func math_Trunc(x float64) float64 { return math_trunc(x) } //go:linkname math_trunc math.trunc func math_trunc(x float64) float64 From a3c4421f39a219772817a8497ef7e31ace48ff2f Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 6 Aug 2021 17:53:12 +0200 Subject: [PATCH 10/47] compiler: move math aliases from the runtime to the compiler This makes them more flexible, especially with Go 1.17 making the situation more complicated (see https://github.com/golang/go/commit/1d20a362d0ca4898d77865e314ef6f73582daef0). It also makes it possible to do the same for many other functions, such as assembly implementations of cryptographich functions which are similarly dependent on the architecture. --- compiler/alias.go | 81 ++++++++++++++++ compiler/compiler.go | 25 ++++- src/runtime/math.go | 224 ------------------------------------------- 3 files changed, 105 insertions(+), 225 deletions(-) create mode 100644 compiler/alias.go delete mode 100644 src/runtime/math.go diff --git a/compiler/alias.go b/compiler/alias.go new file mode 100644 index 000000000..d599e2ea0 --- /dev/null +++ b/compiler/alias.go @@ -0,0 +1,81 @@ +package compiler + +// This file defines alias functions for functions that are normally defined in +// Go assembly. +// +// The Go toolchain defines many performance critical functions in assembly +// instead of plain Go. This is a problem for TinyGo as it currently (as of +// august 2021) is not able to compile these assembly files and even if it +// could, it would not be able to make use of them for many targets that are +// supported by TinyGo (baremetal RISC-V, AVR, etc). Therefore, many of these +// functions are aliased to their generic Go implementation. +// This results in slower than possible implementations, but at least they are +// usable. + +import "tinygo.org/x/go-llvm" + +var stdlibAliases = map[string]string{ + // math package + "math.Asin": "math.asin", + "math.Asinh": "math.asinh", + "math.Acos": "math.acos", + "math.Acosh": "math.acosh", + "math.Atan": "math.atan", + "math.Atanh": "math.atanh", + "math.Atan2": "math.atan2", + "math.Cbrt": "math.cbrt", + "math.Ceil": "math.ceil", + "math.Cos": "math.cos", + "math.Cosh": "math.cosh", + "math.Erf": "math.erf", + "math.Erfc": "math.erfc", + "math.Exp": "math.exp", + "math.Expm1": "math.expm1", + "math.Exp2": "math.exp2", + "math.Floor": "math.floor", + "math.Frexp": "math.frexp", + "math.Hypot": "math.hypot", + "math.Ldexp": "math.ldexp", + "math.Log": "math.log", + "math.Log1p": "math.log1p", + "math.Log10": "math.log10", + "math.Log2": "math.log2", + "math.Max": "math.max", + "math.Min": "math.min", + "math.Mod": "math.mod", + "math.Modf": "math.modf", + "math.Pow": "math.pow", + "math.Remainder": "math.remainder", + "math.Sin": "math.sin", + "math.Sinh": "math.sinh", + "math.Sqrt": "math.sqrt", + "math.Tan": "math.tan", + "math.Tanh": "math.tanh", + "math.Trunc": "math.trunc", +} + +// createAlias implements the function (in the builder) as a call to the alias +// function. +func (b *builder) createAlias(alias llvm.Value) { + if b.Debug { + if b.fn.Syntax() != nil { + // Create debug info file if present. + b.difunc = b.attachDebugInfo(b.fn) + } + pos := b.program.Fset.Position(b.fn.Pos()) + b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{}) + } + entryBlock := llvm.AddBasicBlock(b.llvmFn, "entry") + b.SetInsertPointAtEnd(entryBlock) + if b.llvmFn.Type() != alias.Type() { + b.addError(b.fn.Pos(), "alias function should have the same type as aliasee "+alias.Name()) + b.CreateUnreachable() + return + } + result := b.CreateCall(alias, b.llvmFn.Params(), "") + if result.Type().TypeKind() == llvm.VoidTypeKind { + b.CreateRetVoid() + } else { + b.CreateRet(result) + } +} diff --git a/compiler/compiler.go b/compiler/compiler.go index 94bbc1afd..f82730392 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -23,7 +23,7 @@ import ( // Version of the compiler pacakge. Must be incremented each time the compiler // package changes in a way that affects the generated LLVM module. // This version is independent of the TinyGo version number. -const Version = 13 // last change: implement LLVM math builtins in the compiler +const Version = 14 // last change: add math assembly aliases func init() { llvm.InitializeAllTargets() @@ -768,6 +768,29 @@ func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package } } } + + // Add forwarding functions for functions that would otherwise be + // implemented in assembly. + for _, name := range members { + member := pkg.Members[name] + switch member := member.(type) { + case *ssa.Function: + if member.Blocks != nil { + continue // external function + } + info := c.getFunctionInfo(member) + if aliasName, ok := stdlibAliases[info.linkName]; ok { + alias := c.mod.NamedFunction(aliasName) + if alias.IsNil() { + // Shouldn't happen, but perhaps best to just ignore. + // The error will be a link error, if there is an error. + continue + } + b := newBuilder(c, irbuilder, member) + b.createAlias(alias) + } + } + } } // createFunction builds the LLVM IR implementation for this function. The diff --git a/src/runtime/math.go b/src/runtime/math.go deleted file mode 100644 index 73af15915..000000000 --- a/src/runtime/math.go +++ /dev/null @@ -1,224 +0,0 @@ -package runtime - -// This file redirects math stubs to their fallback implementation. -// TODO: use optimized versions if possible. - -import ( - _ "unsafe" -) - -//go:linkname math_Asin math.Asin -func math_Asin(x float64) float64 { return math_asin(x) } - -//go:linkname math_asin math.asin -func math_asin(x float64) float64 - -//go:linkname math_Asinh math.Asinh -func math_Asinh(x float64) float64 { return math_asinh(x) } - -//go:linkname math_asinh math.asinh -func math_asinh(x float64) float64 - -//go:linkname math_Acos math.Acos -func math_Acos(x float64) float64 { return math_acos(x) } - -//go:linkname math_acos math.acos -func math_acos(x float64) float64 - -//go:linkname math_Acosh math.Acosh -func math_Acosh(x float64) float64 { return math_acosh(x) } - -//go:linkname math_acosh math.acosh -func math_acosh(x float64) float64 - -//go:linkname math_Atan math.Atan -func math_Atan(x float64) float64 { return math_atan(x) } - -//go:linkname math_atan math.atan -func math_atan(x float64) float64 - -//go:linkname math_Atanh math.Atanh -func math_Atanh(x float64) float64 { return math_atanh(x) } - -//go:linkname math_atanh math.atanh -func math_atanh(x float64) float64 - -//go:linkname math_Atan2 math.Atan2 -func math_Atan2(y, x float64) float64 { return math_atan2(y, x) } - -//go:linkname math_atan2 math.atan2 -func math_atan2(y, x float64) float64 - -//go:linkname math_Cbrt math.Cbrt -func math_Cbrt(x float64) float64 { return math_cbrt(x) } - -//go:linkname math_cbrt math.cbrt -func math_cbrt(x float64) float64 - -//go:linkname math_Ceil math.Ceil -func math_Ceil(x float64) float64 { return math_ceil(x) } - -//go:linkname math_ceil math.ceil -func math_ceil(x float64) float64 - -//go:linkname math_Cos math.Cos -func math_Cos(x float64) float64 { return math_cos(x) } - -//go:linkname math_cos math.cos -func math_cos(x float64) float64 - -//go:linkname math_Cosh math.Cosh -func math_Cosh(x float64) float64 { return math_cosh(x) } - -//go:linkname math_cosh math.cosh -func math_cosh(x float64) float64 - -//go:linkname math_Erf math.Erf -func math_Erf(x float64) float64 { return math_erf(x) } - -//go:linkname math_erf math.erf -func math_erf(x float64) float64 - -//go:linkname math_Erfc math.Erfc -func math_Erfc(x float64) float64 { return math_erfc(x) } - -//go:linkname math_erfc math.erfc -func math_erfc(x float64) float64 - -//go:linkname math_Exp math.Exp -func math_Exp(x float64) float64 { return math_exp(x) } - -//go:linkname math_exp math.exp -func math_exp(x float64) float64 - -//go:linkname math_Expm1 math.Expm1 -func math_Expm1(x float64) float64 { return math_expm1(x) } - -//go:linkname math_expm1 math.expm1 -func math_expm1(x float64) float64 - -//go:linkname math_Exp2 math.Exp2 -func math_Exp2(x float64) float64 { return math_exp2(x) } - -//go:linkname math_exp2 math.exp2 -func math_exp2(x float64) float64 - -//go:linkname math_Floor math.Floor -func math_Floor(x float64) float64 { return math_floor(x) } - -//go:linkname math_floor math.floor -func math_floor(x float64) float64 - -//go:linkname math_Frexp math.Frexp -func math_Frexp(x float64) (float64, int) { return math_frexp(x) } - -//go:linkname math_frexp math.frexp -func math_frexp(x float64) (float64, int) - -//go:linkname math_Hypot math.Hypot -func math_Hypot(p, q float64) float64 { return math_hypot(p, q) } - -//go:linkname math_hypot math.hypot -func math_hypot(p, q float64) float64 - -//go:linkname math_Ldexp math.Ldexp -func math_Ldexp(frac float64, exp int) float64 { return math_ldexp(frac, exp) } - -//go:linkname math_ldexp math.ldexp -func math_ldexp(frac float64, exp int) float64 - -//go:linkname math_Log math.Log -func math_Log(x float64) float64 { return math_log(x) } - -//go:linkname math_log math.log -func math_log(x float64) float64 - -//go:linkname math_Log1p math.Log1p -func math_Log1p(x float64) float64 { return math_log1p(x) } - -//go:linkname math_log1p math.log1p -func math_log1p(x float64) float64 - -//go:linkname math_Log10 math.Log10 -func math_Log10(x float64) float64 { return math_log10(x) } - -//go:linkname math_log10 math.log10 -func math_log10(x float64) float64 - -//go:linkname math_Log2 math.Log2 -func math_Log2(x float64) float64 { return math_log2(x) } - -//go:linkname math_log2 math.log2 -func math_log2(x float64) float64 - -//go:linkname math_Max math.Max -func math_Max(x, y float64) float64 { return math_max(x, y) } - -//go:linkname math_max math.max -func math_max(x, y float64) float64 - -//go:linkname math_Min math.Min -func math_Min(x, y float64) float64 { return math_min(x, y) } - -//go:linkname math_min math.min -func math_min(x, y float64) float64 - -//go:linkname math_Mod math.Mod -func math_Mod(x, y float64) float64 { return math_mod(x, y) } - -//go:linkname math_mod math.mod -func math_mod(x, y float64) float64 - -//go:linkname math_Modf math.Modf -func math_Modf(x float64) (float64, float64) { return math_modf(x) } - -//go:linkname math_modf math.modf -func math_modf(x float64) (float64, float64) - -//go:linkname math_Pow math.Pow -func math_Pow(x, y float64) float64 { return math_pow(x, y) } - -//go:linkname math_pow math.pow -func math_pow(x, y float64) float64 - -//go:linkname math_Remainder math.Remainder -func math_Remainder(x, y float64) float64 { return math_remainder(x, y) } - -//go:linkname math_remainder math.remainder -func math_remainder(x, y float64) float64 - -//go:linkname math_Sin math.Sin -func math_Sin(x float64) float64 { return math_sin(x) } - -//go:linkname math_sin math.sin -func math_sin(x float64) float64 - -//go:linkname math_Sinh math.Sinh -func math_Sinh(x float64) float64 { return math_sinh(x) } - -//go:linkname math_sinh math.sinh -func math_sinh(x float64) float64 - -//go:linkname math_Sqrt math.Sqrt -func math_Sqrt(x float64) float64 { return math_sqrt(x) } - -//go:linkname math_sqrt math.sqrt -func math_sqrt(x float64) float64 - -//go:linkname math_Tan math.Tan -func math_Tan(x float64) float64 { return math_tan(x) } - -//go:linkname math_tan math.tan -func math_tan(x float64) float64 - -//go:linkname math_Tanh math.Tanh -func math_Tanh(x float64) float64 { return math_tanh(x) } - -//go:linkname math_tanh math.tanh -func math_tanh(x float64) float64 - -//go:linkname math_Trunc math.Trunc -func math_Trunc(x float64) float64 { return math_trunc(x) } - -//go:linkname math_trunc math.trunc -func math_trunc(x float64) float64 From 5e5ce98d42f56336779f7969437892cf2df2eabd Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 6 Aug 2021 17:55:45 +0200 Subject: [PATCH 11/47] compiler: add aliases for many hashing packages This commit adds support for the following packages: - crypto/md5 - crypto/sha1 - crypto/sha256 - crypto/sha512 They would normally need assembly implementations, but with these aliases they already work everywhere. --- Makefile | 4 ++++ compiler/alias.go | 7 +++++++ compiler/compiler.go | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 21ee69d94..571613ae1 100644 --- a/Makefile +++ b/Makefile @@ -190,6 +190,10 @@ TEST_PACKAGES = \ container/list \ container/ring \ crypto/des \ + crypto/md5 \ + crypto/sha1 \ + crypto/sha256 \ + crypto/sha512 \ encoding \ encoding/ascii85 \ encoding/base32 \ diff --git a/compiler/alias.go b/compiler/alias.go index d599e2ea0..cc08c8320 100644 --- a/compiler/alias.go +++ b/compiler/alias.go @@ -15,6 +15,13 @@ package compiler import "tinygo.org/x/go-llvm" var stdlibAliases = map[string]string{ + // crypto packages + "crypto/md5.block": "crypto/md5.blockGeneric", + "crypto/sha1.block": "crypto/sha1.blockGeneric", + "crypto/sha1.blockAMD64": "crypto/sha1.blockGeneric", + "crypto/sha256.block": "crypto/sha256.blockGeneric", + "crypto/sha512.blockAMD64": "crypto/sha512.blockGeneric", + // math package "math.Asin": "math.asin", "math.Asinh": "math.asinh", diff --git a/compiler/compiler.go b/compiler/compiler.go index f82730392..ea0d51a67 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -23,7 +23,7 @@ import ( // Version of the compiler pacakge. Must be incremented each time the compiler // package changes in a way that affects the generated LLVM module. // This version is independent of the TinyGo version number. -const Version = 14 // last change: add math assembly aliases +const Version = 15 // last change: add crypto assembly aliases func init() { llvm.InitializeAllTargets() From c25a7cc7477a5fc58d68e125ec2cf62836f23fe2 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 9 Aug 2021 14:26:15 +0200 Subject: [PATCH 12/47] testing: test testing package using `tinygo test` --- Makefile | 3 +-- {tests/tinygotest => src/testing}/benchmark_test.go | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) rename {tests/tinygotest => src/testing}/benchmark_test.go (92%) diff --git a/Makefile b/Makefile index 571613ae1..709dfb112 100644 --- a/Makefile +++ b/Makefile @@ -203,6 +203,7 @@ TEST_PACKAGES = \ hash/crc64 \ math \ math/cmplx \ + testing \ text/scanner \ unicode/utf8 \ @@ -211,8 +212,6 @@ TEST_PACKAGES = \ .PHONY: tinygo-test tinygo-test: $(TINYGO) test $(TEST_PACKAGES) - # until "test testing" passes - cd tests/tinygotest && $(TINYGO) test benchmark_test.go .PHONY: smoketest smoketest: diff --git a/tests/tinygotest/benchmark_test.go b/src/testing/benchmark_test.go similarity index 92% rename from tests/tinygotest/benchmark_test.go rename to src/testing/benchmark_test.go index c045c975b..49612d3d4 100644 --- a/tests/tinygotest/benchmark_test.go +++ b/src/testing/benchmark_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package testbench +package testing_test import ( "testing" @@ -11,8 +11,8 @@ import ( var buf = make([]byte, 13579) func NonASCII(b []byte, i int, offset int) int { - for i = offset; i < len(b) + offset; i++ { - if b[i % len(b)] >= 0x80 { + for i = offset; i < len(b)+offset; i++ { + if b[i%len(b)] >= 0x80 { break } } From f57e9622fdb650f14a48cc327db01c216cd0d9c6 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 10 Aug 2021 21:35:52 +0200 Subject: [PATCH 13/47] baremetal,wasm: support command line params and environment variables This is mainly useful to be able to run `tinygo test`, for example: tinygo test -target=cortex-m-qemu -v math This is not currently supported, but will be in the future. --- loader/goroot.go | 2 +- main_test.go | 86 +++++++++++++------ src/runtime/nonhosted.go | 48 +++++++++++ src/runtime/runtime_wasm_js.go | 5 -- ...call_baremetal.go => syscall_nonhosted.go} | 20 ++++- ...ables_baremetal.go => tables_nonhosted.go} | 2 +- 6 files changed, 126 insertions(+), 37 deletions(-) create mode 100644 src/runtime/nonhosted.go rename src/syscall/{syscall_baremetal.go => syscall_nonhosted.go} (88%) rename src/syscall/{tables_baremetal.go => tables_nonhosted.go} (99%) diff --git a/loader/goroot.go b/loader/goroot.go index 6770d0458..8f0acf1b8 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -208,7 +208,7 @@ func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides // with the TinyGo version. This is the case on some targets. func needsSyscallPackage(buildTags []string) bool { for _, tag := range buildTags { - if tag == "baremetal" || tag == "darwin" || tag == "nintendoswitch" || tag == "wasi" { + if tag == "baremetal" || tag == "darwin" || tag == "nintendoswitch" || tag == "tinygo.wasm" { return true } } diff --git a/main_test.go b/main_test.go index 2c1b2f864..a502115e4 100644 --- a/main_test.go +++ b/main_test.go @@ -162,15 +162,15 @@ func runPlatTests(target string, tests []string, t *testing.T) { runTest(name, target, t, nil, nil) }) } + t.Run("env.go", func(t *testing.T) { + t.Parallel() + runTest("env.go", target, t, []string{"first", "second"}, []string{"ENV1=VALUE1", "ENV2=VALUE2"}) + }) if target == "" || target == "wasi" { t.Run("filesystem.go", func(t *testing.T) { t.Parallel() runTest("filesystem.go", target, t, nil, nil) }) - t.Run("env.go", func(t *testing.T) { - t.Parallel() - runTest("env.go", target, t, []string{"first", "second"}, []string{"ENV1=VALUE1", "ENV2=VALUE2"}) - }) } if target == "" || target == "wasi" || target == "wasm" { t.Run("rand.go", func(t *testing.T) { @@ -233,6 +233,42 @@ func runTestWithConfig(name, target string, t *testing.T, options compileopts.Op } }() + // Determine whether we're on a system that supports environment variables + // and command line parameters (operating systems, WASI) or not (baremetal, + // WebAssembly in the browser). If we're on a system without an environment, + // we need to pass command line arguments and environment variables through + // global variables (built into the binary directly) instead of the + // conventional way. + spec, err := compileopts.LoadTarget(target) + if err != nil { + t.Fatal("failed to load target spec:", err) + } + needsEnvInVars := spec.GOOS == "js" + for _, tag := range spec.BuildTags { + if tag == "baremetal" { + needsEnvInVars = true + } + } + if needsEnvInVars { + runtimeGlobals := make(map[string]string) + if len(cmdArgs) != 0 { + runtimeGlobals["osArgs"] = strings.Join(cmdArgs, "\x00") + } + if len(environmentVars) != 0 { + runtimeGlobals["osEnv"] = strings.Join(environmentVars, "\x00") + } + if len(runtimeGlobals) != 0 { + // This sets the global variables like they would be set with + // `-ldflags="-X=runtime.osArgs=first\x00second`. + // The runtime package has two variables (osArgs and osEnv) that are + // both strings, from which the parameters and environment variables + // are read. + options.GlobalValues = map[string]map[string]string{ + "runtime": runtimeGlobals, + } + } + } + // Build the test binary. binary := filepath.Join(tmpdir, "test") err = runBuild("./"+path, binary, &options) @@ -242,37 +278,31 @@ func runTestWithConfig(name, target string, t *testing.T, options compileopts.Op return } - // Run the test. - runComplete := make(chan struct{}) + // Create the test command, taking care of emulators etc. var cmd *exec.Cmd - ranTooLong := false - if target == "" { + if len(spec.Emulator) == 0 { cmd = exec.Command(binary) - cmd.Env = append(cmd.Env, environmentVars...) + } else { + args := append(spec.Emulator[1:], binary) + cmd = exec.Command(spec.Emulator[0], args...) + } + if len(spec.Emulator) != 0 && spec.Emulator[0] == "wasmtime" { + // Allow reading from the current directory. + cmd.Args = append(cmd.Args, "--dir=.") + for _, v := range environmentVars { + cmd.Args = append(cmd.Args, "--env", v) + } cmd.Args = append(cmd.Args, cmdArgs...) } else { - spec, err := compileopts.LoadTarget(target) - if err != nil { - t.Fatal("failed to load target spec:", err) - } - if len(spec.Emulator) == 0 { - cmd = exec.Command(binary) - } else { - args := append(spec.Emulator[1:], binary) - cmd = exec.Command(spec.Emulator[0], args...) - } - - if len(spec.Emulator) != 0 && spec.Emulator[0] == "wasmtime" { - // Allow reading from the current directory. - cmd.Args = append(cmd.Args, "--dir=.") - for _, v := range environmentVars { - cmd.Args = append(cmd.Args, "--env", v) - } - cmd.Args = append(cmd.Args, cmdArgs...) - } else { + if !needsEnvInVars { + cmd.Args = append(cmd.Args, cmdArgs...) // works on qemu-aarch64 etc cmd.Env = append(cmd.Env, environmentVars...) } } + + // Run the test. + runComplete := make(chan struct{}) + ranTooLong := false stdout := &bytes.Buffer{} cmd.Stdout = stdout cmd.Stderr = os.Stderr diff --git a/src/runtime/nonhosted.go b/src/runtime/nonhosted.go new file mode 100644 index 000000000..61f5023df --- /dev/null +++ b/src/runtime/nonhosted.go @@ -0,0 +1,48 @@ +// +build baremetal js + +package runtime + +// This file is for non-hosted environments, that don't support command line +// parameters or environment variables. To still be able to run certain tests, +// command line parameters and environment variables can be passed to the binary +// by setting the variables `runtime.osArgs` and `runtime.osEnv`, both of which +// are strings separated by newlines. +// +// The primary use case is `tinygo test`, which takes some parameters (such as +// -test.v). + +var env []string + +//go:linkname syscall_runtime_envs syscall.runtime_envs +func syscall_runtime_envs() []string { + return env +} + +var osArgs string +var osEnv string + +func init() { + if osArgs != "" { + s := osArgs + start := 0 + for i := 0; i < len(s); i++ { + if s[i] == 0 { + args = append(args, s[start:i]) + start = i + 1 + } + } + args = append(args, s[start:]) + } + + if osEnv != "" { + s := osEnv + start := 0 + for i := 0; i < len(s); i++ { + if s[i] == 0 { + env = append(env, s[start:i]) + start = i + 1 + } + } + env = append(env, s[start:]) + } +} diff --git a/src/runtime/runtime_wasm_js.go b/src/runtime/runtime_wasm_js.go index b39aef0a5..f4335e121 100644 --- a/src/runtime/runtime_wasm_js.go +++ b/src/runtime/runtime_wasm_js.go @@ -15,11 +15,6 @@ func _start() { run() } -//go:linkname syscall_runtime_envs syscall.runtime_envs -func syscall_runtime_envs() []string { - return nil -} - var handleEvent func() //go:linkname setEventHandler syscall/js.setEventHandler diff --git a/src/syscall/syscall_baremetal.go b/src/syscall/syscall_nonhosted.go similarity index 88% rename from src/syscall/syscall_baremetal.go rename to src/syscall/syscall_nonhosted.go index 4f0c6e530..885738e4a 100644 --- a/src/syscall/syscall_baremetal.go +++ b/src/syscall/syscall_nonhosted.go @@ -1,4 +1,4 @@ -// +build baremetal +// +build baremetal js package syscall @@ -47,8 +47,24 @@ const ( O_CLOEXEC = 0 ) +func runtime_envs() []string + func Getenv(key string) (value string, found bool) { - return "", false // stub + env := runtime_envs() + for _, keyval := range env { + // Split at '=' character. + var k, v string + for i := 0; i < len(keyval); i++ { + if keyval[i] == '=' { + k = keyval[:i] + v = keyval[i+1:] + } + } + if k == key { + return v, true + } + } + return "", false } func Open(path string, mode int, perm uint32) (fd int, err error) { diff --git a/src/syscall/tables_baremetal.go b/src/syscall/tables_nonhosted.go similarity index 99% rename from src/syscall/tables_baremetal.go rename to src/syscall/tables_nonhosted.go index 47a536bff..a78eb75fb 100644 --- a/src/syscall/tables_baremetal.go +++ b/src/syscall/tables_nonhosted.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build baremetal nintendoswitch +// +build baremetal nintendoswitch js package syscall From 04f520040e6d60ace0728d0016cbe1e75bc44f71 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 11 Aug 2021 01:09:50 +0200 Subject: [PATCH 14/47] testing: add support for the -test.v flag This flag is passed automatically with the (new) -v flag for TinyGo. For example, this prints all the test outputs: $ tinygo test -v crypto/md5 === RUN TestGolden --- PASS: TestGolden === RUN TestGoldenMarshal --- PASS: TestGoldenMarshal === RUN TestLarge --- PASS: TestLarge === RUN TestBlockGeneric --- PASS: TestBlockGeneric === RUN TestLargeHashes --- PASS: TestLargeHashes PASS ok crypto/md5 0.002s This prints just a summary: $ tinygo test crypto/md5 PASS ok crypto/md5 0.002s (The superfluous 'PASS' message may be removed in the future). This is especially useful when testing a large number of packages: $ tinygo test crypto/md5 crypto/sha1 crypto/sha256 crypto/sha512 PASS ok crypto/md5 0.002s PASS ok crypto/sha1 0.043s PASS ok crypto/sha256 0.002s PASS ok crypto/sha512 0.003s At the moment, the -test.v flag is not supplied to binaries running in emulation. I intend to fix this after https://github.com/tinygo-org/tinygo/pull/2038 lands by refactoring runPackageTest, Run, and runTestWithConfig in the main package which all do something similar. --- main.go | 18 ++++++---- main_test.go | 1 + src/testing/testing.go | 78 +++++++++++++++++++++++++++++++++--------- testdata/testing.go | 42 +++++++++++++++++++++++ testdata/testing.txt | 16 +++++++++ 5 files changed, 133 insertions(+), 22 deletions(-) create mode 100644 testdata/testing.go create mode 100644 testdata/testing.txt diff --git a/main.go b/main.go index c9fd0c042..612ad0747 100644 --- a/main.go +++ b/main.go @@ -158,7 +158,7 @@ func Build(pkgName, outpath string, options *compileopts.Options) error { // Test runs the tests in the given package. Returns whether the test passed and // possibly an error if the test failed to run. -func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, outpath string) (bool, error) { +func Test(pkgName string, options *compileopts.Options, testCompileOnly, testVerbose bool, outpath string) (bool, error) { options.TestConfig.CompileTestBinary = true config, err := builder.NewConfig(options) if err != nil { @@ -184,7 +184,7 @@ func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, ou // Run the test. start := time.Now() var err error - passed, err = runPackageTest(config, result) + passed, err = runPackageTest(config, result, testVerbose) if err != nil { return err } @@ -210,10 +210,14 @@ func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, ou // runPackageTest runs a test binary that was previously built. The return // values are whether the test passed and any errors encountered while trying to // run the binary. -func runPackageTest(config *compileopts.Config, result builder.BuildResult) (bool, error) { +func runPackageTest(config *compileopts.Config, result builder.BuildResult, testVerbose bool) (bool, error) { if len(config.Target.Emulator) == 0 { // Run directly. - cmd := executeCommand(config.Options, result.Binary) + var flags []string + if testVerbose { + flags = append(flags, "-test.v") + } + cmd := executeCommand(config.Options, result.Binary, flags...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Dir = result.MainDir @@ -229,6 +233,7 @@ func runPackageTest(config *compileopts.Config, result builder.BuildResult) (boo return true, nil } else { // Run in an emulator. + // TODO: pass the -test.v flag if needed. args := append(config.Target.Emulator[1:], result.Binary) cmd := executeCommand(config.Options, config.Target.Emulator[0], args...) buf := &bytes.Buffer{} @@ -1038,9 +1043,10 @@ func main() { if command == "help" || command == "build" || command == "build-library" || command == "test" { flag.StringVar(&outpath, "o", "", "output filename") } - var testCompileOnlyFlag *bool + var testCompileOnlyFlag, testVerboseFlag *bool if command == "help" || command == "test" { testCompileOnlyFlag = flag.Bool("c", false, "compile the test binary but do not run it") + testVerboseFlag = flag.Bool("v", false, "verbose: print additional output") } // Early command processing, before commands are interpreted by the Go flag @@ -1201,7 +1207,7 @@ func main() { allTestsPassed := true for _, pkgName := range pkgNames { // TODO: parallelize building the test binaries - passed, err := Test(pkgName, options, *testCompileOnlyFlag, outpath) + passed, err := Test(pkgName, options, *testCompileOnlyFlag, *testVerboseFlag, outpath) handleCompilerError(err) if !passed { allTestsPassed = false diff --git a/main_test.go b/main_test.go index a502115e4..f47b43398 100644 --- a/main_test.go +++ b/main_test.go @@ -51,6 +51,7 @@ func TestCompiler(t *testing.T) { "stdlib.go", "string.go", "structs.go", + "testing.go", "zeroalloc.go", } diff --git a/src/testing/testing.go b/src/testing/testing.go index 88e751ffb..c15ddab53 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -10,15 +10,34 @@ package testing import ( "bytes" + "flag" "fmt" - "io" "os" + "strings" ) +// Testing flags. +var ( + flagVerbose bool +) + +var initRan bool + +// Init registers testing flags. It has no effect if it has already run. +func Init() { + if initRan { + return + } + initRan = true + + flag.BoolVar(&flagVerbose, "test.v", false, "verbose: print additional output") +} + // common holds the elements common between T and B and // captures common methods such as Errorf. type common struct { - output io.Writer + output bytes.Buffer + indent string failed bool // Test or benchmark has failed. skipped bool // Test of benchmark has been skipped. @@ -53,7 +72,6 @@ var _ TB = (*B)(nil) // type T struct { common - indent string } // Name returns the name of the running test or benchmark. @@ -85,8 +103,22 @@ func (c *common) FailNow() { // log generates the output. func (c *common) log(s string) { // This doesn't print the same as in upstream go, but works for now. - fmt.Fprintf(c.output, "\t") - fmt.Fprintln(c.output, s) + if len(s) != 0 && s[len(s)-1] == '\n' { + s = s[:len(s)-1] + } + lines := strings.Split(s, "\n") + // First line. + c.output.WriteString(c.indent) + c.output.WriteString(" ") // 4 spaces + c.output.WriteString(lines[0]) + c.output.WriteByte('\n') + // More lines. + for _, line := range lines[1:] { + c.output.WriteString(c.indent) + c.output.WriteString(" ") // 8 spaces + c.output.WriteString(line) + c.output.WriteByte('\n') + } } // Log formats its arguments using default formatting, analogous to Println, @@ -165,25 +197,30 @@ func (c *common) Helper() { func (t *T) Run(name string, f func(t *T)) bool { // Create a subtest. sub := T{ - indent: t.indent + " ", common: common{ name: t.name + "/" + name, - output: &bytes.Buffer{}, + indent: t.indent + " ", }, } // Run the test. - fmt.Printf("=== RUN %s\n", sub.name) + if flagVerbose { + fmt.Fprintf(&t.output, "=== RUN %s\n", sub.name) + + } f(&sub) // Process the result (pass or fail). if sub.failed { t.failed = true - fmt.Printf(sub.indent+"--- FAIL: %s\n", sub.name) + fmt.Fprintf(&t.output, sub.indent+"--- FAIL: %s\n", sub.name) + t.output.Write(sub.output.Bytes()) } else { - fmt.Printf(sub.indent+"--- PASS: %s\n", sub.name) + if flagVerbose { + fmt.Fprintf(&t.output, sub.indent+"--- PASS: %s\n", sub.name) + t.output.Write(sub.output.Bytes()) + } } - fmt.Print(sub.output) return !sub.failed } @@ -205,24 +242,32 @@ func (m *M) Run() int { fmt.Fprintln(os.Stderr, "testing: warning: no tests to run") } + if !flag.Parsed() { + flag.Parse() + } + failures := 0 for _, test := range m.Tests { t := &T{ common: common{ - name: test.Name, - output: &bytes.Buffer{}, + name: test.Name, }, } - fmt.Printf("=== RUN %s\n", test.Name) + if flagVerbose { + fmt.Printf("=== RUN %s\n", test.Name) + } test.F(t) if t.failed { fmt.Printf("--- FAIL: %s\n", test.Name) + os.Stdout.Write(t.output.Bytes()) } else { - fmt.Printf("--- PASS: %s\n", test.Name) + if flagVerbose { + fmt.Printf("--- PASS: %s\n", test.Name) + os.Stdout.Write(t.output.Bytes()) + } } - fmt.Print(t.output) if t.failed { failures++ @@ -242,6 +287,7 @@ func TestMain(m *M) { } func MainStart(deps interface{}, tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) *M { + Init() return &M{ Tests: tests, } diff --git a/testdata/testing.go b/testdata/testing.go new file mode 100644 index 000000000..ba952f34d --- /dev/null +++ b/testdata/testing.go @@ -0,0 +1,42 @@ +package main + +// TODO: also test the verbose version. + +import ( + "testing" +) + +func TestFoo(t *testing.T) { + t.Log("log Foo.a") + t.Log("log Foo.b") +} + +func TestBar(t *testing.T) { + t.Log("log Bar") + t.Log("log g\nh\ni\n") + t.Run("Bar1", func(t *testing.T) {}) + t.Run("Bar2", func(t *testing.T) { + t.Log("log Bar2\na\nb\nc") + t.Error("failed") + t.Log("after failed") + }) + t.Run("Bar3", func(t *testing.T) {}) + t.Log("log Bar end") +} + +var tests = []testing.InternalTest{ + {"TestFoo", TestFoo}, + {"TestBar", TestBar}, +} + +var benchmarks = []testing.InternalBenchmark{} + +var examples = []testing.InternalExample{} + +func main() { + m := testing.MainStart(nil, tests, benchmarks, examples) + exitcode := m.Run() + if exitcode != 0 { + println("exitcode:", exitcode) + } +} diff --git a/testdata/testing.txt b/testdata/testing.txt new file mode 100644 index 000000000..816246c2e --- /dev/null +++ b/testdata/testing.txt @@ -0,0 +1,16 @@ +--- FAIL: TestBar + log Bar + log g + h + i + + --- FAIL: TestBar/Bar2 + log Bar2 + a + b + c + failed + after failed + log Bar end +FAIL +exitcode: 1 From 25c7bfd404448a159a52f41930ac210bca6f1b53 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 15 Aug 2021 15:22:18 +0200 Subject: [PATCH 15/47] ci: drop support for Go 1.13 and 1.14 They aren't supported anymore in CI, and because untested code is broken code, let's remove support for these Go versions altogether. --- .circleci/config.yml | 24 +++++------------------- BUILDING.md | 2 +- builder/config.go | 4 ++-- go.mod | 2 +- main_test.go | 19 +++---------------- tests/wasm/event_test.go | 2 -- tests/wasm/fmt_test.go | 9 --------- tests/wasm/fmtprint_test.go | 2 -- tests/wasm/log_test.go | 2 -- 9 files changed, 12 insertions(+), 54 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 01f2bec82..bcc9b80d1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -365,24 +365,12 @@ commands: - /go/pkg/mod jobs: - test-llvm10-go113: - docker: - - image: circleci/golang:1.13-buster - steps: - - test-linux: - llvm: "10" - test-llvm10-go114: - docker: - - image: circleci/golang:1.14-buster - steps: - - test-linux: - llvm: "10" - test-llvm11-go115: + test-llvm10-go115: docker: - image: circleci/golang:1.15-buster steps: - test-linux: - llvm: "11" + llvm: "10" test-llvm11-go116: docker: - image: circleci/golang:1.16-buster @@ -391,12 +379,12 @@ jobs: llvm: "11" assert-test-linux: docker: - - image: circleci/golang:1.14-stretch + - image: circleci/golang:1.16-stretch steps: - assert-test-linux build-linux: docker: - - image: circleci/golang:1.14-stretch + - image: circleci/golang:1.16-stretch steps: - build-linux build-macos: @@ -410,9 +398,7 @@ jobs: workflows: test-all: jobs: - - test-llvm10-go113 - - test-llvm10-go114 - - test-llvm11-go115 + - test-llvm10-go115 - test-llvm11-go116 - build-linux - build-macos diff --git a/BUILDING.md b/BUILDING.md index 8439c32c8..22dd87ec1 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -23,7 +23,7 @@ different guide: LLVM, Clang and LLD are quite light on dependencies, requiring only standard build tools to be built. Go is of course necessary to build TinyGo itself. - * Go (1.13+) + * Go (1.15+) * Standard build tools (gcc/clang) * git * CMake diff --git a/builder/config.go b/builder/config.go index c94571fe4..cdd9c0dad 100644 --- a/builder/config.go +++ b/builder/config.go @@ -33,8 +33,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 < 13 || minor > 16 { - return nil, fmt.Errorf("requires go version 1.13 through 1.16, got go%d.%d", major, minor) + if major != 1 || minor < 15 || minor > 16 { + return nil, fmt.Errorf("requires go version 1.15 through 1.16, got go%d.%d", major, minor) } clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT")) diff --git a/go.mod b/go.mod index 4820d7747..285c02a4a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/tinygo-org/tinygo -go 1.13 +go 1.15 require ( github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 diff --git a/main_test.go b/main_test.go index f47b43398..8159951cf 100644 --- a/main_test.go +++ b/main_test.go @@ -20,7 +20,6 @@ import ( "github.com/tinygo-org/tinygo/builder" "github.com/tinygo-org/tinygo/compileopts" - "github.com/tinygo-org/tinygo/goenv" ) const TESTDATA = "testdata" @@ -95,21 +94,9 @@ func TestCompiler(t *testing.T) { t.Run("ARM64Linux", func(t *testing.T) { runPlatTests("aarch64--linux-gnu", tests, t) }) - goVersion, err := goenv.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", tests, t) - }) - } - + t.Run("WebAssembly", func(t *testing.T) { + runPlatTests("wasm", tests, t) + }) t.Run("WASI", func(t *testing.T) { runPlatTests("wasi", tests, t) }) diff --git a/tests/wasm/event_test.go b/tests/wasm/event_test.go index f4ede023a..bbece4b4a 100644 --- a/tests/wasm/event_test.go +++ b/tests/wasm/event_test.go @@ -1,5 +1,3 @@ -// +build go1.14 - package wasm import ( diff --git a/tests/wasm/fmt_test.go b/tests/wasm/fmt_test.go index cac38e721..0a96c45dd 100644 --- a/tests/wasm/fmt_test.go +++ b/tests/wasm/fmt_test.go @@ -1,14 +1,5 @@ -// +build go1.14 - package wasm -// NOTE: this should work in go1.13 but panics with: -// panic: syscall/js: call of Value.Get on string -// which is coming from here: https://github.com/golang/go/blob/release-branch.go1.13/src/syscall/js/js.go#L252 -// But I'm not sure how import "fmt" results in this. -// To reproduce, install Go 1.13.x and change the build tag above -// to go1.13 and run this test. - import ( "testing" "time" diff --git a/tests/wasm/fmtprint_test.go b/tests/wasm/fmtprint_test.go index 7b4a4701a..16a2173b1 100644 --- a/tests/wasm/fmtprint_test.go +++ b/tests/wasm/fmtprint_test.go @@ -1,5 +1,3 @@ -// +build go1.14 - package wasm import ( diff --git a/tests/wasm/log_test.go b/tests/wasm/log_test.go index ea314e478..3da94ed39 100644 --- a/tests/wasm/log_test.go +++ b/tests/wasm/log_test.go @@ -1,5 +1,3 @@ -// +build go1.14 - package wasm import ( From 59d53182bb30e7b1de8076550057b465b28a4ccb Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 15 Aug 2021 16:03:17 +0200 Subject: [PATCH 16/47] all: use new testing features of Go 1.14 and 1.15 This simplifies the tests a bit. --- main_test.go | 11 +---------- tests/wasm/chan_test.go | 3 +-- tests/wasm/event_test.go | 3 +-- tests/wasm/fmt_test.go | 3 +-- tests/wasm/fmtprint_test.go | 3 +-- tests/wasm/log_test.go | 3 +-- tests/wasm/setup_test.go | 23 ++++------------------- 7 files changed, 10 insertions(+), 39 deletions(-) diff --git a/main_test.go b/main_test.go index 8159951cf..bbc1a062d 100644 --- a/main_test.go +++ b/main_test.go @@ -210,16 +210,7 @@ func runTestWithConfig(name, target string, t *testing.T, options compileopts.Op } // Create a temporary directory for test output files. - tmpdir, err := ioutil.TempDir("", "tinygo-test") - if err != nil { - t.Fatal("could not create temporary directory:", err) - } - defer func() { - rerr := os.RemoveAll(tmpdir) - if rerr != nil { - t.Errorf("failed to remove temporary directory %q: %s", tmpdir, rerr.Error()) - } - }() + tmpdir := t.TempDir() // Determine whether we're on a system that supports environment variables // and command line parameters (operating systems, WASI) or not (baremetal, diff --git a/tests/wasm/chan_test.go b/tests/wasm/chan_test.go index 2faf95bac..793bf1de3 100644 --- a/tests/wasm/chan_test.go +++ b/tests/wasm/chan_test.go @@ -11,8 +11,7 @@ func TestChan(t *testing.T) { t.Parallel() - wasmTmpDir, server, cleanup := startServer(t) - defer cleanup() + wasmTmpDir, server := startServer(t) err := run("tinygo build -o " + wasmTmpDir + "/chan.wasm -target wasm testdata/chan.go") if err != nil { diff --git a/tests/wasm/event_test.go b/tests/wasm/event_test.go index bbece4b4a..038a500a3 100644 --- a/tests/wasm/event_test.go +++ b/tests/wasm/event_test.go @@ -11,8 +11,7 @@ func TestEvent(t *testing.T) { t.Parallel() - wasmTmpDir, server, cleanup := startServer(t) - defer cleanup() + wasmTmpDir, server := startServer(t) err := run("tinygo build -o " + wasmTmpDir + "/event.wasm -target wasm testdata/event.go") if err != nil { diff --git a/tests/wasm/fmt_test.go b/tests/wasm/fmt_test.go index 0a96c45dd..f9f2f77b1 100644 --- a/tests/wasm/fmt_test.go +++ b/tests/wasm/fmt_test.go @@ -11,8 +11,7 @@ func TestFmt(t *testing.T) { t.Parallel() - wasmTmpDir, server, cleanup := startServer(t) - defer cleanup() + wasmTmpDir, server := startServer(t) err := run("tinygo build -o " + wasmTmpDir + "/fmt.wasm -target wasm testdata/fmt.go") if err != nil { diff --git a/tests/wasm/fmtprint_test.go b/tests/wasm/fmtprint_test.go index 16a2173b1..90825ba08 100644 --- a/tests/wasm/fmtprint_test.go +++ b/tests/wasm/fmtprint_test.go @@ -11,8 +11,7 @@ func TestFmtprint(t *testing.T) { t.Parallel() - wasmTmpDir, server, cleanup := startServer(t) - defer cleanup() + wasmTmpDir, server := startServer(t) err := run("tinygo build -o " + wasmTmpDir + "/fmtprint.wasm -target wasm testdata/fmtprint.go") if err != nil { diff --git a/tests/wasm/log_test.go b/tests/wasm/log_test.go index 3da94ed39..fae4c670b 100644 --- a/tests/wasm/log_test.go +++ b/tests/wasm/log_test.go @@ -11,8 +11,7 @@ func TestLog(t *testing.T) { t.Parallel() - wasmTmpDir, server, cleanup := startServer(t) - defer cleanup() + wasmTmpDir, server := startServer(t) err := run("tinygo build -o " + wasmTmpDir + "/log.wasm -target wasm testdata/log.go") if err != nil { diff --git a/tests/wasm/setup_test.go b/tests/wasm/setup_test.go index 77f5063f8..0071076c2 100644 --- a/tests/wasm/setup_test.go +++ b/tests/wasm/setup_test.go @@ -4,11 +4,9 @@ import ( "context" "errors" "fmt" - "io/ioutil" "log" "net/http" "net/http/httptest" - "os" "os/exec" "regexp" "strings" @@ -47,12 +45,8 @@ func chromectx(timeout time.Duration) (context.Context, context.CancelFunc) { return ctx, cancel } -func startServer(t *testing.T) (string, *httptest.Server, func()) { - // In Go 1.15, all this can be replaced by t.TempDir() - tmpDir, err := ioutil.TempDir("", "wasm_test") - if err != nil { - t.Fatalf("unable to create temp dir: %v", err) - } +func startServer(t *testing.T) (string, *httptest.Server) { + tmpDir := t.TempDir() fsh := http.FileServer(http.Dir(tmpDir)) h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -124,18 +118,9 @@ if (wasmSupported) { server := httptest.NewServer(h) t.Logf("Started server at %q for dir: %s", server.URL, tmpDir) + t.Cleanup(server.Close) - // In Go 1.14+, this can be replaced by t.Cleanup() - cleanup := func() { - err := os.RemoveAll(tmpDir) - if err != nil { - t.Error(err) - } - - server.Close() - } - - return tmpDir, server, cleanup + return tmpDir, server } // waitLog blocks until the log output equals the text provided (ignoring whitespace before and after) From a2cc5715ba4b387f24d6f197a1215c5b01a054e3 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 15 Aug 2021 02:03:45 +0200 Subject: [PATCH 17/47] compiler: add *ssa.MakeSlice bounds tests There are some bugs in it. This commit adds the tests, so that the next commit can show what changed. --- compiler/testdata/slice.go | 18 +++++++++ compiler/testdata/slice.ll | 76 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/compiler/testdata/slice.go b/compiler/testdata/slice.go index f26d53d88..2e6dffb5d 100644 --- a/compiler/testdata/slice.go +++ b/compiler/testdata/slice.go @@ -23,3 +23,21 @@ func sliceAppendSlice(ints, added []int) []int { func sliceCopy(dst, src []int) int { return copy(dst, src) } + +// Test bounds checking in *ssa.MakeSlice instruction. + +func makeByteSlice(len int) []byte { + return make([]byte, len) +} + +func makeInt16Slice(len int) []int16 { + return make([]int16, len) +} + +func makeArraySlice(len int) [][3]byte { + return make([][3]byte, len) // slice with element size of 3 +} + +func makeInt32Slice(len int) []int32 { + return make([]int32, len) +} diff --git a/compiler/testdata/slice.ll b/compiler/testdata/slice.ll index 6a62c8bc0..f3e2aac63 100644 --- a/compiler/testdata/slice.ll +++ b/compiler/testdata/slice.ll @@ -86,3 +86,79 @@ entry: } declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*) + +define hidden { i8*, i32, i32 } @main.makeByteSlice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %slice.maxcap = icmp slt i32 %len, 0 + br i1 %slice.maxcap, label %slice.throw, label %slice.next + +slice.throw: ; preds = %entry + call void @runtime.slicePanic(i8* undef, i8* null) + unreachable + +slice.next: ; preds = %entry + %makeslice.buf = call i8* @runtime.alloc(i32 %len, i8* undef, i8* null) + %0 = insertvalue { i8*, i32, i32 } undef, i8* %makeslice.buf, 0 + %1 = insertvalue { i8*, i32, i32 } %0, i32 %len, 1 + %2 = insertvalue { i8*, i32, i32 } %1, i32 %len, 2 + ret { i8*, i32, i32 } %2 +} + +declare void @runtime.slicePanic(i8*, i8*) + +define hidden { i16*, i32, i32 } @main.makeInt16Slice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %slice.maxcap = icmp slt i32 %len, 0 + br i1 %slice.maxcap, label %slice.throw, label %slice.next + +slice.throw: ; preds = %entry + call void @runtime.slicePanic(i8* undef, i8* null) + unreachable + +slice.next: ; preds = %entry + %makeslice.cap = shl i32 %len, 1 + %makeslice.buf = call i8* @runtime.alloc(i32 %makeslice.cap, i8* undef, i8* null) + %makeslice.array = bitcast i8* %makeslice.buf to i16* + %0 = insertvalue { i16*, i32, i32 } undef, i16* %makeslice.array, 0 + %1 = insertvalue { i16*, i32, i32 } %0, i32 %len, 1 + %2 = insertvalue { i16*, i32, i32 } %1, i32 %len, 2 + ret { i16*, i32, i32 } %2 +} + +define hidden { [3 x i8]*, i32, i32 } @main.makeArraySlice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %slice.maxcap = icmp slt i32 %len, 0 + br i1 %slice.maxcap, label %slice.throw, label %slice.next + +slice.throw: ; preds = %entry + call void @runtime.slicePanic(i8* undef, i8* null) + unreachable + +slice.next: ; preds = %entry + %makeslice.cap = mul i32 %len, 3 + %makeslice.buf = call i8* @runtime.alloc(i32 %makeslice.cap, i8* undef, i8* null) + %makeslice.array = bitcast i8* %makeslice.buf to [3 x i8]* + %0 = insertvalue { [3 x i8]*, i32, i32 } undef, [3 x i8]* %makeslice.array, 0 + %1 = insertvalue { [3 x i8]*, i32, i32 } %0, i32 %len, 1 + %2 = insertvalue { [3 x i8]*, i32, i32 } %1, i32 %len, 2 + ret { [3 x i8]*, i32, i32 } %2 +} + +define hidden { i32*, i32, i32 } @main.makeInt32Slice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %slice.maxcap = icmp slt i32 %len, 0 + br i1 %slice.maxcap, label %slice.throw, label %slice.next + +slice.throw: ; preds = %entry + call void @runtime.slicePanic(i8* undef, i8* null) + unreachable + +slice.next: ; preds = %entry + %makeslice.cap = shl i32 %len, 2 + %makeslice.buf = call i8* @runtime.alloc(i32 %makeslice.cap, i8* undef, i8* null) + %makeslice.array = bitcast i8* %makeslice.buf to i32* + %0 = insertvalue { i32*, i32, i32 } undef, i32* %makeslice.array, 0 + %1 = insertvalue { i32*, i32, i32 } %0, i32 %len, 1 + %2 = insertvalue { i32*, i32, i32 } %1, i32 %len, 2 + ret { i32*, i32, i32 } %2 +} From 0f2f73be53b97edede0c3459454e6a59fda825f9 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 15 Aug 2021 02:08:15 +0200 Subject: [PATCH 18/47] compiler: fix max possible slice This commit improves make([]T, len) to be closer to upstream Go. The difference is unlikely to have much real-world effect, but previously certain make([]T, len) expressions would not result in a slice out of bounds error in TinyGo while they would have done such a thing in Go proper. In practice, available RAM is likely to be a bigger limiting factor. --- compiler/compiler.go | 33 +++++++++++++++++++++++++++------ compiler/testdata/slice.ll | 4 ++-- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index ea0d51a67..dda5f51eb 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -23,7 +23,7 @@ import ( // Version of the compiler pacakge. Must be incremented each time the compiler // package changes in a way that affects the generated LLVM module. // This version is independent of the TinyGo version number. -const Version = 15 // last change: add crypto assembly aliases +const Version = 16 // last change: fix max slice size func init() { llvm.InitializeAllTargets() @@ -1439,6 +1439,28 @@ func (b *builder) getValue(expr ssa.Value) llvm.Value { } } +// maxSliceSize determines the maximum size a slice of the given element type +// can be. +func (c *compilerContext) maxSliceSize(elementType llvm.Type) uint64 { + // Calculate ^uintptr(0), which is the max value that fits in uintptr. + maxPointerValue := llvm.ConstNot(llvm.ConstInt(c.uintptrType, 0, false)).ZExtValue() + // Calculate (^uint(0))/2, which is the max value that fits in an int. + maxIntegerValue := llvm.ConstNot(llvm.ConstInt(c.intType, 0, false)).ZExtValue() / 2 + + // Determine the maximum allowed size for a slice. The biggest possible + // pointer (starting from 0) would be maxPointerValue*sizeof(elementType) so + // divide by the element type to get the real maximum size. + maxSize := maxPointerValue / c.targetData.TypeAllocSize(elementType) + + // len(slice) is an int. Make sure the length remains small enough to fit in + // an int. + if maxSize > maxIntegerValue { + maxSize = maxIntegerValue + } + + return maxSize +} + // createExpr translates a Go SSA expression to LLVM IR. This can be zero, one, // or multiple LLVM IR instructions and/or runtime calls. func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { @@ -1652,10 +1674,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { elemSize := b.targetData.TypeAllocSize(llvmElemType) elemSizeValue := llvm.ConstInt(b.uintptrType, elemSize, false) - // Calculate (^uintptr(0)) >> 1, which is the max value that fits in - // uintptr if uintptr were signed. - maxSize := llvm.ConstLShr(llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)), llvm.ConstInt(b.uintptrType, 1, false)) - if elemSize > maxSize.ZExtValue() { + maxSize := b.maxSliceSize(llvmElemType) + if elemSize > maxSize { // This seems to be checked by the typechecker already, but let's // check it again just to be sure. return llvm.Value{}, b.makeError(expr.Pos(), fmt.Sprintf("slice element type is too big (%v bytes)", elemSize)) @@ -1664,7 +1684,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { // Bounds checking. lenType := expr.Len.Type().Underlying().(*types.Basic) capType := expr.Cap.Type().Underlying().(*types.Basic) - b.createSliceBoundsCheck(maxSize, sliceLen, sliceCap, sliceCap, lenType, capType, capType) + maxSizeValue := llvm.ConstInt(b.uintptrType, maxSize, false) + b.createSliceBoundsCheck(maxSizeValue, sliceLen, sliceCap, sliceCap, lenType, capType, capType) // Allocate the backing array. sliceCapCast, err := b.createConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos()) diff --git a/compiler/testdata/slice.ll b/compiler/testdata/slice.ll index f3e2aac63..7d027fb8b 100644 --- a/compiler/testdata/slice.ll +++ b/compiler/testdata/slice.ll @@ -127,7 +127,7 @@ slice.next: ; preds = %entry define hidden { [3 x i8]*, i32, i32 } @main.makeArraySlice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr { entry: - %slice.maxcap = icmp slt i32 %len, 0 + %slice.maxcap = icmp ugt i32 %len, 1431655765 br i1 %slice.maxcap, label %slice.throw, label %slice.next slice.throw: ; preds = %entry @@ -146,7 +146,7 @@ slice.next: ; preds = %entry define hidden { i32*, i32, i32 } @main.makeInt32Slice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr { entry: - %slice.maxcap = icmp slt i32 %len, 0 + %slice.maxcap = icmp ugt i32 %len, 1073741823 br i1 %slice.maxcap, label %slice.throw, label %slice.next slice.throw: ; preds = %entry From 7d83e2ee5ceb1cb95ac6a1b0f834d5b7e0b92aec Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 18 Aug 2021 17:16:19 +0200 Subject: [PATCH 19/47] docker: apt clean before apt get of llvm to avoid broken packages Signed-off-by: deadprogram --- .dockerignore | 3 +++ Dockerfile | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..e3760b68c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +build/ +llvm-*/ + diff --git a/Dockerfile b/Dockerfile index b8f5a3b55..b07119576 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM golang:1.16 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-11 main" >> /etc/apt/sources.list && \ - apt-get update && \ + apt-get update && apt-get clean && \ apt-get install -y llvm-11-dev libclang-11-dev lld-11 git COPY . /tinygo From 972f4254eb54697285e3b201c03a615d09d4a93f Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 18 Aug 2021 20:18:16 +0200 Subject: [PATCH 20/47] docker: add GH actions build on fix-docker-llvm-build branch to sort out build issues Signed-off-by: deadprogram --- .github/workflows/build-tinygo-dev-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-tinygo-dev-docker.yml b/.github/workflows/build-tinygo-dev-docker.yml index ba8ba220e..9e83910fd 100644 --- a/.github/workflows/build-tinygo-dev-docker.yml +++ b/.github/workflows/build-tinygo-dev-docker.yml @@ -1,7 +1,7 @@ name: CI for tinygo-dev docker container on: push: - branches: [ dev ] + branches: [ dev, fix-docker-llvm-build ] jobs: push_to_registry: From 192a32f8d99d60a8c2ba53fc893c45a0b6c45e58 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 18 Aug 2021 20:19:33 +0200 Subject: [PATCH 21/47] docker: use autoremove to tr to cleanup broken packages Signed-off-by: deadprogram --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index b07119576..bab40c1cd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM golang:1.16 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-11 main" >> /etc/apt/sources.list && \ - apt-get update && apt-get clean && \ + apt-get update && apt-get clean && apt-get autoremove && \ apt-get install -y llvm-11-dev libclang-11-dev lld-11 git COPY . /tinygo From 931f87f96a36565cf01291e9a306a808884e5b97 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 18 Aug 2021 20:28:36 +0200 Subject: [PATCH 22/47] docker: golang default images now based on bullseye Signed-off-by: deadprogram --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index bab40c1cd..7e65e6583 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,8 +2,8 @@ FROM golang:1.16 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-11 main" >> /etc/apt/sources.list && \ - apt-get update && apt-get clean && apt-get autoremove && \ + echo "deb http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-11 main" >> /etc/apt/sources.list && \ + apt-get update && \ apt-get install -y llvm-11-dev libclang-11-dev lld-11 git COPY . /tinygo From ad73986070f4dfc30887cbf9b01f86d7eb914597 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 14 Aug 2021 15:32:28 +0200 Subject: [PATCH 23/47] goenv: improve Go version detection First look at the VERSION file, only then look at src/runtime/internal/sys/zversion.go. This makes it possible to correctly detect the Go version for release candidates. --- goenv/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/goenv/version.go b/goenv/version.go index e0fe0949c..87752a015 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -48,7 +48,10 @@ func GetGorootVersion(goroot string) (major, minor int, err error) { // toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but // can have some variations (for beta releases, for example). func GorootVersionString(goroot string) (string, error) { - if data, err := ioutil.ReadFile(filepath.Join( + if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil { + return string(data), nil + + } else if data, err := ioutil.ReadFile(filepath.Join( goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil { r := regexp.MustCompile("const TheVersion = `(.*)`") @@ -59,9 +62,6 @@ func GorootVersionString(goroot string) (string, error) { return string(matches[1]), nil - } else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil { - return string(data), nil - } else { return "", err } From d45497691f15fa11b592bfa6c02ed39eb4479fbd Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 14 Aug 2021 15:52:45 +0200 Subject: [PATCH 24/47] reflect: add StructField.IsExported method This field was introduced in Go 1.17 and is used by the encoding/json package (starting with Go 1.17). --- src/reflect/type.go | 5 +++++ testdata/reflect.go | 1 + testdata/reflect.txt | 13 +++++++++++++ 3 files changed, 19 insertions(+) diff --git a/src/reflect/type.go b/src/reflect/type.go index 2e7096759..5424336e1 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -706,6 +706,11 @@ type StructField struct { Offset uintptr } +// IsExported reports whether the field is exported. +func (f StructField) IsExported() bool { + return f.PkgPath == "" +} + // rawStructField is the same as StructField but with the Type member replaced // with rawType. For internal use only. Avoiding this conversion to the Type // interface improves code size in many cases. diff --git a/testdata/reflect.go b/testdata/reflect.go index 63cd16757..5a107e2d1 100644 --- a/testdata/reflect.go +++ b/testdata/reflect.go @@ -398,6 +398,7 @@ func showValue(rv reflect.Value, indent string) { println(indent+" field:", i, field.Name) println(indent+" tag:", field.Tag) println(indent+" embedded:", field.Anonymous) + println(indent+" exported:", field.IsExported()) showValue(rv.Field(i), indent+" ") } default: diff --git a/testdata/reflect.txt b/testdata/reflect.txt index 4f04afe98..4bd55fd83 100644 --- a/testdata/reflect.txt +++ b/testdata/reflect.txt @@ -235,6 +235,7 @@ reflect type: struct field: 0 error tag: embedded: true + exported: false reflect type: interface interface nil: true @@ -243,16 +244,19 @@ reflect type: struct field: 0 a tag: embedded: false + exported: false reflect type: uint8 uint: 42 field: 1 b tag: embedded: false + exported: false reflect type: int16 int: 321 field: 2 c tag: embedded: false + exported: false reflect type: int8 int: 123 reflect type: struct comparable=false @@ -260,31 +264,37 @@ reflect type: struct comparable=false field: 0 n tag: foo:"bar" embedded: false + exported: false reflect type: int int: 5 field: 1 some tag: embedded: false + exported: false reflect type: struct struct: 2 field: 0 X tag: embedded: false + exported: true reflect type: int16 int: -5 field: 1 Y tag: embedded: false + exported: true reflect type: int16 int: 3 field: 2 zero tag: embedded: false + exported: false reflect type: struct struct: 0 field: 3 buf tag: embedded: false + exported: false reflect type: slice comparable=false slice: uint8 2 2 pointer: true @@ -298,6 +308,7 @@ reflect type: struct comparable=false field: 4 Buf tag: embedded: false + exported: true reflect type: slice comparable=false slice: uint8 1 1 pointer: true @@ -313,12 +324,14 @@ reflect type: ptr field: 0 next tag: description:"chain" embedded: false + exported: false reflect type: ptr addrable=true pointer: false struct nil: true field: 1 foo tag: embedded: false + exported: false reflect type: int addrable=true int: 42 reflect type: ptr From 8e88e560a1eb76558364ee3400b3bfe0e4c054c6 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 14 Aug 2021 15:35:19 +0200 Subject: [PATCH 25/47] all: add support for Go 1.17 --- .circleci/config.yml | 8 ++++---- azure-pipelines.yml | 2 +- builder/config.go | 4 ++-- compiler/alias.go | 11 +++++++++++ compiler/compiler.go | 2 +- src/os/file.go | 5 +++++ 6 files changed, 24 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bcc9b80d1..5225152f3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -286,8 +286,8 @@ commands: - run: name: "Install dependencies" command: | - curl https://dl.google.com/go/go1.16.darwin-amd64.tar.gz -o go1.16.darwin-amd64.tar.gz - sudo tar -C /usr/local -xzf go1.16.darwin-amd64.tar.gz + curl https://dl.google.com/go/go1.17.darwin-amd64.tar.gz -o go1.17.darwin-amd64.tar.gz + sudo tar -C /usr/local -xzf go1.17.darwin-amd64.tar.gz ln -s /usr/local/go/bin/go /usr/local/bin/go HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu - install-xtensa-toolchain: @@ -379,12 +379,12 @@ jobs: llvm: "11" assert-test-linux: docker: - - image: circleci/golang:1.16-stretch + - image: circleci/golang:1.17-stretch steps: - assert-test-linux build-linux: docker: - - image: circleci/golang:1.16-stretch + - image: circleci/golang:1.17-stretch steps: - build-linux build-macos: diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 96d918848..6a983aaec 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -12,7 +12,7 @@ jobs: steps: - task: GoTool@0 inputs: - version: '1.16' + version: '1.17' - checkout: self fetchDepth: 1 - task: Cache@2 diff --git a/builder/config.go b/builder/config.go index cdd9c0dad..82693f7fe 100644 --- a/builder/config.go +++ b/builder/config.go @@ -33,8 +33,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 < 15 || minor > 16 { - return nil, fmt.Errorf("requires go version 1.15 through 1.16, got go%d.%d", major, minor) + if major != 1 || minor < 15 || minor > 17 { + return nil, fmt.Errorf("requires go version 1.15 through 1.17, got go%d.%d", major, minor) } clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT")) diff --git a/compiler/alias.go b/compiler/alias.go index cc08c8320..bdc6cc4d1 100644 --- a/compiler/alias.go +++ b/compiler/alias.go @@ -32,33 +32,44 @@ var stdlibAliases = map[string]string{ "math.Atan2": "math.atan2", "math.Cbrt": "math.cbrt", "math.Ceil": "math.ceil", + "math.archCeil": "math.ceil", "math.Cos": "math.cos", "math.Cosh": "math.cosh", "math.Erf": "math.erf", "math.Erfc": "math.erfc", "math.Exp": "math.exp", + "math.archExp": "math.exp", "math.Expm1": "math.expm1", "math.Exp2": "math.exp2", + "math.archExp2": "math.exp2", "math.Floor": "math.floor", + "math.archFloor": "math.floor", "math.Frexp": "math.frexp", "math.Hypot": "math.hypot", + "math.archHypot": "math.hypot", "math.Ldexp": "math.ldexp", "math.Log": "math.log", + "math.archLog": "math.log", "math.Log1p": "math.log1p", "math.Log10": "math.log10", "math.Log2": "math.log2", "math.Max": "math.max", + "math.archMax": "math.max", "math.Min": "math.min", + "math.archMin": "math.min", "math.Mod": "math.mod", "math.Modf": "math.modf", + "math.archModf": "math.modf", "math.Pow": "math.pow", "math.Remainder": "math.remainder", "math.Sin": "math.sin", "math.Sinh": "math.sinh", "math.Sqrt": "math.sqrt", + "math.archSqrt": "math.sqrt", "math.Tan": "math.tan", "math.Tanh": "math.tanh", "math.Trunc": "math.trunc", + "math.archTrunc": "math.trunc", } // createAlias implements the function (in the builder) as a call to the alias diff --git a/compiler/compiler.go b/compiler/compiler.go index dda5f51eb..d78115351 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -23,7 +23,7 @@ import ( // Version of the compiler pacakge. Must be incremented each time the compiler // package changes in a way that affects the generated LLVM module. // This version is independent of the TinyGo version number. -const Version = 16 // last change: fix max slice size +const Version = 17 // last change: add math.arch* aliases func init() { llvm.InitializeAllTargets() diff --git a/src/os/file.go b/src/os/file.go index 4d90bdde5..4d2458a5a 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -24,6 +24,11 @@ func Mkdir(path string, perm FileMode) error { return nil } +// MkdirTemp is a stub, it will always return an error. +func MkdirTemp(dir, pattern string) (string, error) { + return "", &PathError{"mkdirtemp", dir, ErrNotImplemented} +} + // Remove removes a file or (empty) directory. If the operation fails, it will // return an error of type *PathError. func Remove(path string) error { From 255f35671d3eef11d6cfe0020aeda5c17985657d Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 15 Aug 2021 02:29:27 +0200 Subject: [PATCH 26/47] compiler: add support for new language features of Go 1.17 --- compiler/asserts.go | 49 +++++++++++++ compiler/compiler.go | 43 ++++++++++++ compiler/compiler_test.go | 19 +++-- compiler/testdata/go1.17.go | 41 +++++++++++ compiler/testdata/go1.17.ll | 136 ++++++++++++++++++++++++++++++++++++ go.mod | 4 +- go.sum | 23 ++++-- main_test.go | 9 +++ src/runtime/panic.go | 12 ++++ testdata/go1.17.go | 34 +++++++++ testdata/go1.17.txt | 3 + 11 files changed, 361 insertions(+), 12 deletions(-) create mode 100644 compiler/testdata/go1.17.go create mode 100644 compiler/testdata/go1.17.ll create mode 100644 testdata/go1.17.go create mode 100644 testdata/go1.17.txt diff --git a/compiler/asserts.go b/compiler/asserts.go index 01e7a4021..381900bb6 100644 --- a/compiler/asserts.go +++ b/compiler/asserts.go @@ -101,6 +101,55 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo b.createRuntimeAssert(outOfBounds, "slice", "slicePanic") } +// createSliceToArrayPointerCheck adds a check for slice-to-array pointer +// conversions. This conversion was added in Go 1.17. For details, see: +// https://tip.golang.org/ref/spec#Conversions_from_slice_to_array_pointer +func (b *builder) createSliceToArrayPointerCheck(sliceLen llvm.Value, arrayLen int64) { + // From the spec: + // > If the length of the slice is less than the length of the array, a + // > run-time panic occurs. + arrayLenValue := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false) + isLess := b.CreateICmp(llvm.IntULT, sliceLen, arrayLenValue, "") + b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic") +} + +// createUnsafeSliceCheck inserts a runtime check used for unsafe.Slice. This +// function must panic if the ptr/len parameters are invalid. +func (b *builder) createUnsafeSliceCheck(ptr, len llvm.Value, lenType *types.Basic) { + // From the documentation of unsafe.Slice: + // > At run time, if len is negative, or if ptr is nil and len is not + // > zero, a run-time panic occurs. + // However, in practice, it is also necessary to check that the length is + // not too big that a GEP wouldn't be possible without wrapping the pointer. + // These two checks (non-negative and not too big) can be merged into one + // using an unsiged greater than. + + // Make sure the len value is at least as big as a uintptr. + if len.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() { + if lenType.Info()&types.IsUnsigned != 0 { + len = b.CreateZExt(len, b.uintptrType, "") + } else { + len = b.CreateSExt(len, b.uintptrType, "") + } + } + + // Determine the maximum slice size, and therefore the maximum value of the + // len parameter. + maxSize := b.maxSliceSize(ptr.Type().ElementType()) + maxSizeValue := llvm.ConstInt(len.Type(), maxSize, false) + + // Do the check. By using unsigned greater than for the length check, signed + // negative values are also checked (which are very large numbers when + // interpreted as signed values). + zero := llvm.ConstInt(len.Type(), 0, false) + lenOutOfBounds := b.CreateICmp(llvm.IntUGT, len, maxSizeValue, "") + ptrIsNil := b.CreateICmp(llvm.IntEQ, ptr, llvm.ConstNull(ptr.Type()), "") + lenIsNotZero := b.CreateICmp(llvm.IntNE, len, zero, "") + assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "") + assert = b.CreateOr(assert, lenOutOfBounds, "") + b.createRuntimeAssert(assert, "unsafe.Slice", "unsafeSlicePanic") +} + // createChanBoundsCheck creates a bounds check before creating a new channel to // check that the value is not too big for runtime.chanMake. func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) { diff --git a/compiler/compiler.go b/compiler/compiler.go index d78115351..185859dec 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1293,6 +1293,38 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c case "ssa:wrapnilchk": // TODO: do an actual nil check? return argValues[0], nil + + // Builtins from the unsafe package. + case "Add": // unsafe.Add + // This is basically just a GEP operation. + // Note: the pointer is always of type *i8. + ptr := argValues[0] + len := argValues[1] + return b.CreateGEP(ptr, []llvm.Value{len}, ""), nil + case "Slice": // unsafe.Slice + // This creates a slice from a pointer and a length. + // Note that the exception mentioned in the documentation (if the + // pointer and length are nil, the slice is also nil) is trivially + // already the case. + ptr := argValues[0] + len := argValues[1] + slice := llvm.Undef(b.ctx.StructType([]llvm.Type{ + ptr.Type(), + b.uintptrType, + b.uintptrType, + }, false)) + b.createUnsafeSliceCheck(ptr, len, argTypes[1].Underlying().(*types.Basic)) + if len.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() { + // Too small, zero-extend len. + len = b.CreateZExt(len, b.uintptrType, "") + } else if len.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() { + // Too big, truncate len. + len = b.CreateTrunc(len, b.uintptrType, "") + } + slice = b.CreateInsertValue(slice, ptr, 0, "") + slice = b.CreateInsertValue(slice, len, 1, "") + slice = b.CreateInsertValue(slice, len, 2, "") + return slice, nil default: return llvm.Value{}, b.makeError(pos, "todo: builtin: "+callName) } @@ -1928,6 +1960,17 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { default: return llvm.Value{}, b.makeError(expr.Pos(), "unknown slice type: "+typ.String()) } + case *ssa.SliceToArrayPointer: + // Conversion from a slice to an array pointer, as the name clearly + // says. This requires a runtime check to make sure the slice is at + // least as big as the array. + slice := b.getValue(expr.X) + sliceLen := b.CreateExtractValue(slice, 1, "") + arrayLen := expr.Type().Underlying().(*types.Pointer).Elem().Underlying().(*types.Array).Len() + b.createSliceToArrayPointerCheck(sliceLen, arrayLen) + ptr := b.CreateExtractValue(slice, 0, "") + ptr = b.CreateBitCast(ptr, b.getLLVMType(expr.Type()), "") + return ptr, nil case *ssa.TypeAssert: return b.createTypeAssert(expr), nil case *ssa.UnOp: diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 6db5ebb38..0fb9fa913 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/tinygo-org/tinygo/compileopts" + "github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/loader" "tinygo.org/x/go-llvm" ) @@ -16,6 +17,11 @@ import ( // Pass -update to go test to update the output of the test files. var flagUpdate = flag.Bool("update", false, "update tests based on test output") +type testCase struct { + file string + target string +} + // Basic tests for the compiler. Build some Go files and compare the output with // the expected LLVM IR for regression testing. func TestCompiler(t *testing.T) { @@ -34,10 +40,7 @@ func TestCompiler(t *testing.T) { t.Skip("compiler tests require LLVM 11 or above, got LLVM ", llvm.Version) } - tests := []struct { - file string - target string - }{ + tests := []testCase{ {"basic.go", ""}, {"pointer.go", ""}, {"slice.go", ""}, @@ -52,6 +55,14 @@ func TestCompiler(t *testing.T) { {"intrinsics.go", "wasm"}, } + _, minor, err := goenv.GetGorootVersion(goenv.Get("GOROOT")) + if err != nil { + t.Fatal("could not read Go version:", err) + } + if minor >= 17 { + tests = append(tests, testCase{"go1.17.go", ""}) + } + for _, tc := range tests { name := tc.file targetString := "wasm" diff --git a/compiler/testdata/go1.17.go b/compiler/testdata/go1.17.go new file mode 100644 index 000000000..076dded4c --- /dev/null +++ b/compiler/testdata/go1.17.go @@ -0,0 +1,41 @@ +package main + +// Test changes to the language introduced in Go 1.17. +// For details, see: https://tip.golang.org/doc/go1.17#language +// These tests should be merged into the regular slice tests once Go 1.17 is the +// minimun Go version for TinyGo. + +import "unsafe" + +func Add32(p unsafe.Pointer, len int) unsafe.Pointer { + return unsafe.Add(p, len) +} + +func Add64(p unsafe.Pointer, len int64) unsafe.Pointer { + return unsafe.Add(p, len) +} + +func SliceToArray(s []int) *[4]int { + return (*[4]int)(s) +} + +func SliceToArrayConst() *[4]int { + s := make([]int, 6) + return (*[4]int)(s) +} + +func SliceInt(ptr *int, len int) []int { + return unsafe.Slice(ptr, len) +} + +func SliceUint16(ptr *byte, len uint16) []byte { + return unsafe.Slice(ptr, len) +} + +func SliceUint64(ptr *int, len uint64) []int { + return unsafe.Slice(ptr, len) +} + +func SliceInt64(ptr *int, len int64) []int { + return unsafe.Slice(ptr, len) +} diff --git a/compiler/testdata/go1.17.ll b/compiler/testdata/go1.17.ll new file mode 100644 index 000000000..6fa47c8b1 --- /dev/null +++ b/compiler/testdata/go1.17.ll @@ -0,0 +1,136 @@ +; ModuleID = 'go1.17.go' +source_filename = "go1.17.go" +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32--wasi" + +declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) + +define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr { +entry: + ret void +} + +define hidden i8* @main.Add32(i8* %p, i32 %len, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = getelementptr i8, i8* %p, i32 %len + ret i8* %0 +} + +define hidden i8* @main.Add64(i8* %p, i64 %len, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = trunc i64 %len to i32 + %1 = getelementptr i8, i8* %p, i32 %0 + ret i8* %1 +} + +define hidden [4 x i32]* @main.SliceToArray(i32* %s.data, i32 %s.len, i32 %s.cap, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = icmp ult i32 %s.len, 4 + br i1 %0, label %slicetoarray.throw, label %slicetoarray.next + +slicetoarray.throw: ; preds = %entry + call void @runtime.sliceToArrayPointerPanic(i8* undef, i8* null) + unreachable + +slicetoarray.next: ; preds = %entry + %1 = bitcast i32* %s.data to [4 x i32]* + ret [4 x i32]* %1 +} + +declare void @runtime.sliceToArrayPointerPanic(i8*, i8*) + +define hidden [4 x i32]* @main.SliceToArrayConst(i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %makeslice = call i8* @runtime.alloc(i32 24, i8* undef, i8* null) + br i1 false, label %slicetoarray.throw, label %slicetoarray.next + +slicetoarray.throw: ; preds = %entry + unreachable + +slicetoarray.next: ; preds = %entry + %0 = bitcast i8* %makeslice to [4 x i32]* + ret [4 x i32]* %0 +} + +define hidden { i32*, i32, i32 } @main.SliceInt(i32* dereferenceable_or_null(4) %ptr, i32 %len, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = icmp ugt i32 %len, 1073741823 + %1 = icmp eq i32* %ptr, null + %2 = icmp ne i32 %len, 0 + %3 = and i1 %1, %2 + %4 = or i1 %3, %0 + br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next + +unsafe.Slice.throw: ; preds = %entry + call void @runtime.unsafeSlicePanic(i8* undef, i8* null) + unreachable + +unsafe.Slice.next: ; preds = %entry + %5 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0 + %6 = insertvalue { i32*, i32, i32 } %5, i32 %len, 1 + %7 = insertvalue { i32*, i32, i32 } %6, i32 %len, 2 + ret { i32*, i32, i32 } %7 +} + +declare void @runtime.unsafeSlicePanic(i8*, i8*) + +define hidden { i8*, i32, i32 } @main.SliceUint16(i8* dereferenceable_or_null(1) %ptr, i16 %len, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = icmp eq i8* %ptr, null + %1 = icmp ne i16 %len, 0 + %2 = and i1 %0, %1 + br i1 %2, label %unsafe.Slice.throw, label %unsafe.Slice.next + +unsafe.Slice.throw: ; preds = %entry + call void @runtime.unsafeSlicePanic(i8* undef, i8* null) + unreachable + +unsafe.Slice.next: ; preds = %entry + %3 = zext i16 %len to i32 + %4 = insertvalue { i8*, i32, i32 } undef, i8* %ptr, 0 + %5 = insertvalue { i8*, i32, i32 } %4, i32 %3, 1 + %6 = insertvalue { i8*, i32, i32 } %5, i32 %3, 2 + ret { i8*, i32, i32 } %6 +} + +define hidden { i32*, i32, i32 } @main.SliceUint64(i32* dereferenceable_or_null(4) %ptr, i64 %len, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = icmp ugt i64 %len, 1073741823 + %1 = icmp eq i32* %ptr, null + %2 = icmp ne i64 %len, 0 + %3 = and i1 %1, %2 + %4 = or i1 %3, %0 + br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next + +unsafe.Slice.throw: ; preds = %entry + call void @runtime.unsafeSlicePanic(i8* undef, i8* null) + unreachable + +unsafe.Slice.next: ; preds = %entry + %5 = trunc i64 %len to i32 + %6 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0 + %7 = insertvalue { i32*, i32, i32 } %6, i32 %5, 1 + %8 = insertvalue { i32*, i32, i32 } %7, i32 %5, 2 + ret { i32*, i32, i32 } %8 +} + +define hidden { i32*, i32, i32 } @main.SliceInt64(i32* dereferenceable_or_null(4) %ptr, i64 %len, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = icmp ugt i64 %len, 1073741823 + %1 = icmp eq i32* %ptr, null + %2 = icmp ne i64 %len, 0 + %3 = and i1 %1, %2 + %4 = or i1 %3, %0 + br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next + +unsafe.Slice.throw: ; preds = %entry + call void @runtime.unsafeSlicePanic(i8* undef, i8* null) + unreachable + +unsafe.Slice.next: ; preds = %entry + %5 = trunc i64 %len to i32 + %6 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0 + %7 = insertvalue { i32*, i32, i32 } %6, i32 %5, 1 + %8 = insertvalue { i32*, i32, i32 } %7, i32 %5, 2 + ret { i32*, i32, i32 } %8 +} diff --git a/go.mod b/go.mod index 285c02a4a..60b7cbe00 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892 github.com/mattn/go-colorable v0.1.8 go.bug.st/serial v1.1.2 - golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 - golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 + golang.org/x/sys v0.0.0-20210510120138-977fb7262007 + golang.org/x/tools v0.1.6-0.20210813165731-45389f592fe9 tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c ) diff --git a/go.sum b/go.sum index 1451afebf..03688c437 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.bug.st/serial v1.0.0 h1:ogEPzrllCsnG00EqKRjeYvPRsO7NJW6DqykzkdD6E/k= go.bug.st/serial v1.0.0/go.mod h1:rpXPISGjuNjPTRTcMlxi9lN6LoIPxd1ixVjBd8aSk/Q= go.bug.st/serial v1.1.2 h1:6xDpbta8KJ+VLRTeM8ghhxXRMLE/Lr8h9iDKwydarAY= @@ -38,23 +40,32 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.4.2/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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU= -golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 h1:0sfSpGSa544Fwnbot3Oxq/U6SXqjty6Jy/3wRhVS7ig= -golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +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.6-0.20210813165731-45389f592fe9 h1:nvvuMxmx1q0gfRki3T0hjG8EwAcVCs91oWAXvyt4zhI= +golang.org/x/tools v0.1.6-0.20210813165731-45389f592fe9/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +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= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4 h1:CMUHxVTb+UuUePuMf8vkWjZ3gTp9BBK91KrgOCwoNHs= diff --git a/main_test.go b/main_test.go index bbc1a062d..7ebf8eb03 100644 --- a/main_test.go +++ b/main_test.go @@ -20,6 +20,7 @@ import ( "github.com/tinygo-org/tinygo/builder" "github.com/tinygo-org/tinygo/compileopts" + "github.com/tinygo-org/tinygo/goenv" ) const TESTDATA = "testdata" @@ -54,6 +55,14 @@ func TestCompiler(t *testing.T) { "zeroalloc.go", } + _, minor, err := goenv.GetGorootVersion(goenv.Get("GOROOT")) + if err != nil { + t.Fatal("could not read version from GOROOT:", err) + } + if minor >= 17 { + tests = append(tests, "go1.17.go") + } + if *testTarget != "" { // This makes it possible to run one specific test (instead of all), // which is especially useful to quickly check whether some changes diff --git a/src/runtime/panic.go b/src/runtime/panic.go index cf7534173..37b7c259a 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -42,6 +42,18 @@ func slicePanic() { runtimePanic("slice out of range") } +// Panic when trying to convert a slice to an array pointer (Go 1.17+) and the +// slice is shorter than the array. +func sliceToArrayPointerPanic() { + runtimePanic("slice smaller than array") +} + +// Panic when calling unsafe.Slice() (Go 1.17+) with a len that's too large +// (which includes if the ptr is nil and len is nonzero). +func unsafeSlicePanic() { + runtimePanic("unsafe.Slice: len out of range") +} + // Panic when trying to create a new channel that is too big. func chanMakePanic() { runtimePanic("new channel is too big") diff --git a/testdata/go1.17.go b/testdata/go1.17.go new file mode 100644 index 000000000..2a9fba75c --- /dev/null +++ b/testdata/go1.17.go @@ -0,0 +1,34 @@ +package main + +// Test new language features introduced in Go 1.17: +// https://tip.golang.org/doc/go1.17#language +// Once this becomes the minimum Go version of TinyGo, these tests should be +// merged with the regular slice tests. + +import "unsafe" + +func main() { + // Test conversion from array to slice. + slice1 := []int{1, 2, 3, 4} + arr1 := (*[4]int)(slice1) + arr1[1] = -2 + arr1[2] = 20 + println("slice to array pointer:", arr1[0], arr1[1], arr1[2], arr1[3]) + + // Test unsafe.Add. + arr2 := [...]int{1, 2, 3, 4} + *(*int)(unsafe.Add(unsafe.Pointer(&arr2[0]), unsafe.Sizeof(int(1))*1)) = 5 + *addInt(&arr2[0], 2) = 8 + println("unsafe.Add array:", arr2[0], arr2[1], arr2[2], arr2[3]) + + // Test unsafe.Slice. + arr3 := [...]int{1, 2, 3, 4} + slice3 := unsafe.Slice(&arr3[1], 3) + slice3[0] = 9 + slice3[1] = 15 + println("unsafe.Slice array:", len(slice3), cap(slice3), slice3[0], slice3[1], slice3[2]) +} + +func addInt(ptr *int, index uintptr) *int { + return (*int)(unsafe.Add(unsafe.Pointer(ptr), unsafe.Sizeof(int(1))*index)) +} diff --git a/testdata/go1.17.txt b/testdata/go1.17.txt new file mode 100644 index 000000000..eafc1b45a --- /dev/null +++ b/testdata/go1.17.txt @@ -0,0 +1,3 @@ +slice to array pointer: 1 -2 20 4 +unsafe.Add array: 1 5 8 4 +unsafe.Slice array: 3 3 9 15 4 From 98bd947817ab57dab8009118a096dc680bc43820 Mon Sep 17 00:00:00 2001 From: sago35 Date: Tue, 17 Aug 2021 21:15:24 +0900 Subject: [PATCH 27/47] machine/arduino_mkrwifi1010: add board definition for Arduino MKR WiFi 1010 --- Makefile | 2 + README.md | 3 +- src/machine/board_arduino_mkrwifi1010.go | 154 +++++++++++++++++++++++ targets/arduino-mkrwifi1010.json | 8 ++ 4 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 src/machine/board_arduino_mkrwifi1010.go create mode 100644 targets/arduino-mkrwifi1010.json diff --git a/Makefile b/Makefile index 709dfb112..185c1df4f 100644 --- a/Makefile +++ b/Makefile @@ -363,6 +363,8 @@ smoketest: @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=arduino-nano33 examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=arduino-mkrwifi1010 examples/blinky1 + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pico examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=nano-33-ble examples/blinky1 diff --git a/README.md b/README.md index 8f233d6ba..14ecfbc55 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for You can compile TinyGo programs for microcontrollers, WebAssembly and Linux. -The following 68 microcontroller boards are currently supported: +The following 69 microcontroller boards are currently supported: * [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333) * [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333) @@ -69,6 +69,7 @@ The following 68 microcontroller boards are currently supported: * [Arduino Mega 1280](https://www.arduino.cc/en/Main/arduinoBoardMega/) * [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3) * [Arduino MKR1000](https://store.arduino.cc/arduino-mkr1000-wifi) +* [Arduino MKR WiFi 1010](https://store.arduino.cc/usa/mkr-wifi-1010) * [Arduino Nano](https://store.arduino.cc/arduino-nano) * [Arduino Nano 33 BLE](https://store.arduino.cc/nano-33-ble) * [Arduino Nano 33 BLE Sense](https://store.arduino.cc/nano-33-ble-sense) diff --git a/src/machine/board_arduino_mkrwifi1010.go b/src/machine/board_arduino_mkrwifi1010.go new file mode 100644 index 000000000..a96105f39 --- /dev/null +++ b/src/machine/board_arduino_mkrwifi1010.go @@ -0,0 +1,154 @@ +// +build arduino_mkrwifi1010 + +// This contains the pin mappings for the Arduino MKR WiFi 1010 board. +// +// For more information, see: https://store.arduino.cc/usa/mkr-wifi-1010 +// +package machine + +import ( + "device/sam" + "runtime/interrupt" +) + +// used to reset into bootloader +const RESET_MAGIC_VALUE = 0x07738135 + +// GPIO Pins +const ( + RX0 Pin = PB23 // UART1 RX + TX1 Pin = PB22 // UART1 TX + + D0 Pin = PA22 // PWM available + D1 Pin = PA23 // PWM available + D2 Pin = PA10 // PWM available + D3 Pin = PA11 // PWM available + D4 Pin = PB10 // PWM available + D5 Pin = PB11 // PWM available + + D6 Pin = PA20 // PWM available + D7 Pin = PA21 // PWM available + D8 Pin = PA16 // PWM available + D9 Pin = PA17 + D10 Pin = PA19 // PWM available + D11 Pin = PA08 // SDA + D12 Pin = PA09 // PWM available, SCL + D13 Pin = PB23 // RX + D14 Pin = PB22 // TX +) + +// Analog pins +const ( + A0 Pin = PA02 // ADC0/AIN[0] + A1 Pin = PB02 // AIN[10] + A2 Pin = PB03 // AIN[11] + A3 Pin = PA04 // AIN[04] + A4 Pin = PA05 // AIN[05] + A5 Pin = PA06 // AIN[06] + A6 Pin = PA07 // AIN[07] +) + +const ( + LED = D6 +) + +// USBCDC pins +const ( + USBCDC_DM_PIN Pin = PA24 + USBCDC_DP_PIN Pin = PA25 +) + +// UART1 pins +const ( + UART_TX_PIN Pin = PB22 + UART_RX_PIN Pin = PB23 +) + +// I2C pins +const ( + SDA_PIN Pin = D11 // SDA + SCL_PIN Pin = D12 // SCL +) + +// SPI pins +const ( + SPI0_SCK_PIN Pin = D9 // SCK: S1 + SPI0_SDO_PIN Pin = D8 // SDO: S1 + SPI0_SDI_PIN Pin = D10 // SDI: S1 +) + +// I2S pins +const ( + I2S_SCK_PIN Pin = PA10 + I2S_SD_PIN Pin = PA07 + I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino MKR WiFi 1010. +) + +// NINA-W102 Pins +const ( + NINA_SDO Pin = PA12 + NINA_SDI Pin = PA13 + NINA_CS Pin = PA14 + NINA_SCK Pin = PA15 + NINA_GPIO0 Pin = PA27 + NINA_RESETN Pin = PA08 + NINA_ACK Pin = PA28 + NINA_TX Pin = PA22 + NINA_RX Pin = PA23 +) + +// UART on the Arduino MKR WiFi 1010. +var ( + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: sam.SERCOM5_USART, + SERCOM: 5, + } +) + +func init() { + UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM5, _UART1.handleInterrupt) +} + +// I2C on the Arduino MKR WiFi 1010. +var ( + I2C0 = &I2C{ + Bus: sam.SERCOM2_I2CM, + SERCOM: 2, + } +) + +// SPI on the Arduino MKR WiFi 1010. +var ( + SPI0 = SPI{ + Bus: sam.SERCOM1_SPI, + SERCOM: 1, + } + + SPI1 = SPI{ + Bus: sam.SERCOM4_SPI, + SERCOM: 4, + } + NINA_SPI = SPI1 +) + +// I2S on the Arduino MKR WiFi 1010. +var ( + I2S0 = I2S{Bus: sam.I2S} +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Arduino MKR WiFi 1010" + usb_STRING_MANUFACTURER = "Arduino" +) + +var ( + usb_VID uint16 = 0x2341 + usb_PID uint16 = 0x8054 +) + +var ( + DefaultUART = UART1 +) diff --git a/targets/arduino-mkrwifi1010.json b/targets/arduino-mkrwifi1010.json new file mode 100644 index 000000000..2e9d68569 --- /dev/null +++ b/targets/arduino-mkrwifi1010.json @@ -0,0 +1,8 @@ +{ + "inherits": ["atsamd21g18a"], + "build-tags": ["arduino_mkrwifi1010"], + "serial": "usb", + "serial-port": ["acm:2341:8054", "acm:2341:0054"], + "flash-command": "bossac -i -e -w -v -R -U --port={port} --offset=0x2000 {bin}", + "flash-1200-bps-reset": "true" +} From 6c6fea5387105cbc601a8aec03a2c7069579059d Mon Sep 17 00:00:00 2001 From: Olivier Fauchon Date: Sun, 13 Jun 2021 20:55:32 +0200 Subject: [PATCH 28/47] BlackMagic (BMP) ARM JTAG/SWD debugger: - Flashing and debugging with BMP can be done with -programmer=bmp - New getBMPPorts() function was added to properly detect BMP USB serial ports. --- compileopts/config.go | 3 +++ main.go | 57 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/compileopts/config.go b/compileopts/config.go index f2ba28578..8f815ffb1 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -297,6 +297,9 @@ func (c *Config) Programmer() (method, openocdInterface string) { case "openocd", "msd", "command": // The -programmer flag only specifies the flash method. return c.Options.Programmer, c.Target.OpenOCDInterface + case "bmp": + // The -programmer flag only specifies the flash method. + return c.Options.Programmer, "" default: // The -programmer flag specifies something else, assume it specifies // the OpenOCD interface name. diff --git a/main.go b/main.go index 612ad0747..188a0100b 100644 --- a/main.go +++ b/main.go @@ -293,6 +293,8 @@ func Flash(pkgName, port string, options *compileopts.Options) error { fileExt = filepath.Ext(config.Target.FlashFilename) case "openocd": fileExt = ".hex" + case "bmp": + fileExt = ".elf" case "native": return errors.New("unknown flash method \"native\" - did you miss a -target flag?") default: @@ -385,6 +387,25 @@ func Flash(pkgName, port string, options *compileopts.Options) error { return &commandError{"failed to flash", result.Binary, err} } return nil + case "bmp": + gdb, err := config.Target.LookupGDB() + if err != nil { + return err + } + var bmpGDBPort string + bmpGDBPort, _, err = getBMPPorts() + if err != nil { + return err + } + args := []string{"-ex", "target extended-remote " + bmpGDBPort, "-ex", "monitor swdp_scan", "-ex", "attach 1", "-ex", "load", filepath.ToSlash(result.Binary)} + cmd := executeCommand(config.Options, gdb, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err = cmd.Run() + if err != nil { + return &commandError{"failed to flash", result.Binary, err} + } + return nil default: return fmt.Errorf("unknown flash method: %s", flashMethod) } @@ -439,6 +460,13 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro switch gdbInterface { case "native": // Run GDB directly. + case "bmp": + var bmpGDBPort string + bmpGDBPort, _, err = getBMPPorts() + if err != nil { + return err + } + gdbCommands = append(gdbCommands, "target extended-remote "+bmpGDBPort, "monitor swdp_scan", "compare-sections", "attach 1", "load") case "openocd": gdbCommands = append(gdbCommands, "target extended-remote :3333", "monitor halt", "load", "monitor reset halt") @@ -846,6 +874,35 @@ func getDefaultPort(portFlag string, usbInterfaces []string) (port string, err e return "", errors.New("port you specified '" + strings.Join(portCandidates, ",") + "' does not exist, available ports are " + strings.Join(ports, ", ")) } +// getBMPPorts returns BlackMagicProbe's serial ports if any +func getBMPPorts() (gdbPort, uartPort string, err error) { + var portsList []*enumerator.PortDetails + portsList, err = enumerator.GetDetailedPortsList() + if err != nil { + return "", "", err + } + var ports []string + for _, p := range portsList { + if !p.IsUSB { + continue + } + if p.VID != "" && p.PID != "" { + vid, vidErr := strconv.ParseUint(p.VID, 16, 16) + pid, pidErr := strconv.ParseUint(p.PID, 16, 16) + if vidErr == nil && pidErr == nil && vid == 0x1d50 && pid == 0x6018 { + ports = append(ports, p.Name) + } + } + } + if len(ports) == 2 { + return ports[0], ports[1], nil + } else if len(ports) == 0 { + return "", "", errors.New("no BMP detected") + } else { + return "", "", fmt.Errorf("expected 2 BMP serial ports, found %d - did you perhaps connect more than one BMP?", len(ports)) + } +} + func usage() { fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.") fmt.Fprintln(os.Stderr, "version:", goenv.Version) From 2d224ae049286acf77298ee7060aa2dea2fdec19 Mon Sep 17 00:00:00 2001 From: "Federico G. Schwindt" Date: Tue, 31 Aug 2021 10:46:45 +0100 Subject: [PATCH 29/47] Minor changes to support go 1.17 --- src/os/{file_go_116.go => file_go_new.go} | 2 +- src/os/{file_go_other.go => file_go_old.go} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/os/{file_go_116.go => file_go_new.go} (99%) rename src/os/{file_go_other.go => file_go_old.go} (98%) diff --git a/src/os/file_go_116.go b/src/os/file_go_new.go similarity index 99% rename from src/os/file_go_116.go rename to src/os/file_go_new.go index 7049d37e5..594e36abb 100644 --- a/src/os/file_go_116.go +++ b/src/os/file_go_new.go @@ -1,4 +1,4 @@ -// +build go1.16 +// +build go1.16 go1.17 package os diff --git a/src/os/file_go_other.go b/src/os/file_go_old.go similarity index 98% rename from src/os/file_go_other.go rename to src/os/file_go_old.go index 351de7acc..20748b586 100644 --- a/src/os/file_go_other.go +++ b/src/os/file_go_old.go @@ -1,4 +1,4 @@ -// +build !go1.16 +// +build !go1.16,!go1.17 package os From a7c53cce065f79a4b9e1cad1e47abf3ae0538134 Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Wed, 1 Sep 2021 11:58:13 -0300 Subject: [PATCH 30/47] machine/rp2040: add PWM implementation (#2015) machine/rp2040: add PWM implementation --- src/examples/pwm/pico.go | 11 + src/machine/machine_rp2040_gpio.go | 3 + src/machine/machine_rp2040_pwm.go | 384 +++++++++++++++++++++++++++++ 3 files changed, 398 insertions(+) create mode 100644 src/examples/pwm/pico.go create mode 100644 src/machine/machine_rp2040_pwm.go diff --git a/src/examples/pwm/pico.go b/src/examples/pwm/pico.go new file mode 100644 index 000000000..0c0c0a83d --- /dev/null +++ b/src/examples/pwm/pico.go @@ -0,0 +1,11 @@ +// +build pico + +package main + +import "machine" + +var ( + pwm = machine.PWM4 // Pin 25 (LED on pico) corresponds to PWM4. + pinA = machine.LED + pinB = machine.GPIO24 +) diff --git a/src/machine/machine_rp2040_gpio.go b/src/machine/machine_rp2040_gpio.go index 5e3d06cc0..3aea11e82 100644 --- a/src/machine/machine_rp2040_gpio.go +++ b/src/machine/machine_rp2040_gpio.go @@ -81,6 +81,7 @@ const ( PinInputPullup PinAnalog PinUART + PinPWM PinI2C PinSPI ) @@ -181,6 +182,8 @@ func (p Pin) Configure(config PinConfig) { p.pulloff() case PinUART: p.setFunc(fnUART) + case PinPWM: + p.setFunc(fnPWM) case PinI2C: // IO config according to 4.3.1.3 of rp2040 datasheet. p.setFunc(fnI2C) diff --git a/src/machine/machine_rp2040_pwm.go b/src/machine/machine_rp2040_pwm.go new file mode 100644 index 000000000..e0ecc9872 --- /dev/null +++ b/src/machine/machine_rp2040_pwm.go @@ -0,0 +1,384 @@ +// +build rp2040 + +package machine + +import ( + "device/rp" + "errors" + "runtime/volatile" + "unsafe" +) + +var ( + ErrPeriodTooBig = errors.New("period outside valid range 1..4e9ns") +) + +const ( + maxPWMPins = 29 +) + +// pwmGroup is one PWM peripheral, which consists of a counter and two output +// channels. You can set the frequency using SetPeriod, +// but only for all the channels in this PWM peripheral at once. +// +// div: integer value to reduce counting rate by. Must be greater than or equal to 1. +// +// cc: counter compare level. Contains 2 channel levels. The 16 LSBs are Channel A's level (Duty Cycle) +// and the 16 MSBs are Channel B's level. +// +// top: Wrap. Highest number counter will reach before wrapping over. usually 0xffff. +// +// csr: Clock mode. PWM_CH0_CSR_DIVMODE_xxx registers have 4 possible modes, of which Free-running is used. +// csr contains output polarity bit at PWM_CH0_CSR_x_INV where x is the channel. +// csr contains phase correction bit at PWM_CH0_CSR_PH_CORRECT_Msk. +// csr contains PWM enable bit at PWM_CH0_CSR_EN. If not enabled PWM will not be active. +// +// ctr: PWM counter value. +type pwmGroup struct { + CSR volatile.Register32 + DIV volatile.Register32 + CTR volatile.Register32 + CC volatile.Register32 + TOP volatile.Register32 +} + +// Equivalent of +// var pwmSlice []pwmGroup = (*[8]pwmGroup)(unsafe.Pointer(rp.PWM))[:] +// return &pwmSlice[index] +// 0x14 is the size of a pwmGroup. +func getPWMGroup(index uintptr) *pwmGroup { + return (*pwmGroup)(unsafe.Pointer(uintptr(unsafe.Pointer(rp.PWM)) + 0x14*index)) +} + +// PWM peripherals available on RP2040. Each peripheral has 2 pins available for +// a total of 16 available PWM outputs. Some pins may not be available on some boards. +var ( + PWM0 = getPWMGroup(0) + PWM1 = getPWMGroup(1) + PWM2 = getPWMGroup(2) + PWM3 = getPWMGroup(3) + PWM4 = getPWMGroup(4) + PWM5 = getPWMGroup(5) + PWM6 = getPWMGroup(6) + PWM7 = getPWMGroup(7) +) + +// Configure enables and configures this PWM. +func (pwm *pwmGroup) Configure(config PWMConfig) error { + return pwm.init(config, true) +} + +// Channel returns a PWM channel for the given pin. If pin does +// not belong to PWM peripheral ErrInvalidOutputPin error is returned. +// It also configures pin as PWM output. +func (pwm *pwmGroup) Channel(pin Pin) (channel uint8, err error) { + if pin > maxPWMPins || pwmGPIOToSlice(pin) != pwm.peripheral() { + return 3, ErrInvalidOutputPin + } + pin.Configure(PinConfig{PinPWM}) + return pwmGPIOToChannel(pin), nil +} + +// Peripheral returns the RP2040 PWM peripheral which ranges from 0 to 7. Each +// PWM peripheral has 2 channels, A and B which correspond to 0 and 1 in the program. +// This number corresponds to the package's PWM0 throughout PWM7 handles +func PWMPeripheral(pin Pin) (sliceNum uint8, err error) { + if pin > maxPWMPins { + return 0, ErrInvalidOutputPin + } + return pwmGPIOToSlice(pin), nil +} + +// returns the number of the pwm peripheral (0-7) +func (pwm *pwmGroup) peripheral() uint8 { + return uint8((uintptr(unsafe.Pointer(pwm)) - uintptr(unsafe.Pointer(rp.PWM))) / 0x14) +} + +// SetPeriod updates the period of this PWM peripheral. +// To set a particular frequency, use the following formula: +// +// period = 1e9 / frequency +// +// If you use a period of 0, a period that works well for LEDs will be picked. +// +// SetPeriod will not change the prescaler, but also won't change the current +// value in any of the channels. This means that you may need to update the +// value for the particular channel. +// +// Note that you cannot pick any arbitrary period after the PWM peripheral has +// been configured. If you want to switch between frequencies, pick the lowest +// frequency (longest period) once when calling Configure and adjust the +// frequency here as needed. +func (p *pwmGroup) SetPeriod(period uint64) error { + if period > 0xffff_ffff { + return ErrPeriodTooBig + } + if period == 0 { + period = 1e5 + } + p.setPeriod(period) + return nil +} + +// Top returns the current counter top, for use in duty cycle calculation. +// +// The value returned here is hardware dependent. In general, it's best to treat +// it as an opaque value that can be divided by some number and passed to Set +// (see Set documentation for more information). +func (p *pwmGroup) Top() uint32 { + return p.getWrap() +} + +// Counter returns the current counter value of the timer in this PWM +// peripheral. It may be useful for debugging. +func (p *pwmGroup) Counter() uint32 { + return (p.CTR.Get() & rp.PWM_CH0_CTR_CH0_CTR_Msk) >> rp.PWM_CH0_CTR_CH0_CTR_Pos +} + +// Period returns the used PWM period in nanoseconds. It might deviate slightly +// from the configured period due to rounding. +func (p *pwmGroup) Period() uint64 { + periodPerCycle := getPeriod() + top := p.getWrap() + phc := p.getPhaseCorrect() + Int, frac := p.getClockDiv() + return uint64((Int + frac/16) * (top + 1) * (phc + 1) * periodPerCycle) // cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16) +} + +// SetInverting sets whether to invert the output of this channel. +// Without inverting, a 25% duty cycle would mean the output is high for 25% of +// the time and low for the rest. Inverting flips the output as if a NOT gate +// was placed at the output, meaning that the output would be 25% low and 75% +// high with a duty cycle of 25%. +func (p *pwmGroup) SetInverting(channel uint8, inverting bool) { + channel &= 1 + p.setInverting(channel, inverting) +} + +// Set updates the channel value. This is used to control the channel duty +// cycle, in other words the fraction of time the channel output is high (or low +// when inverted). For example, to set it to a 25% duty cycle, use: +// +// pwm.Set(channel, pwm.Top() / 4) +// +// pwm.Set(channel, 0) will set the output to low and pwm.Set(channel, +// pwm.Top()) will set the output to high, assuming the output isn't inverted. +func (p *pwmGroup) Set(channel uint8, value uint32) { + val := uint16(value) + channel &= 1 + p.setChanLevel(channel, val) +} + +// Get current level (last set by Set). Default value on initialization is 0. +func (p *pwmGroup) Get(channel uint8) (value uint32) { + channel &= 1 + return uint32(p.getChanLevel(channel)) +} + +// SetTop sets TOP control register. Max value is 16bit (0xffff). +func (p *pwmGroup) SetTop(top uint32) { + p.setWrap(uint16(top)) +} + +// Enable enables or disables PWM peripheral channels. +func (p *pwmGroup) Enable(enable bool) { + p.enable(enable) +} + +// IsEnabled returns true if peripheral is enabled. +func (p *pwmGroup) IsEnabled() (enabled bool) { + return (p.CSR.Get()&rp.PWM_CH0_CSR_EN_Msk)>>rp.PWM_CH0_CSR_EN_Pos != 0 +} + +// Hardware Pulse Width Modulation (PWM) API +// +// The RP2040 PWM block has 8 identical slices. Each slice can drive two PWM output signals, or +// measure the frequency or duty cycle of an input signal. This gives a total of up to 16 controllable +// PWM outputs. All 30 GPIOs can be driven by the PWM block +// +// The PWM hardware functions by continuously comparing the input value to a free-running counter. This produces a +// toggling output where the amount of time spent at the high output level is proportional to the input value. The fraction of +// time spent at the high signal level is known as the duty cycle of the signal. +// +// The default behaviour of a PWM slice is to count upward until the wrap value (\ref pwm_config_set_wrap) is reached, and then +// immediately wrap to 0. PWM slices also offer a phase-correct mode, where the counter starts to count downward after +// reaching TOP, until it reaches 0 again. +type pwms struct { + slice pwmGroup + hw *rp.PWM_Type +} + +// Handle to all pwm peripheral registers. +var _PWM = pwms{ + hw: rp.PWM, +} + +// Initialise a PWM with settings from a configuration object. +// If start is true then PWM starts on initialization. +func (pwm *pwmGroup) init(config PWMConfig, start bool) error { + // Not enable Phase correction + pwm.setPhaseCorrect(false) + + // Clock mode set by default to Free running + pwm.setDivMode(rp.PWM_CH0_CSR_DIVMODE_DIV) + + // Set Output polarity (false/false) + pwm.setInverting(0, false) + pwm.setInverting(1, false) + + // Set wrap. The highest value the counter will reach before returning to zero, also known as TOP. + pwm.setWrap(0xffff) + // period is set after TOP (Wrap). + err := pwm.SetPeriod(config.Period) + if err != nil { + return err + } + // period already set beforea + // Reset counter and compare (pwm level set to zero) + pwm.CTR.ReplaceBits(0, rp.PWM_CH0_CTR_CH0_CTR_Msk, 0) // PWM_CH0_CTR_RESET + pwm.CC.Set(0) // PWM_CH0_CC_RESET + + pwm.enable(start) + return nil +} + +func (pwm *pwmGroup) setPhaseCorrect(correct bool) { + pwm.CSR.ReplaceBits(boolToBit(correct)< cycles = target_period/period_per_cycle + Int := targetPeriod/((1+phc)*periodPerCycle*(1+top)) - frac/16 + if Int > 0xff { + Int = 0xff + } + pwm.setClockDiv(uint8(Int), 0) +} + +// Int is integer value to reduce counting rate by. Must be greater than or equal to 1. DIV_INT is bits 4:11 (8 bits). +// frac's (DIV_FRAC) default value on reset is 0. Max value for frac is 15 (4 bits). This is known as a fixed-point +// fractional number. +// +// cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16) +func (pwm *pwmGroup) setClockDiv(Int, frac uint8) { + pwm.DIV.ReplaceBits((uint32(frac)<> pos) + return level +} + +func (pwm *pwmGroup) getWrap() (top uint32) { + return (pwm.TOP.Get() & rp.PWM_CH0_TOP_CH0_TOP_Msk) >> rp.PWM_CH0_TOP_CH0_TOP_Pos +} + +func (pwm *pwmGroup) getPhaseCorrect() (phCorrect uint32) { + return (pwm.CSR.Get() & rp.PWM_CH0_CSR_PH_CORRECT_Msk) >> rp.PWM_CH0_CSR_PH_CORRECT_Pos +} + +func (pwm *pwmGroup) getClockDiv() (Int, frac uint32) { + div := pwm.DIV.Get() + return (div & rp.PWM_CH0_DIV_INT_Msk) >> rp.PWM_CH0_DIV_INT_Pos, (div & rp.PWM_CH0_DIV_FRAC_Msk) >> rp.PWM_CH0_DIV_FRAC_Pos +} + +// pwmGPIOToSlice Determine the PWM channel that is attached to the specified GPIO. +// gpio must be less than 30. Returns the PWM slice number that controls the specified GPIO. +func pwmGPIOToSlice(gpio Pin) (slicenum uint8) { + return (uint8(gpio) >> 1) & 7 +} + +// Determine the PWM channel that is attached to the specified GPIO. +// Each slice 0 to 7 has two channels, A and B. +func pwmGPIOToChannel(gpio Pin) (channel uint8) { + return uint8(gpio) & 1 +} + +// Returns the period of a clock cycle for the raspberry pi pico in nanoseconds. +func getPeriod() uint32 { + const periodIn uint32 = 1e9 / (125 * MHz) + return periodIn +} From 97d48e5c0286852de319613136d2996d38fa4ab8 Mon Sep 17 00:00:00 2001 From: Yurii Soldak Date: Thu, 26 Aug 2021 12:30:10 +0200 Subject: [PATCH 31/47] board/nano-rp2040: define NINA_SPI and fix wifinina pins --- src/machine/board_nano-rp2040.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/machine/board_nano-rp2040.go b/src/machine/board_nano-rp2040.go index 1b799d70b..d91a26589 100644 --- a/src/machine/board_nano-rp2040.go +++ b/src/machine/board_nano-rp2040.go @@ -68,6 +68,10 @@ const ( SPI1_SDI_PIN Pin = GPIO22 ) +var ( + NINA_SPI = SPI1 +) + // NINA-W102 Pins const ( NINA_SCK Pin = GPIO14 @@ -76,7 +80,7 @@ const ( NINA_CS Pin = GPIO9 NINA_ACK Pin = GPIO10 - NINA_GPIO0 Pin = GPIO0 + NINA_GPIO0 Pin = GPIO2 NINA_RESETN Pin = GPIO3 NINA_TX Pin = GPIO9 From f0936ffccb725d304692980417a9b9db3917264b Mon Sep 17 00:00:00 2001 From: "Federico G. Schwindt" Date: Tue, 31 Aug 2021 11:45:12 +0100 Subject: [PATCH 32/47] Implement os.Executable For now this is a stub for everything but linux, which is a slightly modified copy of the official implementation. Should address #1778. --- src/os/executable_other.go | 9 +++++++++ src/os/executable_procfs.go | 25 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 src/os/executable_other.go create mode 100644 src/os/executable_procfs.go diff --git a/src/os/executable_other.go b/src/os/executable_other.go new file mode 100644 index 000000000..15a9f9f7b --- /dev/null +++ b/src/os/executable_other.go @@ -0,0 +1,9 @@ +// +build !linux + +package os + +import "errors" + +func Executable() (string, error) { + return "", errors.New("Executable not implemented") +} diff --git a/src/os/executable_procfs.go b/src/os/executable_procfs.go new file mode 100644 index 000000000..2f9d0b637 --- /dev/null +++ b/src/os/executable_procfs.go @@ -0,0 +1,25 @@ +// The following is copied from Go 1.17 official implementation. + +// Copyright 2016 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. + +// +build linux + +package os + +func Executable() (string, error) { + path, err := Readlink("/proc/self/exe") + + // When the executable has been deleted then Readlink returns a + // path appended with " (deleted)". + return stringsTrimSuffix(path, " (deleted)"), err +} + +// stringsTrimSuffix is the same as strings.TrimSuffix. +func stringsTrimSuffix(s, suffix string) string { + if len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix { + return s[:len(s)-len(suffix)] + } + return s +} From f985f2c376da5413a4aad44e09ca8ba1d97e7a89 Mon Sep 17 00:00:00 2001 From: sago35 Date: Tue, 31 Aug 2021 20:45:44 +0900 Subject: [PATCH 33/47] targets: add openocd configuration for rp2040 --- targets/rp2040.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/targets/rp2040.json b/targets/rp2040.json index c679758e6..917a743e9 100644 --- a/targets/rp2040.json +++ b/targets/rp2040.json @@ -9,5 +9,7 @@ "rp2040-boot-patch": true, "extra-files": [ "src/device/rp/rp2040.s" - ] + ], + "openocd-transport": "swd", + "openocd-target": "rp2040" } From ca39bc9f355c32d6a2ed2d2b5056e7e3f8b7e58d Mon Sep 17 00:00:00 2001 From: sago35 Date: Tue, 31 Aug 2021 21:16:28 +0900 Subject: [PATCH 34/47] machine/feather-rp2040: add pin name definition for feather --- src/machine/board_feather_rp2040.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/machine/board_feather_rp2040.go b/src/machine/board_feather_rp2040.go index 58816f05c..9226760bd 100644 --- a/src/machine/board_feather_rp2040.go +++ b/src/machine/board_feather_rp2040.go @@ -9,6 +9,28 @@ const ( xoscFreq = 12 // MHz ) +// GPIO Pins +const ( + D4 = GPIO6 + D5 = GPIO7 + D6 = GPIO8 + D9 = GPIO9 + D10 = GPIO10 + D11 = GPIO11 + D12 = GPIO12 + D13 = GPIO13 + D24 = GPIO24 + D25 = GPIO25 +) + +// Analog pins +const ( + A0 = GPIO26 + A1 = GPIO27 + A2 = GPIO28 + A3 = GPIO29 +) + // I2C Pins. const ( I2C0_SDA_PIN = GPIO24 From 4b1f92600f3f201923f67312aad3658fe5c06067 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Wed, 1 Sep 2021 19:54:03 +0200 Subject: [PATCH 35/47] docker: use go 1.17 for docker dev build Signed-off-by: deadprogram --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7e65e6583..399f74f7b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ -# TinyGo base stage installs the most recent Go 1.16.x, LLVM 11 and the TinyGo compiler itself. -FROM golang:1.16 AS tinygo-base +# TinyGo base stage installs the most recent Go 1.17.x, LLVM 11 and the TinyGo compiler itself. +FROM golang:1.17 AS tinygo-base RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \ echo "deb http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-11 main" >> /etc/apt/sources.list && \ From 5fa1e7163aa5e87d536ffa062b7d818665237ffe Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Fri, 3 Sep 2021 16:30:32 -0700 Subject: [PATCH 36/47] src/runtime: reset heapptr to heapStart after preinit() heapptr is assinged to heapStart (which is 0) when it's declared, but preinit() may have moved the heap somewhere else. Set heapptr to the proper value of heapStart when we initialize the heap properly. This allows the leaking allocator to work on unix. --- src/runtime/gc_leaking.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/runtime/gc_leaking.go b/src/runtime/gc_leaking.go index bbded072e..2dc76af34 100644 --- a/src/runtime/gc_leaking.go +++ b/src/runtime/gc_leaking.go @@ -52,7 +52,8 @@ func SetFinalizer(obj interface{}, finalizer interface{}) { } func initHeap() { - // Nothing to initialize. + // preinit() may have moved heapStart; reset heapptr + heapptr = heapStart } // setHeapEnd sets a new (larger) heapEnd pointer. From b3f1dacbb914ea793c07351a9ce42a0d4702eba5 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Sat, 4 Sep 2021 08:04:50 -0700 Subject: [PATCH 37/47] interp: remove unused gepOperands slice --- interp/interpreter.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/interp/interpreter.go b/interp/interpreter.go index c6ce6b7f9..db121cc75 100644 --- a/interp/interpreter.go +++ b/interp/interpreter.go @@ -590,18 +590,14 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // GetElementPtr does pointer arithmetic, changing the offset of the // pointer into the underlying object. var offset uint64 - var gepOperands []uint64 for i := 2; i < len(operands); i += 2 { index := operands[i].Uint() elementSize := operands[i+1].Uint() if int64(elementSize) < 0 { // This is a struct field. - // The field number is encoded by flipping all the bits. - gepOperands = append(gepOperands, ^elementSize) offset += index } else { // This is a normal GEP, probably an array index. - gepOperands = append(gepOperands, index) offset += elementSize * index } } From bbbe7d43ce1c0c3ae97e1fd1a50479ac28ae7fa7 Mon Sep 17 00:00:00 2001 From: sago35 Date: Sun, 5 Sep 2021 09:51:55 +0900 Subject: [PATCH 38/47] machine/arduino_mkrwifi1010: fix pin definition of NINA_RESETN --- src/machine/board_arduino_mkrwifi1010.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/machine/board_arduino_mkrwifi1010.go b/src/machine/board_arduino_mkrwifi1010.go index a96105f39..e9df43cb9 100644 --- a/src/machine/board_arduino_mkrwifi1010.go +++ b/src/machine/board_arduino_mkrwifi1010.go @@ -91,7 +91,7 @@ const ( NINA_CS Pin = PA14 NINA_SCK Pin = PA15 NINA_GPIO0 Pin = PA27 - NINA_RESETN Pin = PA08 + NINA_RESETN Pin = PB08 NINA_ACK Pin = PA28 NINA_TX Pin = PA22 NINA_RX Pin = PA23 From fd9422d21802636608d64f4e01f17e820c99684a Mon Sep 17 00:00:00 2001 From: Mike Mogenson <$EMAIL> Date: Mon, 7 Dec 2020 16:55:16 -0500 Subject: [PATCH 39/47] fix GBA ROM header Populate the GBA ROM header so that emulators and physical Game Boy Advance consoles recognize the ROM as a valid game. Note: The reserve space at the end of the header was hand-tuned. Why this magic value? --- targets/gameboy-advance.s | 52 +++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/targets/gameboy-advance.s b/targets/gameboy-advance.s index 84c275c91..7da6f741f 100644 --- a/targets/gameboy-advance.s +++ b/targets/gameboy-advance.s @@ -5,16 +5,48 @@ _start: b start_vector - .fill 156,1,0 // Nintendo Logo Character Data (8000004h) - .fill 16,1,0 // Game Title - .byte 0x30,0x31 // Maker Code (80000B0h) - .byte 0x96 // Fixed Value (80000B2h) - .byte 0x00 // Main Unit Code (80000B3h) - .byte 0x00 // Device Type (80000B4h) - .fill 7,1,0 // unused - .byte 0x00 // Software Version No (80000BCh) - .byte 0xf0 // Complement Check (80000BDh) - .byte 0x00,0x00 // Checksum (80000BEh) + + // ROM header + .byte 0x24,0xff,0xae,0x51,0x69,0x9a,0xa2,0x21,0x3d,0x84,0x82,0x0a,0x84,0xe4,0x09,0xad + .byte 0x11,0x24,0x8b,0x98,0xc0,0x81,0x7f,0x21,0xa3,0x52,0xbe,0x19,0x93,0x09,0xce,0x20 + .byte 0x10,0x46,0x4a,0x4a,0xf8,0x27,0x31,0xec,0x58,0xc7,0xe8,0x33,0x82,0xe3,0xce,0xbf + .byte 0x85,0xf4,0xdf,0x94,0xce,0x4b,0x09,0xc1,0x94,0x56,0x8a,0xc0,0x13,0x72,0xa7,0xfc + .byte 0x9f,0x84,0x4d,0x73,0xa3,0xca,0x9a,0x61,0x58,0x97,0xa3,0x27,0xfc,0x03,0x98,0x76 + .byte 0x23,0x1d,0xc7,0x61,0x03,0x04,0xae,0x56,0xbf,0x38,0x84,0x00,0x40,0xa7,0x0e,0xfd + .byte 0xff,0x52,0xfe,0x03,0x6f,0x95,0x30,0xf1,0x97,0xfb,0xc0,0x85,0x60,0xd6,0x80,0x25 + .byte 0xa9,0x63,0xbe,0x03,0x01,0x4e,0x38,0xe2,0xf9,0xa2,0x34,0xff,0xbb,0x3e,0x03,0x44 + .byte 0x78,0x00,0x90,0xcb,0x88,0x11,0x3a,0x94,0x65,0xc0,0x7c,0x63,0x87,0xf0,0x3c,0xaf + .byte 0xd6,0x25,0xe4,0x8b,0x38,0x0a,0xac,0x72,0x21,0xd4,0xf8,0x07 + + // Game title + .byte 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + + // Game code + .byte 0x00,0x00,0x00,0x00 + + // Maker code + .byte 0x00,0x00 + + // Fixed value + .byte 0x96 + + // Main unit code + .byte 0x00 + + // Device type (0x00 retail, 0x80 debug) + .byte 0x00 + + // Reserved + .byte 0x00,0x00,0x00,0x00,0x00,0x00,0x00 + + // Software version + .byte 0x00 + + // Complement check + .byte 0x51 + + // Reserved area + .space 98 start_vector: // Configure stacks From 4d1945b4675e5cf27226dfa8106c2ccad3db682c Mon Sep 17 00:00:00 2001 From: sago35 Date: Tue, 31 Aug 2021 22:12:56 +0900 Subject: [PATCH 40/47] nrf52840: fix ram size --- targets/circuitplay-bluefruit.ld | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/circuitplay-bluefruit.ld b/targets/circuitplay-bluefruit.ld index 19b4c22e8..42ac1c4f6 100644 --- a/targets/circuitplay-bluefruit.ld +++ b/targets/circuitplay-bluefruit.ld @@ -2,7 +2,7 @@ MEMORY { FLASH_TEXT (rw) : ORIGIN = 0x00000000+0x26000, LENGTH = 0xED000-0x26000 /* SoftDevice S140. See https://learn.adafruit.com/introducing-the-adafruit-nrf52840-feather/hathach-memory-map. Application starts at 0x26000; user data starts at 0xED000 */ - RAM (xrw) : ORIGIN = 0x20004180, LENGTH = 37K + RAM (xrw) : ORIGIN = 0x20004180, LENGTH = 0x20040000-0x20004180 } _stack_size = 2K; From eaab05fc43129d3ac816ece486498c0b752a1262 Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Mon, 6 Sep 2021 08:54:55 +0200 Subject: [PATCH 41/47] Revert "Minor changes to support go 1.17" This reverts commit 2d224ae049286acf77298ee7060aa2dea2fdec19. --- src/os/{file_go_new.go => file_go_116.go} | 2 +- src/os/{file_go_old.go => file_go_other.go} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/os/{file_go_new.go => file_go_116.go} (99%) rename src/os/{file_go_old.go => file_go_other.go} (98%) diff --git a/src/os/file_go_new.go b/src/os/file_go_116.go similarity index 99% rename from src/os/file_go_new.go rename to src/os/file_go_116.go index 594e36abb..7049d37e5 100644 --- a/src/os/file_go_new.go +++ b/src/os/file_go_116.go @@ -1,4 +1,4 @@ -// +build go1.16 go1.17 +// +build go1.16 package os diff --git a/src/os/file_go_old.go b/src/os/file_go_other.go similarity index 98% rename from src/os/file_go_old.go rename to src/os/file_go_other.go index 20748b586..351de7acc 100644 --- a/src/os/file_go_old.go +++ b/src/os/file_go_other.go @@ -1,4 +1,4 @@ -// +build !go1.16,!go1.17 +// +build !go1.16 package os From 32de906f6d8d76e8ad0ce663c34b23760b05d809 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Sun, 5 Sep 2021 17:32:00 -0700 Subject: [PATCH 42/47] internal/task, runtime: add subsections_via_symbols to assembly files on darwin This allows the assembly routines in these files to be stripped as dead code if they're not referenced. This solves the link issues on MacOS when the `leaking` garbage collector or the `coroutines` scheduler are selected. Fixes #2081 --- src/internal/task/task_stack_amd64.S | 5 +++++ src/runtime/gc_amd64.S | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/internal/task/task_stack_amd64.S b/src/internal/task/task_stack_amd64.S index 44b5f065b..f9182d49f 100644 --- a/src/internal/task/task_stack_amd64.S +++ b/src/internal/task/task_stack_amd64.S @@ -72,3 +72,8 @@ tinygo_swapTask: // Return into the new task, as if tinygo_swapTask was a regular call. ret + +#ifdef __MACH__ // Darwin +// allow these symbols to stripped as dead code +.subsections_via_symbols +#endif diff --git a/src/runtime/gc_amd64.S b/src/runtime/gc_amd64.S index fa89479f8..c0ad7bc88 100644 --- a/src/runtime/gc_amd64.S +++ b/src/runtime/gc_amd64.S @@ -27,3 +27,8 @@ _tinygo_scanCurrentStack: // were only pushed to be discoverable by the GC. addq $56, %rsp retq + +#ifdef __MACH__ // Darwin +// allow these symbols to stripped as dead code +.subsections_via_symbols +#endif From 95ab7cb8d10084a4009ecd73ab3ec25d4c871466 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Sun, 5 Sep 2021 18:06:16 -0700 Subject: [PATCH 43/47] Makefile: add smoke test with gc=leaking to test dead asm code --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 185c1df4f..4f8284304 100644 --- a/Makefile +++ b/Makefile @@ -451,6 +451,9 @@ endif @$(MD5SUM) test.nro $(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go @$(MD5SUM) test.hex +ifneq ($(OS),Windows_NT) + $(TINYGO) build -o test.elf -gc=leaking -scheduler=none examples/serial +endif wasmtest: From d348db4a0d3b865f7b5bf7bfd4c3621de96c041a Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Sat, 4 Sep 2021 07:14:13 -0700 Subject: [PATCH 44/47] tinygo: add a flag for creating cpu profiles --- main.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/main.go b/main.go index 188a0100b..a20c5f9bf 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "path/filepath" "regexp" "runtime" + "runtime/pprof" "strconv" "strings" "sync/atomic" @@ -1089,6 +1090,7 @@ func main() { ldflags := flag.String("ldflags", "", "Go link tool compatible ldflags") wasmAbi := flag.String("wasm-abi", "", "WebAssembly ABI conventions: js (no i64 params) or generic") llvmFeatures := flag.String("llvm-features", "", "comma separated LLVM features to enable") + cpuprofile := flag.String("cpuprofile", "", "cpuprofile output") var flagJSON, flagDeps, flagTest *bool if command == "help" || command == "list" { @@ -1173,6 +1175,20 @@ func main() { os.Exit(1) } + if *cpuprofile != "" { + f, err := os.Create(*cpuprofile) + if err != nil { + fmt.Fprintln(os.Stderr, "could not create CPU profile: ", err) + os.Exit(1) + } + defer f.Close() + if err := pprof.StartCPUProfile(f); err != nil { + fmt.Fprintln(os.Stderr, "could not start CPU profile: ", err) + os.Exit(1) + } + defer pprof.StopCPUProfile() + } + switch command { case "build": if outpath == "" { From 409688e67ab121efe0bf898dd45e3e4bf52b1b80 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 8 Sep 2021 03:14:11 +0200 Subject: [PATCH 45/47] compiler: fix equally named structs in different scopes For example, in this code: type kv struct { v float32 } func foo(a *kv) { type kv struct { v byte } } Both 'kv' types would be given the same LLVM type, even though they are different types! This is fixed by only creating a LLVM type once per Go type (types.Type). As an added bonus, this change gives a performance improvement of about 0.4%. Not that much, but certainly not nothing for such a small change. --- compiler/compiler.go | 33 +++++++++++++++++++++++---------- compiler/testdata/basic.go | 15 +++++++++++++++ compiler/testdata/basic.ll | 14 ++++++++++++++ 3 files changed, 52 insertions(+), 10 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index 185859dec..c0ad39d46 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -23,7 +23,7 @@ import ( // Version of the compiler pacakge. Must be incremented each time the compiler // package changes in a way that affects the generated LLVM module. // This version is independent of the TinyGo version number. -const Version = 17 // last change: add math.arch* aliases +const Version = 18 // last change: fix duplicated named structs func init() { llvm.InitializeAllTargets() @@ -74,6 +74,7 @@ type compilerContext struct { cu llvm.Metadata difiles map[string]llvm.Metadata ditypes map[types.Type]llvm.Metadata + llvmTypes map[types.Type]llvm.Type machine llvm.TargetMachine targetData llvm.TargetData intType llvm.Type @@ -94,6 +95,7 @@ func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *C DumpSSA: dumpSSA, difiles: make(map[string]llvm.Metadata), ditypes: make(map[types.Type]llvm.Metadata), + llvmTypes: make(map[types.Type]llvm.Type), machine: machine, targetData: machine.CreateTargetData(), astComments: map[string]*ast.CommentGroup{}, @@ -315,10 +317,23 @@ func (c *compilerContext) getLLVMRuntimeType(name string) llvm.Type { return c.getLLVMType(typ) } -// getLLVMType creates and returns a LLVM type for a Go type. In the case of -// named struct types (or Go types implemented as named LLVM structs such as -// strings) it also creates it first if necessary. +// getLLVMType returns a LLVM type for a Go type. It doesn't recreate already +// created types. This is somewhat important for performance, but especially +// important for named struct types (which should only be created once). func (c *compilerContext) getLLVMType(goType types.Type) llvm.Type { + // Try to load the LLVM type from the cache. + if t, ok := c.llvmTypes[goType]; ok { + return t + } + // Not already created, so adding this type to the cache. + llvmType := c.makeLLVMType(goType) + c.llvmTypes[goType] = llvmType + return llvmType +} + +// makeLLVMType creates a LLVM type for a Go type. Don't call this, use +// getLLVMType instead. +func (c *compilerContext) makeLLVMType(goType types.Type) llvm.Type { switch typ := goType.(type) { case *types.Array: elemType := c.getLLVMType(typ.Elem()) @@ -367,12 +382,10 @@ func (c *compilerContext) getLLVMType(goType types.Type) llvm.Type { // LLVM. This is because it is otherwise impossible to create // self-referencing types such as linked lists. llvmName := typ.Obj().Pkg().Path() + "." + typ.Obj().Name() - llvmType := c.mod.GetTypeByName(llvmName) - if llvmType.IsNil() { - llvmType = c.ctx.StructCreateNamed(llvmName) - underlying := c.getLLVMType(st) - llvmType.StructSetBody(underlying.StructElementTypes(), false) - } + llvmType := c.ctx.StructCreateNamed(llvmName) + c.llvmTypes[goType] = llvmType // avoid infinite recursion + underlying := c.getLLVMType(st) + llvmType.StructSetBody(underlying.StructElementTypes(), false) return llvmType } return c.getLLVMType(typ.Underlying()) diff --git a/compiler/testdata/basic.go b/compiler/testdata/basic.go index 3a6367043..ab8b5986a 100644 --- a/compiler/testdata/basic.go +++ b/compiler/testdata/basic.go @@ -55,3 +55,18 @@ func complexMul(x, y complex64) complex64 { } // TODO: complexDiv (requires runtime call) + +// A type 'kv' also exists in function foo. Test that these two types don't +// conflict with each other. +type kv struct { + v float32 +} + +func foo(a *kv) { + // Define a new 'kv' type. + type kv struct { + v byte + } + // Use this type. + func(b *kv) {}(nil) +} diff --git a/compiler/testdata/basic.ll b/compiler/testdata/basic.ll index cdafea0f1..aca2ece0a 100644 --- a/compiler/testdata/basic.ll +++ b/compiler/testdata/basic.ll @@ -3,6 +3,9 @@ source_filename = "basic.go" target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target triple = "wasm32--wasi" +%main.kv = type { float } +%main.kv.0 = type { i8 } + declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr { @@ -98,3 +101,14 @@ entry: %7 = insertvalue { float, float } %6, float %5, 1 ret { float, float } %7 } + +define hidden void @main.foo(%main.kv* dereferenceable_or_null(4) %a, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + call void @"main.foo$1"(%main.kv.0* null, i8* undef, i8* undef) + ret void +} + +define hidden void @"main.foo$1"(%main.kv.0* dereferenceable_or_null(1) %b, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + ret void +} From 602d3d7c788392147225c749d5099d7600b14d42 Mon Sep 17 00:00:00 2001 From: BCG Date: Tue, 7 Sep 2021 23:37:19 -0400 Subject: [PATCH 46/47] board: add Raytac MDBT50Q-RX Dongle with TinyUF2 --- README.md | 3 ++- src/machine/board_mdbt50qrx.go | 40 ++++++++++++++++++++++++++++++++++ targets/mdbt50qrx-uf2.json | 12 ++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 src/machine/board_mdbt50qrx.go create mode 100644 targets/mdbt50qrx-uf2.json diff --git a/README.md b/README.md index 14ecfbc55..dbb17f653 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for You can compile TinyGo programs for microcontrollers, WebAssembly and Linux. -The following 69 microcontroller boards are currently supported: +The following 70 microcontroller boards are currently supported: * [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333) * [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333) @@ -102,6 +102,7 @@ The following 69 microcontroller boards are currently supported: * [PJRC Teensy 4.0](https://www.pjrc.com/store/teensy40.html) * [ProductivityOpen P1AM-100](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html) * [Raspberry Pi Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/) +* [Raytac MDBT50Q-RX Dongle (with TinyUF2 bootloader)](https://www.adafruit.com/product/5199) * [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html) * [Seeed Seeeduino XIAO](https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.html) * [Seeed Sipeed MAix BiT](https://www.seeedstudio.com/Sipeed-MAix-BiT-for-RISC-V-AI-IoT-p-2872.html) diff --git a/src/machine/board_mdbt50qrx.go b/src/machine/board_mdbt50qrx.go new file mode 100644 index 000000000..45a059ded --- /dev/null +++ b/src/machine/board_mdbt50qrx.go @@ -0,0 +1,40 @@ +// +build mdbt50qrx + +package machine + +const HasLowFrequencyCrystal = false + +// GPIO Pins +const ( + D0 = P1_13 // LED1 + D1 = P1_11 // LED2 (not populated by default) + D2 = P0_15 // Button +) + +const ( + LED = D0 +) + +// MDBT50Q-RX dongle does not have pins broken out for the peripherals below, +// however the machine_nrf*.go implementations of I2C/SPI/etc expect the pin +// constants to be defined, so we are defining them all as 0 +const ( + UART_TX_PIN = 0 + UART_RX_PIN = 0 + SDA_PIN = 0 + SCL_PIN = 0 + SPI0_SCK_PIN = 0 + SPI0_SDO_PIN = 0 + SPI0_SDI_PIN = 0 +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Raytac MDBT50Q - RX" + usb_STRING_MANUFACTURER = "Raytac Corporation" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x810B +) diff --git a/targets/mdbt50qrx-uf2.json b/targets/mdbt50qrx-uf2.json new file mode 100644 index 000000000..a5cd3d345 --- /dev/null +++ b/targets/mdbt50qrx-uf2.json @@ -0,0 +1,12 @@ +{ + "inherits": ["nrf52840"], + "build-tags": ["mdbt50qrx", "nrf52840_reset_uf2", "softdevice", "s140v6"], + "serial": "usb", + "flash-1200-bps-reset": "true", + "flash-method": "msd", + "serial-port": ["acm:239a:801b", "acm:239a:010b", "acm:239a:810c"], + "msd-volume-name": "MDBT50QBOOT", + "msd-firmware-name": "firmware.uf2", + "uf2-family-id": "0xADA52840", + "linkerscript": "targets/circuitplay-bluefruit.ld" +} From 485a9284e74ea9ff8f0f37f2402ae80448b33f10 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Tue, 7 Sep 2021 20:29:34 -0700 Subject: [PATCH 47/47] builder: add missing error check for ioutil.TempFile() --- builder/cc.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/builder/cc.go b/builder/cc.go index 7f0dad28a..3aac75586 100644 --- a/builder/cc.go +++ b/builder/cc.go @@ -155,6 +155,10 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands // Write dependencies file. f, err := ioutil.TempFile(filepath.Dir(depfileCachePath), depfileName) + if err != nil { + return "", err + } + buf, err = json.MarshalIndent(dependencySlice, "", "\t") if err != nil { panic(err) // shouldn't happen